【笔记】字符集编码及XSS
Jan 30, 2020前端编码安全字符集编码及XSS
1.字符/字节与字符集
字符与字节
肉眼看到的一个文字或符号单元就是一个字符(包括乱码),一个字符可能对应1~n个字节,1字节为8位,每一位要么为1,要么为0。
字符集
一个字符对应1~n个字节是由字符集与编码决定的,比如ASCII字符集就是一个字符对应1字节,不过1字节只用了7位,最高位用于其他目的,所以ASCII字符集共有2的7次方(128)字符,基本就是键盘上的英文字符(包括控制符)。
ASCII字符集表达不了拉丁系的字符,更表达不了东亚字符,所以各种演变出现了诸多字符集,如ISO8859系列、GB2312、GBK、GB18030、BIG5、Shift JIS等。直到Unicode字符集出现,才看到了世界和平的曙光,但是各国的这些字符集还在沿用,不可能清零从头开始,所以这个字符的世界还是很混乱。
字符集编码
字符集大都对应一种编码方式(比如GBK字符集对应了GBK编码),不过Unicode字符集的编码方式有UTF-8、UTF-16、UTF-32、UTF-7,常见的是UTF-8与UTF-7。
编码的目的是最终将这些字符正确地转换为计算机可理解的二进制,对应的解码就是将二进制最终解码将人类可读的字符。
2.宽字节编码带来的安全问题
GIB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象,比如下面这个PHP示例,在magic_quotes_gpc=On的情况下,如何触发XSS?
1 | "Content-Type: text/html;charset=GBK"); header( |
我们会想到,需要闭合双引号才行,如果只是提交如下语句:1
gb.php?x=1";alert(1)//
双引号会被转义成\”,导致闭合失败1
a="1\";alert(1)\\"
由于这个网页头部响应指明了这是GBK编码,GBK编码第一字节(高字节)的范围是0x81
~0xFE
,第二字节(低字节)的范围是0x40
~0x7E
与0x80
~0xFE
,这样的十六进制表示。而\符号的十六进制表示为0x5C
,正好在GBK的低字节中,如果之前有一个高字节,那么正好会被组成一个合法字符,于是提交如下:1
gb.php?x=1%81";alert(1)//
双引号会继续被转义成\”,最终如下:1
a="1[0x81]\";alert(1)//";
[0x81]\
组成了一个合法字符,于是之后的双引号就会产生闭合,这样我们就成功触发了XSS。
这些宽字节编码的高低位范围都不太相同,具体可以查相关维基百科。
这里有一点要注意,GB2312是被GBK兼容的,它的高位范围是0xA1
~0xF7
,低位范围是0xA1
~0xFE
(0x5C
不在该范围内),把上面的PHP代码的GBK改为GB2312,在浏览器中处理行为同GBK,也许是由于GBK兼容GB2312,浏览器都做了同样的兼容:把GB2312统一按GBK行为处理。
上面这类宽字节编码问题的影响非常普遍,不仅是XSS这么简单,从前端到后端的流程中,字符集编码处理不一致可能导致SQL注入、命令执行等一系列安全问题。
UTF-7 问题
UTF-7 是 Unicode字符集的一种编码方式,不过并非是标准推荐的。现在仅IE还支持UTF-7的解析。UTF-7的存在是有历史原因的。
自动选择UTF-7编码
在IE6/7时代,如果没声明HTTP响应头字符集编码方式或声明错误:1
2
3Content-Type: text/html charset=utf-8 // 声明字符集编码方式
Content-Type: text/html // 未声明字符集编码方式
Content-Type: text/html; charset=uf-8 // 声明错误的字符集编码方式
同时,<meta http-equiv>
未指定charset或指定错误,那么IE浏览器会判断响应内容中,是否出现UTF-7编码的字符串,如果有当前页面会自动选择UTF-7编码方式。如1
2
3<title>utf-7 xss</title>
+Adw-script+AD4-alert+AD4-alert(document.location)+ADW-/script+AD4-
<div>123</div>
通过iframe方式调用外部UTF-7编码的HTML文件
父页通过Content-Type或<meta>
标签来声明UTF-7编码,然后通过<iframe>
标签嵌入外部UTF-7编码的HTML文件。代码如下:1
2
3
4
5
6<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-7">
<body>
<iframe src="utf-7.html"/>
</body>
</html>
utf-7.html代码内容如下:1
2
3<html>
+ADW-script+AD4-alert('XSS')+ADw-/script+AD4-
</html>
不过IE现在也限制了<iframe>
只能嵌入同域内地UTF-7编码文件,虽然曾经有通过重定向跳转到外域的方式绕过这个限制。
通过link方式调用外部UTF-7编码的CSS文件
通过标签嵌入外部UTF-7编码的CSS文件,此时父页不需要声明UTF-7编码方式。代码如下:1
2
3
4<html>
<title>123</title>
<link rel="stylesheet" href="http://evil.com/utf7.css" type="text/css"/>
</html>
utf7.css:1
2@charset "utf-7";
body+AHs-x;expression(if(!window.x)+AHs-alert(1)+ADs-window.x=1+ADsAfQ-)+AHO-
通过指定的BOM文件头
这里的BOM不是brower…,指的是Byte Order Mark,即标记字节顺序码,只出现在Unicode字符集中,BOM出现在文件的最开始位置,软件通过识别文件的BOM来判断它的Unicode字符集编码方式,常见的BOM头如下
字符集编码 | BOM | |||
---|---|---|---|---|
UTF-8 | EF BB BF,可以不需要 | |||
UTF-16LE | FF FE | |||
UTF-16BE | FE FF | |||
UTF-32LE | FF FE 00 00 | |||
UTF-32BE | 00 00 FE FF | |||
UTF-7 | 2B 2F 76和1字节以下:[38 | 39 | 2B | 2F],这4字节的组合翻译为对应的字符是:+/v8,+/v9,+/v+,+/v/ |
其中,LE是Little Endian,指地位字节在前,高位字节在后;BE是Big Endian,指高位字节在前,低位字节在后。
相关解析软件如果发现BOM是+/v8,就认为目标文档时UTF-7编码,IE曾经出现的漏洞就是:以最高的优先级判断UTF-7 BOM头。这样只要能控制目标网页开头是UTF-7 BOM头,后续的内容就可以以UTF-7方式编码,从而绕过过滤器。
在实际的攻击场景中,能控制目标网页开头部分的功能如下:
= 用户自定义的CSS样式文件
- JSON CallBack类型的链接,这类出现在几乎各大Web2.0网站中。
修补这类安全问题很简单,只要在目标网页开头部分强制加一个空格即可,这样BOM头就无效了。
浏览器处理字符集编码BUG带来的安全问题
历史上所有浏览器在处理字符集编码时都出现过BUG。
绕过浏览器XSS Filter
XSS Filter主要针对反射型XSS,大体上采用的是一种启发式的检测,根据用户提交的参数判断是否是潜在的XSS特征,并重新渲染响应内容保证潜在的XSS特征不会触发。
demo1
1 | <a href="javasc
ript:alert(1)">click</a> |
解析器-词法分析器 Parser-Lexer combination
解析可以分为两个子过程——语法分析及词法分析
词法分析就是将输入分解为符号,符号是语言的词汇表——基本有效单元的集合。对于人类语言来说,它相当于我们字典中出现的所有单词。
语法分析指对语言应用语法规则。
解析器一般将工作分配给两个组件——词法分析器(有时也叫分词器)负责将输入分解为合法的符号,解析器则根据语言的语法规则分析文档结构,从而构建解析树,词法分析器知道怎么跳过空白和换行之类的无关字符。
首先html编码被还原出来 然后就成了换行 跟冒号1
2<a href="javasc
ript:alert(1)">click</a>
为什么换行后还能够执行 是因为浏览器中的解析器中词法分析器 起的作用会跳过空白跟换行之类的无效字符。
然后就构造成了一个完整的语句1
<a href="javascript:alert(1)">click</a>
不过呢现代浏览器对其做了filter处理,点击时,chrome 报错:1
Refused to run the JavaScript URL because it violates the following Content Security Policy directive: "script-src 'strict-dynamic' 'sha256-1+GSDjMMklBjZY0QiWq+tGupCvajw4Xbn46ect2mZgM=' 'sha256-2mX1M62Fd0u8q0dQY2mRsK5S1NS9jJuQAvyE8tD0dkQ=' 'sha256-6ilhNY6mjQEQ9pQ14zz/I7nMIcfHcceCwbNxtAalnbQ=' 'sha256-HqdPsO6hNmT/mfSeGdcX3eEGrZVva7AKD2Z2+1ujCZ8=' 'sha256-5ArfzK+D442gOOu18DQ8eY13vaOV24n4bfqmSi17OoI=' 'sha256-IEF9PjeyU0vsr61C8cm3JQOerOYWdBsaGddCSPp6tZs=' 'sha256-RIDhH5uF+ciLoS6AP6ZkoxuwQyczkrTetThxXwVwFJI=' 'sha256-JtSk4wWrEa1SJ1s//InAZDhd+6uc3DEhS+CxpkRiER4='". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
demo2
1 | <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">test</a> |
这样当test A链接点击时 就会以data协议 页面以html/text的方式解析 编码为base64 然后单点击a链接时 base64的编码就被还原成我们原本的
1 | <img src=x οnerrοr=alert(1)> |
同样的,现代浏览器对其做了filter处理,点击时,chrome 报错:1
Not allowed to navigate top frame to data URL: data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com