VCTF2024-web
hackjs
看下源码:
const express = require('express') const fs = require('fs') var bodyParser = require('body-parser'); const app = express() app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.post('/plz', (req, res) => { venom = req.body.venom if (Object.keys(venom).length < 3 && venom.welcome == 159753) { try { if(venom.hasOwnProperty("text")){ res.send(venom.text) }else{ res.send("no text detected") } } catch { if (venom.text=="flag") { let flag=fs.readFileSync("/flag"); res.send("Congratulations:"+flag); } else { res.end("Nothing here!") } } } else { res.end("happy game"); } }) app.get('/', function(req, res, next) { res.send('<title>oldjs</title><a>Hack me plz</a><br><form action="/plz" method="POST">text:<input type="text" name="venom[text]" value="ezjs"><input type="submit" value="Hack"></form> '); }); app.listen(80, () => { console.log(`listening at port 80`) })
很显然的思路,首先要进if,那就需要有个welcome的属性,然后里面让hasOwnProperty抛错,从而进入catch块,并且键值对里面还要有text==flag的东西。
首先很容易网上搜到,hasOwnProperty是来自Object.prototype的方法,如果对象内部重新写一个hasOwnProperty,再去调用的时候就会调用你自己重新覆写的hasOwnProperty,所以就会报TypeError这种类型的错。
但是键值对限制死了只能传<3,welcome一个,hasOwnPeoperty一个,text一个,超了:
然后重新审计源码,发现根目录路由可以传参,抓个包看看:
这里传参的是数组。
这里我一开始想到了多线程,能不能这里传数据,那边传json然后卡进去,但是实际上传参后我发现,如果开多线程是不行的,回显是happy game
如果写个脚本一起传参也不行,因为等效于一起发的,还是回显happy game
怎么办?
重新考虑,能不能从原型链下手,重温一下prototype:
JavaScript原型链污染原理及相关CVE漏洞剖析 - FreeBuf网络安全行业门户
n8tz/CVE-2022-24999:“qs”原型中毒漏洞 ( CVE-2022-24999 ) (github.com)
非常形象生动的图:
所以我们可以想到:
一次[__proto__]到venom构造函数的prototype,再一次[__proto__]到Object.prototype,我们在这里创造welcome,本质上能让这个welcome继承下去,所以venom.welcome能够访问到,但是venom对象里面只存在我们写入的"hasOwnProperty":1和"text":"flag"两个键值对,所以能够绕过键值对的长度判断(这是鄙人理解,如有错误还请指正!)。
而且这里只用一次__proto__也是可行的,详见:2024 第一届VCTF 纳新赛 Web方向 题解WP (hqwc.cn)
没来得及截图,看赛后群里师傅发的:
但是有趣的是,这道题发json这么做是不能出的:
所以这种解析方法应该是只存在于把__proto__写入数组方式的情况,对应CVE-2022-24999,高版本已经修复。
Archived elephant(Unsolved)
待复现,本地docker-compose一直报错打不开浏览器。修好了可以试试。