[GYCTF2020]Node Game(ssrf拆分攻击)
我们打开题目发现有两个页面,应该是源码,应该是上传页面
来自郭学姐写的注释
1 | var express = require('express'); |
我们来分析源码
1 | app.get('/', function (req, res) { |
首先是个路由,它过滤了/和\,并且会将/template/"action".pug
这一文件进行pug渲染
1 | app.post('/file_upload', function (req, res) { |
然后是个文件上传的路由,它允许post请求,并限制了ip只能由127.0.0.1访问,同时var ip = req.connection.remoteAddress;说明ip是不能通过请求头来伪造的。
然后会将路径根据mimetype进行拼接:
1 | /uploads/' + req.files[0].mimetype +'/'; |
结合上面的对/template/下的pug文件进行渲染,可以想到使用../
,如:uploads/../template/+filename
这样就相当于传了一个文件到template下
但前提是要先进行ssrf
1 | app.get('/core', function(req, res) { |
然后这里是接受了一个参数q,并且对本地进行了请求,url = ‘http://localhost:8081/source?’ + q,这里就是ssrf的攻击点
通过拆分攻击实现的ssrf攻击
1.对/core路由发起切分攻击,请求/core的同时还向source路由发出上传文件
的请求
2.由于路由是先读取/temple/目录下的pug文件,再将其渲染到当前页面,因此应该上传包含命令执行的pug文件;文件虽然默认上传至/upload/目录下,但可以通过目录穿越将文件上传到/template目录
3.访问上传到/template目录下包含命令执行的pug文件
假设一个服务器,接受用户输入,并将其包含在通过HTTP公开的内部服务请求中,像这样:
1 | GET /private-api?q=<user-input-here> HTTP/1.1 |
如果服务器未正确验证用户输入,则攻击者可能会直接注入协议控制字符到请求里。假设在这种情况下服务器接受了以下用户输入:
1 | “x HTTP/1.1\r\n\r\nDELETE /private-api HTTP/1.1\r\n” |
在发出请求时,服务器可能会直接将其写入路径,如下:
1 | GET /private-api?q=x HTTP/1.1 |
说到底就是\r\n成功生效
接收服务将此解释为两个单独的HTTP请求,一个GET后跟一个DELETE
好的HTTP库通通常包含阻止这一行为的措施,Node.js也不例外:如果你尝试发出一个路径中含有控制字符的HTTP请求,它们会被URL编码:
1 | http.get('http://example.com/\r\n/test').output |
但是,上述的处理unicode字符错误意味着可以规避这些措施。考虑如下的url,其中包含一些带变音符号的unicode字符
1 | 'http://example.com/\u{010D}\u{010A}/test' |
当Node.js版本8或更低版本对此URL发出GET请求时,它不会进行转义,因为它们不是HTTP控制字符:
1 | http.get('http://example.com/\u010D\u010A/test').output |
但是当结果字符串被编码为latin1写入路径时,这些字符将分别被截断为“\r”和“\n”:
1 | Buffer.from('http://example.com/\u{010D}\u{010A}/test', 'latin1').toString() |
Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的unicode字符
说白了,上面这段的意思就是我们可以利用一些特殊字符,它们在URL请求时不会被转义处理,但是当它到了js引擎时,由于其默认用的是latin1,因此可以将我们用的特殊字符转义得到我们需要的字符,从而达到ssrf的目的
原理了解完后,接下来利用那个上传页面上传一个文件,burp suite 抓下包
然后下面就是如何构造http走私了,下面这个是rce的脚本,由于有黑名单验证,可以拼接绕过,然后由于pug模板引擎,需要在前面加上-,来表示开始一段代码
我们到网上找个脚本
1 | import urllib.parse |
运行,得到flag