前言
nodejs 8.12.0版本的请求拆分攻击(感觉现在叫http走私要好一点2333),pug模板的注入导致rce
0x01 首页
当我们访问http://web2.ctf.nullcon.net:8081/
后会显示下面的页面。
由于上面没有什么特点,url参数,交互框等,因此我们查看源代码看看有没有什么提示。
通过源码(F12查看的)我们可以看到它在页面加载js代码完成后发送了个ajax请求:
1 | var xhttp = new XMLHttpRequest(); |
我使用get请求发送/core?q=1
,但是结果跟上面的首页是一样的只有一个动图,这时你可能会想会不会是sql注入,服务器端注入等,但是经过一系列的反恐尝试之没有什么用。
但是在我们查看源代码的时候还给了个提示<!-- /source -->
通过访问/source
可以获取题目源码。
1 | //node 8.12.0 |
0x02 代码分析
通过阅读上面的代码,我们可以看到node
的版本是8.12.0
,服务器端使用的框架是express
,使用nodejs
自带的http
包发送某些请求,服务端使用pug
作为渲染模板(原理加jade
)。
在没有发现漏洞之前,先梳理一下代码的流程:
1./
:
使用sendFile
来读取index.html
页面并且渲染该页面,通过前面我们知道该页面有提示的内容。
2./source
:
该页面返回了服务器端的全部代码。
3./getMeme
:
该页面没有什么用就是返回了一个iframe
框架,其中是个动图。
4./flag
:
这里验证远程地址包含127.0.0.1
,adminauth===secretpassword
,headers
头中必须包含pug
字段并且其值不能包含小写字母,最后会用pug
引擎渲染pug
,但是这里在渲染容易受到服务器端注入攻击https://zhuanlan.zhihu.com/p/28823933,由此我们可以断定前面需要用SSRF
结合CRLF
来绕过,不由联想到node
的版本号,因为该版本存在request splitting漏洞。
pug
模板注入示例:
1 | const pug=require('pug'); |
结果如下:
1 | 1 |
对于pug
模板的相关知识可以参考如下链接:
https://pugjs.org/language/interpolation.html
通过动态的调试我们可以看到代码的拼接过程:
1 | var runtime = require('./'); |
到:
1 | (function anonymous(pug |
上面的这段代码中的template
函数就是通过Function来构造的,代码拼接进去的部分就是\u003C
后面的console.log("1")
和\u003E
前面的console.log("1")
,拼接了两个console.log("1")
这就是,然后在函数运行的时候就会执行console.log("1")
。
我们可以看到代码拼接进去了,结合上面两段代码简化一下就是:
1 | const t=Function('pug','function template(locals) {(console.log(locals))}'+'\n' + |
request splitting漏洞代码示例:
1 | const http = require('http') |
结果如下:
1 | listening on [any] 8000 ... |
由上面的结果可知我们已经成功实现了CRLF
注入(上面的显示:
为%7B %7D
可能是我kali的问题)。
接下来我们需要看那里调用了http.get
导致SSRF。
5./core
这里的q
是我们可控的,而且这里使用了http.get
来发起请求刚好满足我们上面的条件,但是构造的url
中不能有["global", "process","mainModule","require","root","child_process","exec","\"","'","!"]
字符串,我们可以对代码进行jsfuck加密绕过,但是由于加密后的代码太大,请求量会很大,因此一般用js
的匿名函数来绕过,且字母我们可以用8进制,16进制来代替,例如:
1 | []['map']['constructor']('alert(1)')(); |
构造的脚本如下:
1 | function stringToHex(str,mode=8){ |
上面的原理相当于构造了个new Function("alert(1)")();
,由于Object
的constructor
是指向Function
的,示例如下:
1 | //我们可以使用以下方式创建一个函数: |
代码我们也梳理了一遍,现在我们来看看怎么rce拿到flag。
0x03 利用过程:
solve.py
1 | # coding=UTF-8 |