xss之浏览器的编码和解码
在做一些xss的过滤和绕过时,总是使用各种编码绕过,有时候是unicode,有时候是urlencode,有时候是HTML实体化编码,但是很多时候都是直接fuzz,对于存在xss的漏洞点,并没有清楚的分析和认识,对于浏览器的编码和解码没有系统的认识,这会导致我们的payload理解不到位,从而错失很多个src中的xss漏洞。为了以后能更好的挖掘xss漏洞和更快的fuzz出过waf手法。今天就来详细梳理下浏览器的编码和解码过程。
浏览器工作过程
我们都知道网上有很多人写了一个”url点击后电脑都做了什么”,如果感兴趣可以去看看。这里不在赘述请求服务端的流程,这里主要梳理的是一个页面响应后浏览器是如何渲染给我们看的。
浏览器是可以解析html、svg、xhtml的,我们日常使用的网页也基本都是这三种格式,浏览器首先将响应的HTML类文本解析为DOM树。当然CSS解析也会解析为CSS的TREE,但是这里不讨论CSS。我们下面都以HTML举例
浏览器通过解析HTML为DOM 树并通过开始标签、结束标签和标签的属性、方法等根据算法进行解析的。所以浏览器首先是解析HTML,然后根据标签构建DOM树,此时浏览器是没办法识别实体编码的那些内容的,构建完dom树后,浏览器才会对标签里的内容进行解析,然后遇到实体编码的话会解码。所以实体化编码的标签是不会构建为节点的(所以实体化编码很多时候都有用)。
浏览器通过上述的步骤来解析各个标签,最终结合javascript的引擎和CSS的引擎完成对整个页面的操作和渲染。
浏览器的解码顺序
页面响应后浏览器拿到了HTML的页面文本,浏览器首先根据节点构建DOM树,构建的时候是不会进行解码的,所以如果你输入一个
1 | <img src="http://xxx.com/1.jpg"> |
即使http://xxx.com/1.jpg真的是一张图片也可以访问到,这个img标签也是显示不出来的,因为构建的时候没有解码,src不会被认为是一个链接,也就没有了这个属性。
javascript在html解析中也存在一定的作用,会参与其dom树的构建。所以这并不是线性顺序的。比如dom型xss,但是大方向上,解析还是先html的解析。
DOM节点建立起来以后就要进行解析了,比如他遇到script标签就需要JavaScript来解析,当遇到style就给css的解析器。src、href这种链接形式的会调用URL解码、同时对于一些伪协议,也会调用相应的解析器。
比如下面这个例子:
1 | <a href="javascript:%61%6c%65%72%74">123</a> |
这个例子是可以弹窗的,因为dom树构建后,解析过程中发现href为一个链接,会进行urldecode,然后因为javascript伪协议,会交给javascript来解析,最终造成代码执行。
当然javascript本身就支持unicode,所以下面这样也是可以弹窗的:
1 | <a href="javascript:%5c%75%30%30%36%31%6c%65%72%74(123);">12345</a> |
下面我们用两张图来看下浏览器工作和解码的流程:
图一DOM树:
![image-20221026102503733](/Users/geez/Library/Application Support/typora-user-images/image-20221026102503733.png)
图二构建流程:
![image-20221026102859384](/Users/geez/Library/Application Support/typora-user-images/image-20221026102859384.png)
如上面两个图所示,HTML解析构建DOM->script参与构建DOM->DOM TREE构建完后会进行HTML解码->rendering渲染(如果中间遇到URL会进行URLDECODE)
既然了解了解析的流程,那么我们也得了解下可以自解码的编码有哪些?
HTML有两种编码方式:进制编码和实体编码。
1 | < |
JavaScript自解码有三种:Unicode,十进制,十六进制
注意:在js中,单引号,双引号和圆括号等属于控制字符,编码后将无法识别,所以下面这种无法执行:
1 | # 不可以执行 |
URL只有一种编码方式就是URL,这个很好理解。
在实践中理解
下面是一个常用的xss payload
1 | <a href="javascript:alert(123)">1</a> |
因为javascript解析器可以解析unicode,所以我们可以变种:
1 | <a href="javascript:\u0061\u006c\u0065\u0072\u0074(123)">1</a> |
因为href是一个链接所以肯定会被URL解码,所以我们可以变种
1 | <a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34%28%31%32%33%29">1</a> |
现在因为构建DOM树的时候会对HTML进行自解码,所以我们可以变种
1 | <a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34%28%31%32%33%29">3333</a> |
现在我们对于这种浏览器的编码有个基本的认识了,但是前面说到这个javascript可以操纵dom,进行动态生成节点,也是产生domxss的重要原因,下面这个解码就比较复杂了:
1 | <html> |
看看上面这个如果innerHTML可控会有多少种编码方式?如果你真的理解了这里就不再赘述,应该是多层unicode和HTML编码可以混用依然执行弹窗。
所以,得出结论,在大多数情况下如果必须插入标签才能触发的xss实体化编码还是好用的,当然,具体情况要具体分析
另外我们在一些过滤绕过中,发现svg的出场次数也挺高的,这是因为svg是可以内嵌其他标签,同时会被html解码
1 | <svg> |
总结
HTML解析、CSS解析、JS解析和URL解析。这四个解析器的解析顺序搞懂,XSS的编码绕过问题就比较容易理解了,遇到一些SRC的xss点也可以绕过的更从容一点。