转自:https://security.yirendai.com/news/share/26
开篇
谈起XSS漏洞挖掘,可能不少朋友会因为不是很了解字符编码和浏览器解析原理与漏洞擦肩而过,而并非”洞”本身不存在;谈起XSS防御,许多童鞋也是四个字脱口而出“过滤转义”,而忽略了很多细节;
本文讨论范围主要包括XSS漏洞挖掘过程中关于字符编码方面的内容,并简单理解一下浏览器的解析过程辅助深入理解XSS,如有其他意见,欢迎大家提出指正。
浏览器解析过程
浏览器在解析HTML文档时无论按照什么顺序,主要有三个过程:HTML解析、JS解析和URL解析,每个解析器负责HTML文档中各自对应部分的解析工作。下面以一篇HTML文档解析来简单的讨论下解析器如何协同工作的。
首先浏览器接收到一个HTML文档时,会触发HTML解析器对HTML文档进行词法解析,这一过程完成HTML解码并创建DOM树,接下来JavaScript解析器会介入对内联脚本进行解析,这一过程完成JS的解码工作,如果浏览器遇到需要URL的上下文环境,这时URL解析器也会介入完成URL的解码工作,URL解析器的解码顺序会根据URL所在位置不同,可能在JavaScript解析器之前或之后解析。每个解析过程中也有许多细节,下面再做具体讨论。
基本概念
HTML字符实体:
在呈现HTML页面时,针对某些特殊字符如“<”或”>”直接使用,浏览器会误以为它们标签的开始或结束,若想正确的在HTML页面呈现特殊字符就需要用到其对应的字符实体。
字符实体是一个预先定义好的转义序列,它定义了一些无法在文本内容中输入的字符或符号。字符实体以&开头+预先定义的实体名称,以分号结束,如“<”的实体名称为< 或以&开头+#符号以及字符的十进制数字,如”<”的实体编号为<,字符都是有实体编号的但有些字符没有实体名称。
JavaScript编码:最常用的如“\uXXXX”这种写法为Unicode转义序列,表示一个字符,其中xxxx表示一个16进制数字,如”<” Unicode编码为“\u003c”。
URL编码:%加字符的ASCII编码对于的2位16进制数字,如”/”对应的URL编码为%2f。
深入理解
下面我们结合具体示例来讨论下浏览器的解析原理过程和XSS复合编码的一些内容:
<a href="javascript:alert(1)">test</a>
针对上述a标签我们分析一下该环境中浏览器的解析顺序,首先HTML解析器开始工作,并对href中的字符做HTML解码,接下来URL解析器对href值进行解码,正常情况下URL值为一个正常的URL链接,如:“https://www.yirendai.com“,那么URL解析器工作完成后是不需要其他解码的,但是该环境中URL资源类型为JavaScript,因此该环境中最后一步JavaScript解析器还会进行解码操作,最后解析的脚本被执行。
整个解析顺序为3个环节:HTML解码àURL解码àJS解码
我们对href值做一些编码的转换,对照刚才分析的解析过程,思考一下脚本是否会正常执行?
Test1:URL 编码 "javascript:alert(1)”
URL编码“javascript:alert(1)”=“%6A%61%76%61%73%63%72%69%70%74:%61%6C%65%72%74%28%31%29”
编码后如下图所示:
Test2:HTML字符实体编码 "javascript" 、URL 编码 "alert(2)"
HTML编码"javascript"="javascript"
URL编码"alert(2)"=” %61%6C%65%72%74%28%32%29”
编码后如下图所示:
Test3:对<a href="javascript:alert(3)">test3</a>做JS编码àURL编码àHTML编码共3层。
JS编码:<a href="javascript:\u0061\u006c\u0065\u0072\u0074(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>
【过程分析】
许多童鞋把Test1放到HTML里发现脚本并没有正常执行,就会想按照刚才分析的,URL解码之后Javascript解析器完成解码操作,脚本应该会正常执行啊,这里就有一个URL解析过程中的一个细节了,不能对协议类型进行任何的编码操作,否则URL解析器会认为它无类型,就导致Test1中被编码的“javascript”没有解码,当然不会被URL解析器识别了。
那Test2也是对javascript编码了为什么可以执行呢?因为"javascript"是做的HTML实体编码,HTML解析器工作时,href里的HTML实体会被解码,接下来URL解析器工作解析href属性里的链接时,"javascript"协议在第一步被HTML解码了,这样URL解析器是可以识别的,然后继续解析后面的”%61%6C%65%72%74%28%32%29”,最后JavaScript解析器完成解析操作,脚本执行。
Test3实现了3层复合编码,每一层编码都能正常执行,贴出来供大家可以自行验证加深理解,并思考一下在复合编码环境中XSS防御策略不做组合编码,后果是什么呢?
Test截图验证:
只有Test2和Test3正常执行,如下图所示:
<img src=x οnclick=” {$value}”>
下面我们分析一下2层复合编码的img标签
代码如下:
假设onclick属性值为“用户可控”数据,思考一下该如何编码才能防住XSS?
首先传入的“用户可控”数据处在HTML环境中,然后再是onclick环境中,因此浏览器的解析顺序为:HTML解码àJS解码;
HTML解码: \u0061\u006c\u0065\u0072\u0074('YISRC')
JS解码:alert('YISRC')
解码完成,脚本执行,细心的童鞋可能会问了value值的编码顺序为解码的逆序,先将alert进行javascript编码为\u0061\u006c\u0065\u0072\u0074,然后再对整个value值进行HTML编码,居然还可以弹框?
我们来分析一下JavaScript解析的一个细节,Javascript解析器工作的时候将\u0061\u006c\u0065\u0072\u0074进行js解码后为“alert”,而“alert”是一个有效的标识符名称,它是能被正常解析的。像圆括号、双引号、单引号等等这些控制字符,在进行JavaScript解析的时候仅会被解码为字符串文本或者上面讲的标识符名称,例如:<script>alert('YISRC\u0027)</script>对控制字符单引号进行js编码,解析时\u0027被解码成文本单引号,无法闭合因此不能成功执行。本例中只对“alert”进行了Unicode,并没有对圆括号这类控制字符进行Unicode,是因为我想弹框啊,但在正常的XSS防御中肯定要对这些控制字符进行Unicode的。
下面通过几个简单的场景加深理解一下:
假设:alert里的值为用户输入的可控数据,服务端为了防御XSS做了HTML编码
示例一:
用户输入的【");alert("SRC】在服务端HTML实体编码之后返回到前端被浏览器解析执行
1、首先【");alert("SRC 】在HTML解码后变成了【");alert("SRC】。
2、JavaScript解析时,变成了【alert("YISRC ");alert("SRC】,当然可以正常执行了,如下图所示:
事实证明,该上下文环境中只做HTML编码是不能完全防御XSS的。
示例二:
现在调整服务端XSS防御策略为:JS编码àHTML编码
如下图所示,成功防御:
我们来分析一下:
1、首先【\u0027\u0029\u003b\u0061\u006c\u0065\u0072\u0074\u0028\u0027\u0053\u0052\u0043】经过第一步HTML解码后变为【\u0027\u0029\u003b\u0061\u006c\u0065\u0072\u0074\u0028\u0027\u0053\u0052\u0043】
2、JavaScript解析器工作,【\u0027\u0029\u003b\u0061\u006c\u0065\u0072\u0074\u0028\u0027\u0053\u0052\u0043】变为【');alert('SRC】,刚才已经讲过JavaScript解析时只有标识符名称不会被当做字符串,控制字符仅会被解析为标示符名称或者字符串,因此'\u0027'被解释成单引号文本,\u0028和\u0029被解释成为圆括号文本,不会变为控制字符被解析执行。
事实证明,合理的编码方式是防御XSS中首先要考虑到的。
最后
结合上面的内容,自己分析一下value1和value2所处的上下文环境,要防御XSS需要怎么做组合编码?
浏览器解码顺序:
Value1:HTML解码àJavaScript解码àURL解码
Value2:HTML解码àURL解码àJavaScript解码àURL解码
对于Web应用中普遍存在的XSS问题,只有深入理解了浏览器解析过程原理及解析器的协同工作,并结合上下文环境深入分析编码原理,只有这样,才能在XSS漏洞挖掘过程中对于一些编码不合理的场景进行绕过(当然了,也有很多其他绕过方法,利用编码只是其中之一),才能在XSS防御中合理编码用户输入的内容,杜绝因编码不合理造成的XSS漏洞。