XSS基础——浏览器自解码机制
XSS基础——浏览器自解码机制
说点废话
挖洞的时候一直搞不明白编码的问题,抽了一晚上时间好好看了看,解决了不少心中的疑惑,感觉又变强了一点点。
在辅助挖掘XSS的工具中总是能够见到编码转换的功能,原来根本不知道咋用。乱用一通,有的时候也能撞上一些,但是太看人品的漏洞挖的心里很没底。
现在主要是用两个chrome插件,一个是360 keeteem的xss插件。
https://chrome.google.com/webstore/detail/xsssniper/pnhekakhikkhloodcedfcmfpjddcagpi?hl=en-US
另外一个是从wooyun drops上下载的XSS编码转换插件。
链接: https://pan.baidu.com/s/1yqYhcwUL3-GqCgeWMRm4Vg 密码: qv5e
百度云的头像被黑产给改了,所以看起来比较奇怪,目前还没找到怎么改的。
浏览器自解码机制
浏览器在解析HTML文档时无论按照什么顺序,主要有三个过程:HTML解析、CSS解析、JS解析和URL解析,每个解析器负责HTML文档中各自对应部分的解析工作。
从图中可以看出浏览器主要做了三部分的工作。
1. HTML/SVG/XHTML 解析。解析这三种文件会产生一个DOM Tree。
2. CSS 解析,解析CSS会产生CSS规则树。
3. Javascript 解析。(暂时讨论JavaScript动态操作DOM Tree)。
4. URL解析。(这一步的先后顺序不一定,看语境)
众所周知,计算机是通过二进制流的方式进行通信的。对于浏览器与服务器的通信,可以简单的理解为服务器给浏览器返回了一长串字符串。浏览器需要识别这一长串字符串中哪些是文本字符(浏览器不需要解析,只需要显示出来),哪些是控制字符(对于HTML来说就是能够被解析为DOM Tree的字符)。作为攻击者,尽可能将用户输入的值让浏览器识别为控制字符,这样就可以造成反射XSS。
举个例子吧,比如一个意见反馈处,输入一个<img/src=1 onerror=alert(1)>
而服务器过滤了()
,所以输出给浏览器的值是<img/src=1 onerror=alert1>
。
开发者心里想:“我过滤了括号,你没有办法执行函数啦~看你怎么办~”。
所以我们可以猜出来后台大概是这么写的:
1 | str_replace("(","",$request_value); |
这个时候我们可以将()
替换为()
,所以$request_value == "<img/src=1 onerror=alert(1)>"
,服务器一看,OK,没什么问题,于是就写入到数据库中。所以服务器给浏览器返回的同样也是<img/src=1 onerror=alert(1)>
,但是浏览器认识()
呀,就会自动转换为<img/src=1 onerror=alert(1)>
。所以服务器根本就不认识()
,也不会进行解码,解码的过程是浏览器完成的。(真实案例)
HTML自解码
HTML有两种编码方式,进制编码和实体编码。
对于<img/src=1 onerror=alert(1)>
可以进行下面两种编码。
HTML 10进制
<img/src="1" onerror=alert(1)>
HTML 16进制
<img/src="1" onerror=alert(1)>
浏览器在解析HTML的时候将自动解码。那我们能不能将onerror=
一起编码了呢?
<img/src="1" alert(1)>
答案是不行的,因为onerror=
是控制字符,编码后将识别为文本字符,浏览器不会解析为DOM Tree。下面这种方式同理。
<div><a>test</a></div>
a标签被识别为文本字符,只显示,不解析
实体编码一个意思,只不过把一些保留字符给解析为更加特殊的形式了而已。& < > " => < ......
JavaScript自解码
onerror=
后的字符串为js代码,所以会被js解释器自动解码,有三种编码方式:Unicode,十进制,十六进制。(下面都是用Unicode编码)
使用Unicode对js代码进行编码。
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029>
我们发现浏览器并不执行js代码,这是因为js解释器认为上面的js代码都是文本字符。所以当然不解析。在js中,单引号,双引号和圆括号等属于控制字符,编码后将无法识别。所以对于防御来说,应该编码这些控制字符。下面这种方式可以解析。
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')>
从浏览器解析过程中可以知道浏览器先进行HTML自解码,所以可以使用多种编码。
1 | <img src="1" onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')> |
CSS自解码
CSS自解码比较奇葩一些。
CSS 编码解析是用了一套不太正统的转义策略:用一个反斜杠,后边跟1~6位十六进制数字构成。,所以字母e 可以编码为 \65, \065,\000065。而因为这样,后边就不能直接紧跟数字或字母,否则会被当成转义里的内容处理,所以CSS 选择了空格作为终止标识,在解码的时候,再将空格去除。
同时,CSS还支持直接使用反斜杠对非十六进制字符进行转义的方式,就按紧跟着反斜杠后边的字符的字面意思进行解释,这种机制可用来转义引号和反斜杠本身,不过不能转义HTML 控制的字符,比如尖括号,那是因为HTML 解析器总是先于CSS 解析器。
由于CSS 转义规定的语焉不详,许多解析器会对本该用引号括起来的字符串进行任意的转义,特别的,在IE 浏览器里,这种转义优先级高于伪函数语法,于是下边两种情况的写法是一样的:
1 | color:expression(alert(1)) |
这种情况比较少见一些。
URL自解码
在这里需要一个全新的例子了。
<a href="javascript:alert(1)">test</a>
avascript:alert(1)
在这其中alert(1)是在JavaScript环境中的,所以可以被JavaScript自解码。JS编码:
1 | <a href="javascript:\u0061\u006c\u0065\u0072\u0074(3)">test3</a> |
又由于是在href中,href属性会调用url解码,所以可以进行url解码,但是协议名不可以被编码。URL编码:
1 | <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编码。
1 | <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解码,由于这里是href属性,里面带一个URL链接,所以再进行URL解码,最后进行js解码。
再分析一个解码顺序:
1 | <a onclick="window.open('value1')" href="javascript:window.open(value2)"> |
这里的value1和value2的解码顺序是什么呢~
value1:HTML解码->js解码->URL解码
value2:HTML解码->URL解码->js解码->URL解码
特殊情况
1 | <html> |
对于上面的代码,对于value
进行一次js编码能够防止代码注入吗?
答案肯定是不行的,浏览器首先进行js解码,解码后setTimeout
函数中为字符串,问题是像setTimeout()
,setInterval()
,eval()
这种函数,本来就是将字符串当js代码执行的,所以可以截断。如果想让value
的值被当做文本字符解析,在这个环境下需要进行两次js编码,也就是在setTimeout()
函数中依旧是编码后的value
,这样在setTimeout
执行时会将它当做文本字符,当然了,如果setTimeout()
还有类似的函数,那么两次编码还是挡不住的~(这里有点绕)
再说点废话
想要挖的一手好XSS,需要对浏览器还有JavaScript有比较深的理解,所以还是要啃一些比较难懂的东西的~
今天只需要比昨天变强一点点就行了。慢慢沉淀。慢慢积累。
参考资料
https://security.yirendai.com/news/share/26
http://xuelinf.github.io/2016/05/18/%E7%BC%96%E7%A0%81%E4%B8%8E%E8%A7%A3%E7%A0%81-%E6%B5%8F%E8%A7%88%E5%99%A8%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88/