Background

本来在考研还是想着看看corctf 做到其中一道名为simplewaf的题目卡住了,。。 这里带大家好看看我关于这道题的心路历程

0x01 搭建篇

主办方给了DockerFile 但我第一次启动时候踩了一些坑,所以我简单的进行了修改 并写了个run.sh方便大家启动

链接: https://pan.baidu.com/s/1cUQewiSj3xC2IrIRgvs4Xw 提取码: t22m

image-20220808094053959.png

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函数,他并没有任何关于解码的操作他只会进行比对

image-20220808100918286.png

然后空白大哥就提醒我会不会是原型链污染,但实际上你看到没有任何关于对象的操作 merge之类的 你的可控点只有req.query.file 你只能污染你自己对象的属性 所以应该也没什么用

想到能不能用条件竞争之类的手法能不能在某次bypass掉验证,去询问了下猫哥,猫哥告诉我use的middleware肯定会执行的

跟rdd我大哥交流的时候他提到了

image-20220808101747894.png

这里使用两个&&很奇怪 有没有可能让前面为false来bypass掉,但其实也没什么用,那其实最终还是要回归函数。看看里面经历了什么,我在搜索资料的时候也看到了b1ueocean哥提到的

进程在打开文件时会创建一个file descriptor链接到该文件,此时可通过fd来代替文件名读取文件内容。fs.readFileSync()正好也支持fd作为参数。

但他在打开文件前就已经进行了拦截 所以gg

0x03 跟进函数

想到这么试下去也没头,我就下载了源码

image-20220808102325542.png

可以看到在readFileSync这里面他调用openSync进行打开文件,我们继续跟进 在这里看见他进行path的判断
image-20220808102504435.png

继续跟进toPathFileURL

image-20220808102905108.png

在这里提到了判断是不是URL对象,如果是就允许fileURLToPath

但我其实早就翻文档看见 他是可以支持 new URL的

我们继续跟进

image-20220808103648603.png
他这里需要属性 path不为空 并且href origin这两个属性要存在

所以其实他并没有进行一个完整的实例检查,只检查了属性的存在性

接下来的fileURLToPath 要求protocol必须file:开头

image-20220808104142039.png

跟Posix直接就到了 hostname不为空 然后pathname传进来要读的文件就行

image-20220808104840449.png
注意这里面path name double 编码 因为函数内部进行一次编码

整理下

1.href origin属性存在
2.protocol要求file: hostname不为空
3.pathname二次编码

但是我不知道怎么把它写进去就卡在这里了

0x04 赛后复盘

等早上睡醒看见空白大哥发给我一个payload人直接傻了

在我的印象中

image-20220808105712497.png

他是不允许数组的。。

后来发现用了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