浅析宽字节注入
前言
首先我们了解下宽字节注入,宽字节注入主要是源于程序员设置数据库编码与PHP
编码设置为不同的两个编码那么就有可能产生宽字节注入
例如说PHP的编码为UTF-8
而MySql
的编码设置为了
SET NAMES 'gbk'
或是SET character_set_client =gbk
,这样配置会引发编码转换从而导致的注入漏洞。
这里要说明一小点的是:
SET NAMES ‘x’语句与这三个语句等价:
mysql>SET character_set_client =x;
mysql>SET character_set_results =x;
mysql>SET character_set_connection =x;
也就是说你设置了SET NAMES 'x'
时就等于同时执行了上面的3条语句
而我认为的宽字节注入就是PHP发送请求到MySql时使用了语句
SET NAMES 'gbk'
或是SET character_set_client =gbk
进行了一次编码,但是又由于一些不经意的字符集转换导致了宽字节注入
宽字节注入原理
1:在我们正常情况下使用addslashes
函数或是开启PHPGPC
(注:在php5.4已上已给删除,并且需要说明特别说明一点,
GPC
无法过滤$_SERVER
提交的参数)时过滤GET、POST、COOKIE、REQUSET
提交的参数时,
黑客们使用的预定义字符会给转义成添加反斜杠的字符串如下面的例子
例子:
单引号(')= (\')
双引号(") = (\")
反斜杠(\) = (\\)
2:假如这个网站有宽字节注入那么我们提交:
http://127.0.0.1/unicodeSqlTest?id=%df%27
这时,假如我们现在使用的是addslashes
来过滤,那么就会发生如下的转换过程
例子:
%df%27===(addslashes)===>%df%5c%27===(数据库GBK)===>運'
这里可能有一些人没看懂,我可以粗略的解释一下。
前端输入%df%27
时首先经过上面addslashes
函数转义变成了%df%5c%27(%5c是反斜杠\)
,之后在数据库查询前因为设置了GBK编码,
即是在汉字编码范围内两个字节都会给重新编码为一个汉字。然后MySQL服务器就会对查询语句进行GBK编码即是%df%5c转换成了汉字運,
而单引号就逃逸了出来,从而造成了注入漏洞。
干这样看我们可能也没能很清楚的看懂,我们可以来几个例子:
例子1:
在PHP中使用$pdo->query('set names gbk')
;指定三个字符集(客户端、连接层、结果集)都是GBK编码。而PHP的编码等于UTF-8编码时造成的宽字节注入
例子代码:
<?php
if( !isset($_GET['id']) ){
echo "Error:ID NULL";
}
$id=addslashes($_GET['id']);//获取id并且转移与定义字符
//分别对应的是 地址,端口号,连接的数据库
$dsn = "mysql:host=127.0.0.1; port=3306; dbname=blog;";
//账号
$user = 'root';
//密码
$psw = 'root';
//连接到MySQL
$pdo = new PDO($dsn,$user,$psw);
//准备执行的sql语句
$sql = 'SELECT * FROM tp_category WHERE id ='."'{$id}'";
echo $sql;
echo '<br/>';
echo '<br/>';
//设置编码
$pdo->query('set names gbk');
$res = $pdo->query($sql) or var_dump($pdo->errorInfo());
$mon = $res->fetch(PDO::FETCH_ASSOC);
var_dump( $mon );
?>
那么如何逃过addslashes
的限制呢?addslashes
函数产生的效果就是,让’变成\’,让单双引号变得不再是’单双引号’,只是一撇而已。
一般绕过方式就是,想办法处理掉\’前面的\:
1.想办法给\ 前面再加一个\,变成\\',这样\被转义了, ' 逃出了限制
2.想办法把\弄没有。
我们这里的宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ascii码要大于128,
才到汉字的范围)。根据这个我们在地址栏输入%df%27看看会发生什么:
我们可以看到,页面已经报错了。看到报错,说明这句sql语句出错,说明我们已经绕过了addslashes
那么就可以正常的进行注入了。
我们只是在%27
前面加了一个%df
为什么就报错了?而且从上图中可以看到,报错的原因是多了一个单引号,而单引号前面的反斜杠已经不见了。
这就是mysql
的特性,因为gbk
是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字運,而’逃逸了出来,导致了注入。
例子2:
使用set names UTF-8指定了UTF-8字符集,并且也使用转义函数进行转义。有时候在程序运行的时候,为了避免乱码,
会将一些用户提交的GBK字符使用iconv函数(或mb_convert_encoding)
先转为UTF-8
,然后再拼接SQL语句带入数据库。
例子代码:
转换过程:
%df%27===(addslashes)===>%df%5c%27===(iconv)===>%e5%5c%5c%27
$id =iconv('GBK','UTF-8', $id)
;如果内容是utf8编码的,将自动转成gbk编码的. 錦的utf-8编码是0xe98ca6,它的gbk编码是0xe55c
。
有的同学可能就领悟了。\
的ascii码正是5c
。那么,当我们的錦被iconv
从utf-8
转换成gbk
后,变成了%e5%5c
,
而后面的’
被addslashes
变成了%5c%27
,这样组合起来就是%e5%5c%5c%27
,两个%5c
就是\
,正好把反斜杠转义了,导致’
逃逸出单引号,产生注入。
从上面的介绍中可以看出,宽字节注入的关键点有两个:
(1) 需要将数据库编码与PHP编码设置为不同的两个编码那么就有可能产生宽字节注入;
(2) 设置的宽字符集可能吃掉转义符号\(对应的编码为0x5c,即低位中包含正常的0x5c就行了)。