Background
本来在考研还是想着看看corctf 做到其中一道名为simplewaf的题目卡住了,。。 这里带大家好看看我关于这道题的心路历程
0x01 搭建篇
主办方给了DockerFile 但我第一次启动时候踩了一些坑,所以我简单的进行了修改 并写了个run.sh方便大家启动
链接: https://pan.baidu.com/s/1cUQewiSj3xC2IrIRgvs4Xw 提取码: t22m
0x02错误的尝试
const express = require("express");
const fs = require("fs");
const app = express();
const PORT = 80;
app.use((req, res, next) => {
if([req.body, req.headers, req.query].some(
(item) => item && JSON.stringify(item).includes("flag")
)) {
return res.send("bad hacker!");
}
next();
});
app.get("/", (req, res) => {
try {
res.setHeader("Content-Type", "text/html");
res.send(fs.readFileSync(req.query.file || "index.html").toString());
}
catch(err) {
console.log(err);
res.status(500).send("Internal server error");
}
});
app.listen(PORT, () => console.log(`web/simplewaf listening on port ${PORT}`));
代码看着很清晰,文件结构里面也清楚的告诉我们flag是以flag.txt的形式存在的
if([req.body, req.headers, req.query].some(
(item) => item && JSON.stringify(item).includes("flag")
))
这里的waf判断不能包含flag字样,其实看见nodejs的后端我们都会思维定式的想去看看可不可以unicode伪造
尝试了几个发现不行 他会被直接呈现出来
但其实只要你关注数据流向你可以看到你输入的string会流入readFileSync
函数,他并没有任何关于解码的操作他只会进行比对
然后空白大哥就提醒我会不会是原型链污染,但实际上你看到没有任何关于对象的操作 merge之类的 你的可控点只有req.query.file 你只能污染你自己对象的属性 所以应该也没什么用
想到能不能用条件竞争之类的手法能不能在某次bypass掉验证,去询问了下猫哥,猫哥告诉我use的middleware肯定会执行的
跟rdd我大哥交流的时候他提到了
这里使用两个&&很奇怪 有没有可能让前面为false来bypass掉,但其实也没什么用,那其实最终还是要回归函数。看看里面经历了什么,我在搜索资料的时候也看到了b1ueocean哥提到的
进程在打开文件时会创建一个file descriptor链接到该文件,此时可通过fd来代替文件名读取文件内容。fs.readFileSync()正好也支持fd作为参数。
但他在打开文件前就已经进行了拦截 所以gg
0x03 跟进函数
想到这么试下去也没头,我就下载了源码
可以看到在readFileSync这里面他调用openSync进行打开文件,我们继续跟进 在这里看见他进行path的判断
继续跟进toPathFileURL
在这里提到了判断是不是URL对象,如果是就允许fileURLToPath
但我其实早就翻文档看见 他是可以支持 new URL的
我们继续跟进
他这里需要属性 path不为空 并且href origin这两个属性要存在
所以其实他并没有进行一个完整的实例检查,只检查了属性的存在性
接下来的fileURLToPath 要求protocol必须file:开头
跟Posix直接就到了 hostname不为空 然后pathname传进来要读的文件就行
注意这里面path name double 编码 因为函数内部进行一次编码
整理下
1.href origin属性存在
2.protocol要求file: hostname不为空
3.pathname二次编码
但是我不知道怎么把它写进去就卡在这里了
0x04 赛后复盘
等早上睡醒看见空白大哥发给我一个payload人直接傻了
在我的印象中
他是不允许数组的。。
后来发现用了express qs来解析参数
file[a]=a&file[c]=b ==> {"a":"a","c":"b"}
这个对象会被readFilesync识别 显而易见 被自己蠢哭了
最后payload
http://127.0.0.1:3456/?file[href]=aa&file[origin]=aa&file[protocol]=file:&file[hostname]=&file[pathname]=%2566lag.txt