探究浏览器编码规则

背景

某一天,自己在https://xss.haozi.me/#/0x00做XSS练习做到第8题的时候,想到如果这个题用HTML的实义字体表示<>是不是也是可以的,然后发现不行,就在想这其中的原因,然后,在网上搜资料的时候,有一篇大佬的博客详细的解释了浏览器编码的一些规则和顺序,让我对浏览器编码有了更深一步的了解。在文章最后,我会贴上原文链接,这种大佬文章,属实牛批,大家可以自己前去拜读。

自己理解

一、为什么会存在编码?

主要原因是有些内容不适合直接传输,可能包含隐私信息,可能内容过大,也可能直接传输会造成代码歧义。
就比如说

1.1 为什么需要URL编码

对于URL编码而言,&符号本来是用于分割多个参数,但可能会有这样一种情况,假设他要传的参数的键值为key=v&lue,这就会因为key参数的值v&lue中因为携带&而造成歧义。因此至少需要对&进行编码,所以才会有URL编码。

1.2 为什么需要HTML编码

对于HTML编码而言,正常情况下,"<“符号一般被浏览器识别为元素定义的开始,而”>"符号会被浏览器识别为元素的结束,但假设存在这样一个指令:

<div id="1>"></div>

由于div标签的id属性的值位1>,这个值携带了>,同样也会造成歧义。因此至少也需要对>进行编码,所以才会有HTML编码,也就是使用字符实体。

浏览器解码规则

浏览器无论什么情况都会遵守的解码规则:

1.首先,需要HTML解析器对HTML文档进行解析完成HTML解码,并且自创建DOM树

2.其次,javascript或者css解析器会对内联脚本进行解析,完成JS CSS解码

3.URL解码会根据URL所在的顺序不同而在JS解码前或者解码后进行解码

HTML编解码

HTML解析器

HTML中有五类元素:

1.空元素。如<area>、<br>、<base>、<br>、<col>、<command>、<input>、<img>、<hr>、<link>等,之所以称之为空元素是因为他们没有闭合标签,所以不能容纳任何内容,所以是空标签。

2.原始文本元素。如<script>、<style>等,他们可以容纳文本。

3.RCDATA元素。如<textarea>、<title>等,他们可以容纳文本和字符引用

4.外部元素。如MathML命名空间或者SVG命名空间的元素。他们可以容纳文本,字符引用,其他元素和注释等

5.基本元素。除了以上四种元素以外的其他元素。他们可以容纳文本,字符引用,其他元素和注释等

HTML解析器作为一个状态机,他从输入流中获取字符并按照转换规则从一种状态转换到另一种状态。在解析过程中,任何时候他只要遇到一个<符号(后面没有跟/符号的时候),他就会进入标签开始状态(Tag open state),然后转变到标签名状态(Tag name state)…知道他遇到>符号,解析器就会进入数据状态并释放当前标签的token,当解析器处于数据状态时,每当发现一个完整的标签,就会释放出一个token。

在这里插入图片描述

<div >hello</div>

这个图的流程就是解析器先是遇到了<符号,他就在tag open阶段,然后开始到tag name阶段解析标签名,解析完标签名之后就会遇到>符号,从而解析器进入data阶段,然后开始解析数据,解析完数据之后,又遇到了<符号,然后又进入tag open阶段,紧跟着又遇到了/符号,进入close tag open state阶段,然后又遇到字母开始解析tag name,解析完之后,肯定又遇到>符号,进入数据阶段,数据阶段没有数据,转而又遇到<符号,进入tag open阶段。至此,完成一次标签的完整解析。

由此可见,解析器只有在数据状态,RCDATA状态,属性值状态时,字符实体才会被解码为对应的字符,所以对于这样的两行代码

`<&#104;1>hhhhh</h1>` 那么就无法解析这个编码

`<h1>hhhhhhhh&#104</h1>` 却能够正确成功的解析

或者还有下面这个实例:

<div>&#60;img src=x onerror=alert(4)&#62;</div>
<>被编码为字符实体&#60;&#62;HTML解析器解析完<div>时,会进入数据状态(Data State)。
接着解析到实体&#60;时因为处在数据状态(Data State)就会对实体进行解码为<,
后面的&#62;同样道理被解码为>

有个问题似乎,被解码后,这里的img标签是否会被解析为HTML标签而被JS执行呢?

这里需要注意的是,解析器不会因为使用字符引用后就转换到标签打开状态,不进入标签打开状态他就无法发布为HTML标签,因此,是不会创建新的HTML标签的,只会将其作为数据来处理。

原始文本元素类型标签下的所有字符实体编码都不会被HTML编码。HTML解析器解析到script、style标签的内容块(数据部分)的时候,状态会进入Script Data State,该状态并不在之前所说的解码字符实体的三条状态之中。因此,对于

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

上述字符实体并不会被解码,也就不会执行JS

而对于RCDATA类型标签下的所有字符实体都会被解析。大部分解析器解析到textarea、title标签的内容块(数据部分)的时候,状态会进入到RCDATA State,该状态处于之前所说的解码字符实体的三条状态之中,所以,字符实体会解码。

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

上述代码中的字符实体><和>会被解码为<符号和>符号,但是里面的js代码同样不会被执行,原因还是因为解码字符实体时,解析器不会进入标签打开状态,里面的<script>标签不会被解析为HTML标签。

外部元素
<svg>遵循XML和SVG的定义

<script>alert&#40;1)</script>
不能弹窗,原始文本元素类型标签下的所有字符实体编码都不会被HTML解码
<svg><script>alert&#40;1)</script>
能弹窗,在XML中,&#40;会被解析成(,在XML中实体会自动转义,除了<![CDATA[]]>包含的实体

javascript解析器

说完了解码的第一步,接下来就是JS解码或者URL解码

JS解码的规则相对来说比较严谨,他对除了阿拉伯数字和字母外的东西都进行了了一个编码

最常用的如\uXXXX,这种写法为unicode转义序列,表示一个字符,其中XXXX表示一个16进制数字,如<unicode编码为\u003c

JavaScript中有三个地方可以出现Unicode字符转移序列:
1.字符串中

Unicode转义序列如果出现在字符串中,他只会解释为普通字符,而不会破坏字符串的上下文,例如

<script>alert("\u0031\u0030");</script>

被编码转移的部分为10,是字符串,会被正常解码,JS代码也会执行

2.标识符中

如果Unicode转义序列出现在标识符中,即变量名(如函数名等…),它会被进行解码,例如

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

被编码转移的部分为alert字符,是函数名,属于在标识符中的情况,因此会被正常解码,JS代码也会执行

3.控制字符中
若Unicode转义序列存在于控制字符中,那么它会被解码但不会被解释为控制字符,而会被解释为标识符或字符串字符的一部分,例如

<script>alert\u0028"xss"); </script>

这行代码会无法执行.

从浏览器的视角来看,首先读到<开始读取标签,然后督导script调用js解析器。在js中,单引号,双引号和圆括号属于控制字符,编码后将无法识别,正确理解这段解码应该是把单引号解码成了标识符的一部分alert(,所以控制字符中是不能正确解释的

总结,Unicode序列不能出现在控制字符中,否则不能被解释。

实例1:

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

被编码部分为alert(11)
JS不会被执行,因为控制字符被编码了

示例2:

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

被编码部分为alert及括号内为12。
JS不会被执行,因为括号内的编码不能被正常解释,要么使用ASCII数字,要么加引号使其变成字符串,这种没有引号,也不是ASCII数字的会报错

示例3:

<script>alert('13\u0027)</script>

被编码处为’符号
JS不会被执行,因为’符号位控制字符,他只会解码成为字符串或者标识符的一部分,无法识别

示例4:

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

JS会被执行,因为被编码部分处于字符串内,只会被解释成为普通字符

示例5:

<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029>

被编码部分为alert(1)
不会被执行,如果从浏览器的视角来看,首先读到<开始读取标签,然后读到onerror调用js解析器。在js中,单引号,双引号和圆括号属于控制字符,解码后将无法识别

下面这种方式可以解析:

<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')>

如果结合上面的HTML编码,按照解析顺序翻过去,先对一段代码进行JS编码,然后进行HTML编码

<img src="1" onerror=&#92;&#117;&#48;&#48;&#54;&#49;&#92;&#117;&#48;&#48;&#54;&#99;&#92;&#117;&#48;&#48;&#54;&#53;&#92;&#117;&#48;&#48;&#55;&#50;&#92;&#117;&#48;&#48;&#55;&#52;&#40;&#39;&#92;&#117;&#48;&#48;&#51;&#49;&#39;&#41;>

被编码部分为οnerrοr=alert(‘1’)>

浏览器读到<标签开始构造语法树,然后HTML解码,解码之后发现onerror于是进行JS解码,成功弹窗。具体解码过程应该是先转换成

onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')

然后进行JS解码,由于编码部分都是字符串或者变量名,所以可以正常解码,解码之后自动执行,所以可以实现弹窗

这一性质可以延伸到开发人员的一个马虎

比如开发人员单纯的设置HTML实体编码为防御XSS的手段,但是用户输入点却存在于alert中

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

如果用户正常输入,凡是存在<符号,'符号等都能被转码

但是攻击者可以通过语句");alert("test,然后HTML编码即可绕过

例如

<img src = "https://gss1.bdstatic.com" onclick = 'alert("FIRST XSS | &#34;&#41;&#59;&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#116;&#101;&#115;&#116;")'>

被编码部分为");alert("test

发现会弹窗两次,是因为服务端进行一个HTML解码后发现存在两个alert()弹窗,于是会弹窗两次

对于这种情况,正确的防御XSS方法:

在服务器端,应该是先对其进行JS编码,然后在进行HTML编码。

在浏览器端,首先经过第一步HTML解码后变为

\u0022\u0029\u003B\u0061\u006C\u0065\u0072\u0074\u0028\u0022\u0074\u0065\u0073\u0074

然后JS解析器工作,变为 ");alert("test,由于JS解析时只有标识符名称不会被当成字符串,而控制字符会被解析标识为名称或者字符串,因此\u0022\被解释为双引号文本,\u0028和\u0029被解释成为圆括号文本,不会变为控制字符被解析执行。所以只会弹窗一次

URL解析器

URL解析器也可看作是状态机。

1.URL中的Scheml部分(协议部分)必须为ASCII字符,即不能被任何编码,否则URL解析器的状态机将进入No Scheme状态

例如:

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

URL编码部分的是javascript:alert(1)。

这个代码不会被执行,因为作为Scheme部分的“javascript”这个字符串被编码,导致URL解析器状态为No Scheme

2.URL中的:也不能被任何形式编码,否则URL解析器的状态机也会进入No Scheme状态。

例如:

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

不被执行,由于URL中的:被编码为%3,导致URL状态机进入No Scheme状态。

在例如:

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

javascript"这个字符串被实体化编码,:没有被编码,alert(2)被URL编码

会执行,因为javascript还是ASCII编码,:也没有被编码

首先,在HTML解析器中我们谈到过,HTML状态机处于属性值状态(Attribute Value State)时,字符实体时会被解码的,此处在href属性中,所以被实体化编码的"javascript"字符串会被解码。其次,HTML解析是在URL解析之前的,所以在进行URL解析之前,Scheme部分的"javascript"字符串已被解码,而并不再是被实体编码的状态。

解析顺序:

首先,浏览器接收到一个HTML文档时,会触发HTML解析器对HTML文档进行词法解析,这一过程完成HTML解码并创建DOM树。

接下来,JavaScript解析器会介入对内联脚本进行解析,这一过程完成JS的解码工作

如果浏览器遇到需要URL的上下文环境,这时URL解析器也会介入完成URL的解码工作,URL解析器的解码顺序会根据URL所在位置不同,可能在JS解析之前,也可能在之后。

反正,HTML解析总是第一步

实例1

<a href="UserInput"></a>

该例子中,首先由HTML解析器对href的值进行字符实体解码,然后URL解析器对UserInput进行URL decode。
如果URL的Scheme部分为javascript的话,JavaScript解析器会再对UserInput进行解码。
所以解析顺序是:HTML解析->URL解析->JavaScript解析。

实例2:

<a href=# onclick="window.open('UserInput')"></a>

该例子中,首先由HTML解析器对UserInput部分进行字符实体解码;
接着由JavaScript解析器会再对onclick部分的JS进行解析并执行JS;
执行JS后window.open(‘UserInput’)函数的参数会传入URL,所以再由URL解析器对UserInput部分进行解码。
因此解析顺序为:HTML解析->JavaScript解析->URL解析。

示例3:

<a href="javascript:window.open('UserInput')">

该例子中,首先还是由HTML解析器对UserInput部分进行字符实体解码;
接着由URL解析器解析href的属性值;
然后由于Scheme为javascript,所以由JavaScript解析;
解析执行JS后window.open(‘UserInput’)函数传入URL,所以再由URL解析器解析。
所以解析顺序为:HTML解析->URL解析->JavaScript解析->URL解析。

综合实例:

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

经过三轮解析解码后得到结果:<ahref=“javascript:alert(15)”>
首先HTML解析器进行解析,解析到href属性的值时,状态机进入属性值状态(Attribute Value State),该状态会解码字符实体

接着由URL解析器进行解析并解码

再接着由于Scheme为javascript,因此由JavaScript解析器解析并解码,加上编码部分是函数名,属于标识符,因此可以正常解码解释

原文:https://mp.weixin.qq.com/s/liODgY4NjYqdWg3JgPXMdA
大家一定自己去看看原文,我觉得写的很清楚,超厉害

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值