SQL注入:宽字节注入(GBK双字节绕过)
宽字节注入的作用是非常大的,实际上在代码层的防御一般不外乎两种,一是反斜杠转义,而就是replace替换成空字节,之前的替换空字节也就是各种bypass,想办法绕过代码层定制的黑名单,那么,转义的话,就不像替换那么好绕了
要么不用被转义的字符,要们就只能想办法构造了,这时候,便有了一个很牛逼的构造方法,GBK双字节绕过,实际也算是宽字节注入,这个可以看看一本书
- 《双字节编码 php的隐形杀手》
宽字节注入的方法也很简单,就是编码,我们一点点分析
假设一个URL存在注入但是有addslashes,mysql_real_escape_string,mysql_escape_string等等函数实现转义就比如如下代码
function check_addslashes($string) { $string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash $string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash $string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash return $string; }
定义了个一个过滤函数,然后使用它
if(isset($_GET['id'])) { $id=check_addslashes($_GET['id']); mysql_query("SET NAMES gbk"); $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; $result=mysql_query($sql); $row = mysql_fetch_array($result);
这儿形成的URL应该是
http://www.xxx.com/index.php?id=1
我们敲单引号会被过滤,但又必须要敲,那么怎么办呢?这时候就利用GBK双字节注入
我们在后边这么构造URL
http://www.xxx.com/index.php?id=1%df'and 1=2 union select 1,2,3%23
这样的话id的参数传入代码层,就会在’前加一个\,由于采用的URL编码,所以产生的效果是
%df%5c%27
关键就在这,%df会吃掉%5c,形成一个新的字节,举个例子就是%d5遇到%5c会把%5c吃掉,形成%d5%5c,这个编码经过代码解码后会形成一个汉字“誠”
说的再形象点,我来画个画解决
基本解释:
因为%df的关系,\的编码%5c被吃掉了,也就失去了转义的效果,直接被带入到mysql中,然后mysql在解读时无视了%a0%5c形成的新字节,那么单引号便重新发挥了效果
那么真正的原因是什么呢?
GBK双字节注入到底是怎么来的呢?
宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。
http://www.xxx.com/index.php?id=1%df'and 1=2 union select 1,user(),3%23
按照这个参数,我们在页面输出$sql,看看最终传入到mysql中的语句构造
SELECT * FROM users WHERE id='1運' and 1=2 union select 1,user(),3#' LIMIT 0,1
我们可以看到,单引号前并没有\,而是多了一个汉字
運
那么这句传入到mysql中运行的结果是什么呢?
输出了user()
这是为什么呢?为什么在传入到mysql时,%df%5c%27会变成運’?
我们之前强调过了,宽字节注入的发生位置在PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。
就是这一次编码,发生了这一切
当一个Mysql连接请求从客户端传来的时候,服务器认为它的编码是character_set_client,
然后会根据character_set_connection把请求进行转码,从character_set_client转成character_set_connection,
然后更新到数据库的时候,再转化成字段所对应的编码
如果使用了set names指令,那么可以修改character_set_connection的值,
也同时会修改character_set_client和character_set_results的值
当从数据库查询数据返回结果的时候,将字段从默认的编码转成character_set_results
这儿会产生什么呢?
我们追踪下数据的变化过程
%df%27===>(addslashes)====>%df%5c%27====>(GBK)====>運’
用户输入==>过滤函数==>代码层的$sql==>mysql处理请求==>mysql中的sql
mysql_query("SET NAMES gbk");
当这行代码在代码层被写入时,三个字符集(客户端、连接层、结果集)都是GBK编码。
那么便会发生如上的情况
有人会说,那直接试用UTF-8编码呢,很多网站就是这么做的,但是为了避免用户输入的GBK字符形成乱码,网站真正的做法是会将一些用户提交的GBK字符使用iconv函数(或者mb_convert_encoding)先转为UTF-8,然后再拼接入SQL语句。
%e5%5c%27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca6\\’
上面的UTF-8的SQL代码,但是如果转成GBK时,e98ca6\\’实际是錦’
那么,mysql中又是如何处理sql语句中的编码的呢?
我们传统意义上说的编码其实是指字符集,它包括两个方面,一个是存储的字符,另外一个是映射关系,也就是真正的编码。各种字符集的存储的字符都是差不多的,就那么几个字符,而编码却是各不相同,是真正发挥威力的地方。
原来的系统数据存储采用gbk字符集,因为版本原因,升级后系统必须采用latin1字符集来存储,所以新的数据库中存储的是gbk的字符,而使用的是latin1的编码。所以这种数据只能在需要显示gbk的页面上正确显示,在显示其他字符集的地方就会是乱码。
这儿mysql对于那个新形成的字符的处理,问了下phithon大牛,很快便给了答案,在他写的一篇文章中有类似的解释—-《遇到一个有趣的逻辑漏洞》
到这儿,基本就说的差不多了,还剩最后的一部分了,哪些能形成宽字节呢?
GBK双字节编码:一个汉字用两个字节表示,首字节对应0x81-0xFE,尾字节对应0x40-0xFE(除0x7F),刚好涵盖了对应的编码0x5C。
这儿我给个GBK表,便于大家查询
GBK编码表
6、安全方案
对于宽字节编码,有一种最好的修补就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。
上述的两个条件是“与”运算的关系,少一条都不行。