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
2
str_replace("(","",$request_value);
str_replace(")","",$request_value);

  这个时候我们可以将()替换为&#40;&#41;,所以$request_value == "<img/src=1 onerror=alert&#40;1&#41;>",服务器一看,OK,没什么问题,于是就写入到数据库中。所以服务器给浏览器返回的同样也是<img/src=1 onerror=alert&#40;1&#41;>,但是浏览器认识&#40;&#41;呀,就会自动转换为<img/src=1 onerror=alert(1)>。所以服务器根本就不认识&#40;&#41;,也不会进行解码,解码的过程是浏览器完成的。(真实案例)

HTML自解码

  HTML有两种编码方式,进制编码和实体编码。
  
对于<img/src=1 onerror=alert(1)>可以进行下面两种编码。
  HTML 10进制
  <img/src="1" onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>
  HTML 16进制
  <img/src="1" onerror=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;>
  浏览器在解析HTML的时候将自动解码。那我们能不能将onerror=一起编码了呢?
  <img/src="1" &#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>
  答案是不行的,因为onerror=是控制字符,编码后将识别为文本字符,浏览器不会解析为DOM Tree。下面这种方式同理。
  <div>&#60;&#97;&#62;test&#60;&#47;&#97;&#62;</div>a标签被识别为文本字符,只显示,不解析
a标签被识别为文本字符,只显示,不解析
  实体编码一个意思,只不过把一些保留字符给解析为更加特殊的形式了而已。
& < > " => &lt; ......

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=&#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;>

CSS自解码

  CSS自解码比较奇葩一些。

  CSS 编码解析是用了一套不太正统的转义策略:用一个反斜杠,后边跟1~6位十六进制数字构成。,所以字母e 可以编码为 \65, \065,\000065。而因为这样,后边就不能直接紧跟数字或字母,否则会被当成转义里的内容处理,所以CSS 选择了空格作为终止标识,在解码的时候,再将空格去除。
  同时,CSS还支持直接使用反斜杠对非十六进制字符进行转义的方式,就按紧跟着反斜杠后边的字符的字面意思进行解释,这种机制可用来转义引号和反斜杠本身,不过不能转义HTML 控制的字符,比如尖括号,那是因为HTML 解析器总是先于CSS 解析器。
  由于CSS 转义规定的语焉不详,许多解析器会对本该用引号括起来的字符串进行任意的转义,特别的,在IE 浏览器里,这种转义优先级高于伪函数语法,于是下边两种情况的写法是一样的:

1
2
color:expression(alert(1))
color:expression\028 alert \028 1 \029 \029

这种情况比较少见一些。

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="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#51;&#49;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#54;&#51;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#51;&#53;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#55;&#37;&#51;&#50;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#55;&#37;&#51;&#52;&#40;&#51;&#41;">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
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script type="text/javascript">
var value = "value_in_test_function');alert(document.cookie)//";
setTimeout("test('"+value+"')",3000);
function test(value){
console.log('This function is called!\nThe value is'+value);
}
</script>
</body>
</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/