通过浏览器编码深入探究XSS绕过
0x00背景
本篇文章得自于kaiputenku大佬在内部分享中用生命表演的结晶,三个小时,滴水未进,晚饭都不吃了,也要给内部团队的我们这些菜鸟们把浏览器编码解码规则讲清楚,然后还要苦口婆心的教我们怎么应用到实际的XSS Bypass中,话不多说,刚给大佬tian完,下面把大佬的精华也分享给大家,希望有所收获!
编辑完发现内容实在太多,满满都是干货,其中通过多个实例对浏览器编码解码过程进行讲解,需要各位看官花费充分时间消化吸收,建议收藏留存,有空了再拿出来瞅两眼。
0x01基本概念
为什么要进行编码?
主要是因为某些数据不适合传输。原因多种多样,如Size过大,包含隐私数据,另外重要的一点就是有些字符会引起歧意。
对于URL:
&用于分割多个参数,倘若有某个参数键值为name=v&lue,就会因为name参数的值v&lue中携带了&而造成歧义。因此需要对&进行URL编码。
对于HTML:
当浏览器遇到<会识别为元素定义的开始,>会识别为元素的结束。倘若有<divid="1>" ></div>,由于标签的属性值携带了>,同样会造成歧义。因此需要属性值的>需要进行HTML编码,即使用字符实体。
Ø HTML编码(字符实体)
字符实体是一个预先定义好的转义序列。
字符实体两种表示方法:
1、字符实体以&开头+预先定义的实体名称+;分号结束,如“<”的实体名称为<
2、字符实体还可以以&开头+#符号+字符在ASCII对应的十进制数字+;分号结束,如<的实体编号为<
字符都是有实体编号的,但有些字符是没有实体名称
Ø JavaScript编码
最常用的,如uXXXX这种写法的Unicode转义序列,表示一个字符,其中XXXX表示一个16进制数字,如<的Unicode编码为u003c。
Ø URL编码
RFC3986文档规定,URL中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。
RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]
编码方式
%加字符在ASCII码表中的十六进制值。
例如,/在ASCII码表中十六进制为0x2f,那么它对应的URL编码为%2f。
参考链接:https://www.cnblogs.com/jerrysion/p/5522673.html
JavaScript中提供了3个函数用来对URL编码以得到合法的URL:
1、escape()
2、encodeURI()
3、encodeURIComponent()
0x02浏览器解码规则
浏览器无论什么情况都会遵守一个这样的解码规则:
1、HTML解析器对HTML文档进行解析,完成HTML解码并且创建DOM树
2、JavaScript 或者 CSS解析器对内联脚本进行解析,完成JS、CSS解码
3、URL解码会根据URL所在的顺序不同而在JS解码前或者解码后
0x03HTML解析器
HTML中有五类元素:
1、空元素(Void elements),有area、base、br、col、command、embed、hr、img、input、keygen、link、meta、param、source、track、wbr
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、基本元素,可以容纳文本、字符引用、其他元素和注释
HTML解析器以状态机的方式运行,它从文档输入流中消耗字符并根据其转换规则转换到不同的状态。
示例:
<html>
<body>
Hello world
</body>
</html>
1、初始状态为"Data State",当遇到"<"字符,状态变为"Tag open state",读取一个a-z的字符将产生一个开始标签符号,状态相应变为"Tag name state",一直保持这个状态直到读取到">",每个字符都附加到这个符号名上,例子中创建的是一个html符号。
2、当读取到">",当前的符号就完成了,此时,状态回到"Datastate","<body>"重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到"Data state",读取"Helloworld"中的字符"H"将创建并识别出一个字符符号,这里会为"Hello world"中的每个字符生成一个字符符号。
3、这样直到遇到"</body>"中的"<"。现在,又回到了"Tag open state",读取下一个字符"/"将创建一个闭合标签符号,并且状态转移到"Tag name state",还是保持这一状态,直到遇到">"。然后,产生一个新的标签符号并回到"Data state"。后面的"</html>"将和"</body>"一样处理。
HTML解析器处于数据状态(Data State)、RCDATA 状态(RCDATA State)、属性值状态(Attribute ValueState)时,字符实体会被解码为对应的字符。
示例:
<div><img src=x onerror=alert(4)></div>
<和>被编码为字符实体<和>。
当HTML解析器解析完<div>时,会进入数据状态(Data State)并发布标签令牌。
接着解析到实体<时因为处在数据状态(Data State)就会对实体进行解码为<,
后面的>同样道理被解码为>。
这里会有个问题,被解码后,img是否会被解析为HTML标签而导致JS执行呢?
答案是否定的。因为解析器在使用字符引用后不会转换到标签打开状态(Tag Open State),不进入标签打开状态就不会被发布为HTML标签。因此,不会创建新HTML标签,只会将其作为数据来处理。
这也是为什么我们可以使用字符实体来避免用户不安全输入导致XSS的原因。
原始文本元素(Raw text elements)
在HTML中,属于Raw text elements的标签有两个:script、style。
在Raw text elements类型标签下的所有内容块都属于该标签。
存在一条特性:
Raw textelements类型标签下的所有字符实体编码都不会被HTML解码。HTML解析器解析到script、style标签的内容块(数据)部分时,状态会进入Script Data State,该状态并不在我们前面说的会解码字符实体的三条状态之中。
因此,<script>alert(9);</script>这样字符实体并不会被解码,也就不会执行JS。
RCDATA
在HTML中,属于RCDATAElements的标签有两个:textarea、title。
RCDATA Elements类型的标签可以包含文本内容和字符实体。
解析器解析到textarea、title标签的数据部分时,状态会进入RCDATA State。
前面我们提到,处于RCDATA State状态时,字符实体是会被解析器解码的。
示例:
<textarea><script>alert(5)</script></textarea>
<和>被编码为实体<和>。
解析器解析到它们时会进行解码,最终得到<textarea><script>alert(5)</script></textarea>。
但是里面的JS同样还是不会被执行,原因还是因为解码字符实体状态机不会进入标签打开状态(Tag Open State),
因此里面的<script>并不会被解析为HTML标签
外部元素(Foreignelements)
Foreign elemnts来源于MathML和SVG命名空间
<svg>遵循XML和SVG的定义
示例
<script>alert(1)</script>
不能弹窗,Raw text elements类型标签下的所有字符实体编码都不会被HTML解码
<svg><script>alert(1)</script>
能弹窗,在XML中,(会被解析成(,在XML中实体会自动转义,除了<![CDATA[和]]>
包含的实体
0x04JavaScript解析器
形如 uXXXX这样的Unicode字符转义序列或Hex编码是否能被解码需要看情况。
首先,JavaScript中有三个地方可以出现Unicode字符转义序列:
1、字符串中(in String)
Unicode转义序列出现在字符串中时,它只会被解释为普通字符,而不会破坏字符串的上下文。
例如,<script>alert("u0031u0030");</script>
被编码转义的部分为10,是字符串,会被正常解码,JS代码也会被执行。
2、标识符中(in identifier names)
若Unicode转义序列存在于标识符中,即变量名(如函数名等…),它会被进行解码。
例如,<script>u0061u006cu0065u0072u0074(10);</script>
被编码转义的部分为alert字符,是函数名,属于在标识符中的情况,因此会被正常解码,JS代码也会被执行。
3、控制字符中(in control characters)
若Unicode转义序列存在于控制字符中,那么它会被解码但不会被解释为控制字符,而会被解释为标识符或字符串字符的一部分。
控制字符即'、"、()等。
例如,<script>alertu0028"xss"); </script>,(进行了Unicode编码,那么解码后它不再是作为控制字符,而是作为标识符的一部分alert(。
因此函数的括号之类的控制字符进行Unicode转义后是不能被正常解释的。
总结,Unicode序列不能出现在控制字符中,否则不能被解释。
示例1:
<script>u0061u006cu0065u0072u0074u0028u0031u0031u0029</script>
被编码部分为alert(11)。
该例子中的JS不会被执行,因为控制字符被编码了。
示例2:
<script>u0061u006cu0065u0072u0074(u0031u0032)</script>
被编码部分为alert及括号内为12。
该例子中JS不会被执行,原因在于括号内被编码的部分不能被正常解释,要么使用ASCII数字,要么加""或''使其变为字符串,作为字符串也只能作为普通字符。
示例3:
<script>alert('13u0027)</script>
被编码处为'。
该例的JS不会执行,因为控制字符被编码了,解码后的'将变为字符串的一部分,而不再解释为控制字符。
因此该例中字符串是不完整的,因为没有'来结束字符串。
示例4:
<script>alert('14u000a')</script>
该例的JS会被执行,因为被编码的部分处于字符串内,只会被解释为普通字符,
不会突破字符串上下文。
示例5:
<img src="1" onerror=u0061u006cu0065u0072u0074u0028u0031u0029>
无法执行
我们以浏览器的视角来看:首先读到<开始读取标签,然后读到onerror调用JS解析器。在JS中,单引号,双引号和圆括号等属于控制字符,
编码后将无法识别。所以对于防御来说,应该编码这些控制字符。
下面这种方式可以解析:
<img src="1" onerror=u0061u006cu0065u0072u0074('u0031')>
可以结合上面的HTML编码
按照解析顺序反过去,先JS编码然后HTML解码
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')>
浏览器读到了<标签开始构造语法树,然后HTML解码,解码之后发现onerror于是进行一个JS解码,成功弹窗
延伸:
开发人员单纯的设置HTML实体编码为防御xss的手段,但是用户输入点在alert中
<img src = "https://text.com" onclick = 'alert("输入点")'>
如果用户正常输入的话凡是存在< ," 等都能被转码
攻击者可以通过语句 ");alert("test,在服务端被转码:
<img src = "https://gss1.bdstatic.com" onclick = 'alert("FIRST XSS");alert("test")'>
弹窗两次,是因为浏览器进行HTML解码发现存在两个alert()
所以对于这种情况,正确防御XSS的方法:
应该是先JavaScript编码然后再进行HTML编码
用户输入 ");alert("test 后在服务端先JavaScript编码然后再进行HTML编码
在
在浏览器端:
首先经过第一步HTML解码后变为u0022u0029u003Bu0061u006Cu0065u0072u0074u0028u0022u0074u0065u0073u0074
JavaScript解析器工作,变为 ");alert("test ,刚才已经讲过JavaScript解析时只有标识符名称不会被当做字符串,
控制字符仅会被解析为标示符名称或者字符串,因此u0022被解释成双引号文本,u0028和u0029被解释成为圆括号文本,不会变为控制字符被解析执行。
在这里采用的先JS编码后HTML编码中只弹窗了一次。
0x05URL解析器
通用URI的格式如下:
[协议名]://[用户名]:[密码]@[主机名]:[端口]/[路径]?[查询参数]#[片段ID]
URL解析器也被建模为状态机,文档输入流中的字符可以将其导向不同的状态。
首先,要注意的是URL的Scheme部分(协议部分)必须为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)。
JS不会被执行,因为作为Scheme部分的"javascript"这个字符串被编码,导致URL解析器状态机进入No Scheme状态。
URL中的:也不能被以任何方式编码,否则URL解析器的状态机也将进入No Scheme状态。
<a href="javascript%3aalert(3)"></a>
由于:被URL编码为%3a,导致URL状态机进入No Scheme状态,JS代码不能执行。
示例:
<a href="javascript:%61%6c%65%72%74%28%32%29">
"javascript"这个字符串被实体化编码,:没有被编码,alert(2)被URL编码。
成功执行
首先,在HTML解析器中我们谈到过,HTML状态机处于属性值状态(Attribute Value State)时,字符实体时会被解码的,此处在href属性中,所以被实体化编码的"javascript"字符串会被解码。其次,HTML解析是在URL解析之前的,所以在进行URL解析之前,Scheme部分的"javascript"字符串已被解码,而并不再是被实体编码的状态。
0x06解析顺序
首先浏览器接收到一个HTML文档时,会触发HTML解析器对HTML文档进行词法解析,这一过程完成HTML解码并创建DOM树。
接下来JavaScript解析器会介入对内联脚本进行解析,这一过程完成JS的解码工作。
如果浏览器遇到需要URL的上下文环境,这时URL解析器也会介入完成URL的解码工作,URL解析器的解码顺序会根据URL所在位置不同,可能在JavaScript解析器之前或之后解析。HTML解析总是第一步。
URL解析和JavaScript解析,它们的解析顺序要根据情况而定。
示例1:
<a href="UserInput"></a>
该例子中,首先由HTML解析器对UserInput部分进行字符实体解码;
接着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="javascrip
t:%5c%75%3
0%30%36%31
%5c%75%30%
30%36%63%5
c%75%30%30
%36%35%5c%
75%30%30%3
7%32%5c%75
%30%30%37%
34(15)"></a>
首先HTML解析器进行解析,解析到href属性的值时,状态机进入属性值状态(Attribute Value State),该状态会解码字符实体
接着由URL解析器进行解析并解码
再接着由于Scheme为javascript,因此由JavaScript解析器解析并解码,加上编码部分是函数名,属于标识符,因此可以正常解码解释
经过三轮解析解码后得到结果:<ahref="javascript:alert(15)"></a>
到这里,你们以为就结束了吗?
+++哼,太天真了.gif+++
Look!!!
Look!!!
Look!!!
剩下的是往期精彩^_^
往期经典推荐:
----------------------------------------------------------------------
渗透实战(一)|BSides Vancouver 2018 (Workshop)
移动安全(一)|Android设备root及神器Xposed框架安装
本篇相关参考资料:
https://sakuxa.com/2019/03/07/0x00-XSS%E5%AD%A6%E4%B9%A0%E7%B3%BB%E5%88%97%E4%B9%8B%E8%A7%A3%E6%9E%90HTML%E6%96%87%E6%A1%A3/
https://xz.aliyun.com/t/5863
https://www.w3.org/html/ig/zh/wiki/HTML5/syntax
https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element
>>关于我们:
WhITECat安全小组隶属于起源实验室分支安全小组,主要致力于分享小组成员技术研究成果、最新的漏洞新闻、安全招聘以及其他安全相关内容。团队成员暂时由起源实验室核心成员、一线安全厂商、某研究院、漏洞盒TOP10白帽子等人员组成。
欢迎各位大佬关注^_^
本文始发于微信公众号(WhITECat安全团队):深入探究浏览器编码及XSS Bypass
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论