宽字节注入原理
0x00 前言
在mysql中,用于转义的函数有addslashes,mysql_real_escape_string,mysql_escape_string等,还有一种情况是magic_quote_gpc,不过高版本的PHP将去除这个特性。
首先,宽字节注入与HTML页面编码是无关的,笔者曾经看到<metacharset=utf8>
就放弃了尝试,这是一个误区,SQL注入不是XSS。虽然他们中编码的成因相似,不过发生的地点不同。
很多网上的材料都说程序使用了宽字节来处理程序,却又不指出具体是指什么程序。本文就介绍一下具体漏洞发生的原理与简单的利用。
0x01 涉及的一些概念
1. 字符、字符集与字符序
字符(character)是组成字符集(character set)的基本单位。对字符赋予一个数值(encoding)来确定这个字符在该字符集中的位置。
字符序(collation)指同一字符集内字符间的比较规则。
2. UTF-8
由于ASCII表示的字符只有128个,因此网络世界的规范是使用UNICODE编码,但是用ASCII表示的字符使用UNICODE并不高效。因此出现了中间格式字符集,被称为通用转换格式,及UTF(UniversalTransformation Format)。
3. 宽字节
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象。
4.客户端与服务器交互数据传输的字符集
存储时的字符集已经确定了,不会影响交互阶段的字符集。
在MYSQL中,还有一个中间层的结构,负责客户端和服务器之间的连接,所以称为连接层。
交互的过程如下:
(1)客户端以某种字符集生成的SQL语句发送至服务器端,这个“某种字符集”其实是任意规定的,PHP作为客户端连接MYSQL时,这个字符集就是PHP文件默认的编码。
(2)服务器会将这个SQL语句转为连接层的字符集。问题在于MYSQL是怎么知道我们传过来的这个SQL语句是什么编码呢?这时主要依靠两个MYSQL的内部变量来表示,一个是character_set_client(客户端的字符集)和character_set_connection(连接层的字符集)。可以使用show variables like ‘character_set_%’ ;进行查看。
可以看到,这里的客户端字符集为GBK,连接层字符集也是为GBK。
两者相同,就不会有问题,如果不一致,就会出现乱码问题了。
使用MYSQL中的set命令可以对这些内部变量做设置,如修改客户端编码为UTF-8;
set character_set_client = UTF-8
(1)服务器将转换好的SQL语句,转为服务器内部编码与存储在服务器上的数据进行交互
(2)服务器处理完之后,将结果返回给客户端,还是转为服务器认为客户端可以认识的编码,如上图的GBK,使用character_set_results来确定返回客户端的编码。
平时在PHP中写的set names UTF-8相当于下面三条同时执行:
(1)set character_set_client = UTF-8
(2)set character_set_connection = UTF-8
(3)set character_set_results = UTF-8
5、乱码问题原理
设置三个字符集相同,这也就不会出现乱码的真正原理。网页上有时会出现乱码是因为PHP动态文件将数据打印到浏览器的时候,浏览器也会按照一定的字符集进行判断,如果PHP的响应数据编码和浏览器编码一致,就不会出现乱码,否则就出现乱码。可以通过在PHP中使用header()来指定这个响应数据的编码。
0x02 mysql字符集转换过程
1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
• 使用每个数据字段的CHARACTER SET设定值;
• 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
• 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
• 若上述值不存在,则使用character_set_server设定值。
将操作结果从内部操作字符集转换为character_set_results。
重点:宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。
0x03 案例演示
有三种形式
情景一:
在PHP中使用mysql_query(“set names GBK”);指定三个字符集(客户端、连接层、结果集)都是GBK编码。
情景代码:
1 ..... 2 mysql_query(“set names GBK”); 3 $bar = addslashes($_GET[‘bar’]) ; 4 $sql = “select password from user where bar=’{$bar}’”; 5 $res = mysql_query($sql) ; 6 ......
提交:http://127.0.0.1/foo.php?bar=admin%df%27
这时,发生如下转换:
%df%27=====(addslashes)======>%df%5c%27======(GBK)======>運’
带入sql为:
Select password from user where bar=‘運’
成功将单引号闭合。为了避免漏洞,网站一般会设置UTF-8编码,然后进行转义过滤。但是由于一些不经意的字符集转换,又会导致漏洞。
情景二:
使用set names UTF-8指定了UTF-8字符集,并且也使用转义函数进行转义。有时候,为了避免乱码,会将一些用户提交的GBK字符使用iconv函数(或者mb_convert_encoding)先转为UTF-8,然后再拼接入SQL语句。
情景代码:
1 .... 2 mysql_query(“set names UTF-8”) ; 3 $bar =iconv(“GBK”,”UTF-8”, addslashes($_GET[‘’bar])) ; 4 $sql = “select password from user where bar=’{$bar}’” ; 5 $res = mysql_query($sql) ; 6 ......
我们可以看到,为了使得SQL语句中的字符集保持一致,一般都会使用iconv等字符集转换函数进行字符集转换,问题就是出在了GBK向UTF-8转换的过程中。
提交:http://127.0.0.1/foo.php?bar=%e5%5c%27
变换过程:(e55c转为UTF-8为e98ca6)
e55c27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca65c5c27
可以看到,多出了一个5c,将转义符(反斜杠)本身转义,使得后面的%27发挥了作用。
测试如下:
情景三:
使用iconv进行字符集转换,将UTF-8转为GBK,同时,set names字符集为GBK。提交%e9%8c%a6即可。
这个情景的大前提是先编码后转义:
e98ca6====(iconv)=====>e55c=====(addslashes)====>e55c5c
同样可以多出一个反斜杠进行利用,在此不再详述,因为漏洞条件比较苛刻。
0x04 安全方案
对于宽字节编码,有一种最好的修补就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。
上述的两个条件是“与”运算的关系,少一条都不行。
测试:
输出:
效果很明显
参考:https://blog.csdn.net/qq_29419013/article/details/81205291