浏览器在解析HTML文档时无论按照什么顺序,主要有三个过程:HTML解析、js解析和url解析,每个解析器负责HTML文档中各自对应部分的解析工作。
首先浏览器接收到一个HTML文档时,会触发HTML解析器对HTML文档进行词法解析,这一过程完成HTML解码并创建DOM树,接下来JavaScript解析器会介入对内联脚本进行解析,这一过程完成JS的解码工作,如果浏览器遇到需要URL的上下文环境,这时URL解析器也会介入完成URL的解码工作,URL解析器的解码顺序会根据URL所在位置不同,可能在JavaScript解析器之前或之后解析。
基本概念:
HTML字符实体
在呈现HTML页面时,针对某些特殊字符如<、>直接使用,浏览器会误以为它们标签的开始或结束,若想正确的在HTML页面呈现特殊字符就需要用到其对应的字符实体。
字符实体是一个预先定义好的转义序列,它定义了一些无法在文本内容中输入的字符或符号。字符实体以&开头+预先定义的实体名称,以分号结束,如"<"的实体名称为<或以&开头+#符号以及字符的十进制数字,如:"<"的实体编号为<字符都是有实体编号的但有些字符没有实体名称。
JavaScript编码
最常见的如"uxxxx"这种写法为Unicode转义序列,表示一个字符,其中xxxx表示一个16进制数字,如"<"Unicode编码为"u003c"。
会触发JavaScript解析器的地方:
-
直接嵌入<script>代码块
-
通过<script sr=…>加载代码
-
各种HTML CSS参数支持JavaScript:URL触发调用
-
CSS expression(…)语法和某些浏览器的XBL绑定
-
事件处理器(Event handlers),比如onload、onerror、onclick等
-
定时器,Timer(setTimeout, setInterval)
-
eval(…)调用
比如定时器:
<script>
var value = "user_string";
...
setTimeout("do_stuff('"+value+"')", 1000);
</script>
检查setTimeout语法,等到1秒之后,才会解析do_stuff,如果不多做一个转义,就有可能构造成一次注入,比如user_string中插入一个JavaScript编码的构造,截断前边函数,然后构造自己的攻击部分。
实际上,dom操作是js强势介入HTML和CSS的结果,使用dom操作,对dom tree造成了改变,会调用到HTML解析器重新对其解析。
URL解码
%加字符的ASCII编码对应的2位16进制数字,如"/"对应的URL编码为%2f。
深入理解
下面我们结合具体事例来讨论下浏览器的解析原理过程和xss复合编码的一些内容:
针对上述a标签我们分析一下该环境中浏览器的解析顺序,首先HTML解析器开始工作,并对href中的字符做HTML解码,接下来URL解析器对href值进行解码,正常情况下url值为一个正常的url链接,如“https://www.test.com”,那么url解析器工作完成后是不需要其他解码的,但是该环境中url资源类型为JavaScript,因此该环境中最后一步JavaScript解析器还会进行解码操作,最后解析的脚本被执行。
整个解析顺序为3个环节:HTML解码-->URL解码-->js解码
我们对href值做一些编码转换,思考会不会执行xss:
-
url编码"javascript:alert(1)"
%6A%61%76%61%73%63%72%69%70%74:%61%6C%65%72%74%28%31%29
不能执行,url解析过程中有一个细节,不能对协议类型进行任何编码操作,否则url解析器会认为它无类型,冒号也是协议的一部分。这就会导致被编码的"javascript"没有解码,当然不会被url解析器识别了。
-
HTML字符实体编码"javascript"、url编码"alert(2)"
HTML编码:
"javascript"="javascript"
URL编码:
"alert(2)"=” %61%6C%65%72%74%28%32%29”
可以执行。因为"javascript"是做的HTML实体编码,HTML解析器工作时,href中的HTML实体就会被解码,接下来url解析器工作解析href属性里的链接时,"javascript"协议在第一步被HTML解码了,这样url解析器是可以识别的,继续解析后面的" %61%6C%65%72%74%28%32%29",最后JavaScript解析器完成解析操作,脚本执行。
-
对<a href="javascript:alert(3)">test3</a>做js编码-->url编码-->HTML编码共3层。
JS编码:
<a href="javascript:u0061u006cu0065u0072u0074(3)">test3</a>
URL编码:
<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(3)">test3</a>
HTML编码:
<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(3)">test3</a>
可以执行。
<img src=x onclick="{$value}">
代码如下:
<img src=x onclick="\u0061\u006c\u0065\u0072\u0074('1')">
假设onclick属性为用户可控数据:
首先传入的“用户可控”数据处在HTML环境中,然后是onclick环境中,因此浏览器的解析顺序为:HTML解码-->js解码
HTML解码:
u0061u006cu0065u0072u0074('1')
js解码:alert('1')
解析完成,脚本执行。注意像圆括号、双引号、单引号等这些控制字符,在进行JavaScript解析的时候仅会被解码为字符串文本。正常的xss防御应该要对这些控制字符进行Unicode编码。
测试一下DOM的环境,测试代码如下:
<div id='s'>test</div>
<script>
var s = "<img/src=x onerror=alert(1)>";
document.getElementById('s').innerHTML = s;
</script>
<img/src=x onerror=alert(1)>的以下编码都可以弹窗:
Unicode编码:
u003Cu0069u006Du0067u002Fu0073u0072u0063u003Du0078u0020u006Fu006Eu0065u0072u0072u006Fu0072u003Du0061u006Cu0065u0072u0074u0028u0031u0029u003E
八进制编码:
评论