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 树:

图二构建流程:

如上面两个图所示,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 点也可以绕过的更从容一点。