【笔记】字符集编码及 XSS

字符集编码及 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.宽字节编码带来的安全问题
GB2312、GBK、GB18030、BIG5、Shift_JIS 等都属于常说的宽字节编码,其字符可能由两个字节表示。宽字节带来的安全问题主要是“吃掉”ASCII 字符(单字节)的现象。比如下面这个 PHP 示例,在 magic_quotes_gpc=On 的情况下,如何触发 XSS?
1 | header("Content-Type: text/html;charset=GBK"); |
我们会想到,需要闭合双引号才行,如果只是提交如下语句:
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 字符集的一种编码方式,但并非标准推荐的 Web 编码。本节讨论的是旧版 IE 支持 UTF-7 解析时产生的历史安全问题。
自动选择UTF-7编码
在IE6/7时代,如果没声明HTTP响应头字符集编码方式或声明错误:
1 | Content-Type: text/html; charset=utf-8 // 声明字符集编码方式 |
同时,<meta http-equiv>未指定charset或指定错误,那么IE浏览器会判断响应内容中,是否出现UTF-7编码的字符串,如果有当前页面会自动选择UTF-7编码方式。如
1 | <title>utf-7 xss</title> |
通过iframe方式调用外部UTF-7编码的HTML文件
父页通过Content-Type或<meta>标签来声明UTF-7编码,然后通过<iframe>标签嵌入外部UTF-7编码的HTML文件。代码如下:
1 | <html> |
utf-7.html代码内容如下:
1 | <html> |
不过旧版 IE 也限制了 <iframe> 只能嵌入同域内的 UTF-7 编码文件,虽然曾经有通过重定向跳转到外域的方式绕过这个限制。
通过link方式调用外部UTF-7编码的CSS文件
通过标签嵌入外部UTF-7编码的CSS文件,此时父页不需要声明UTF-7编码方式。代码如下:
1 | <html> |
utf7.css:
1 | @charset "utf-7"; |
通过指定的BOM文件头
这里的 BOM 指 Byte Order Mark,即字节顺序标记。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 |
其中,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 | <a href="javasc |
为什么换行后还能够执行 是因为浏览器中的解析器中词法分析器 起的作用会跳过空白跟换行之类的无效字符。
然后就构造成了一个完整的语句
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 链接时,页面会处理 data URL,并对其中的 Base64 数据进行解码,还原出原本的 HTML:
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