最近在研究php代码审计,恰好又看到了宽字节注入,虽然这个在很早之前就了解过,但是由于一直都没有机会尝试过这个注入,所以就利用sqli-labs来重新来研究一下。这里就用的是第33题,GET – Bypass AddSlashes()。
因为大多数的网站对于SQL注入都做了一定的方法,例如使用一些Mysql中转义的函数addslashes,mysql_real_escape_string,mysql_escape_string等(还有一种是magic_quote_gpc,不过PHP高版本已经移除此功能)。其实这些函数就是为了过滤用户输入的一些数据,对特殊的字符加上反斜杠“\”进行转义,所以在条件符合的情况下可利用宽字节注入绕过这些函数。
这个宽字节注入又叫作
文章目录
这里先用这道题来演示注入过程,再分析原理
先用一个单引号试错,发现并没有报错,观察回显发现单引号是被转义了。因为这是一个宽字节注入,所以直接在单引号的前面加上%df,由于单引号被转义是前面加了个反斜杠\,而\的url编码是%5c,这样的话id的参数传入代码层,就会在’前加一个\,由于采用的URL编码,所以产生的效果是
%df%5c%27
关键就在这,%df会吃掉%5c,形成一个新的字节。正因为%df的关系,\的编码%5c被吃掉了,也就失去了转义的效果,直接被带入到mysql中,然后mysql在解读时无视了%df%5c形成的新字节,那么单引号便重新发挥了效果。
既然报错了,就说明我们的尝试是正确的,可以进行注入。
再用一个union联合查询注出一些信息,就不继续注入了,回到正题。
为什么加一个%df就可以了呢?因为这是mysql的一种特性,GBK是多字节编码,它认为两个字节就代表一个汉字,在%df加入的时候会和转义符\,即%5c进行结合,变成了一个“運”,而’逃逸了出来。
因此只要第一个字节和%5c结合是一个汉字,就可以成功绕过了,当第一个字节的ascii码大于128,就可以了。
直接看这道题的关键源码。
用的是check_addslashes这个函数来处理传入的参数,其本质就是addslashes的封装。看一下这个函数的作用。
简单来说就是在前面加个反斜线。
这里我理解的就像这样,简单画个帮助理解。
宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。
我们打印一下这个sql语句,
SELECT * FROM users WHERE id=’-1運’ union select 1,user(),@@version#’ LIMIT 0,1
在mysql中执行会是什么样
为什么在传入到mysql时,%df%5c%27会变成運’?
我们之前强调过了,宽字节注入的发生位置在PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。就是这一次编码,所造成的。
所以数据的变化过程就是
%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语句。
一种方法就是先调用mysql_set_charset
设置当前字符集为gbk,再调用函数mysql_real_escape_string来过滤用户输入。
还可以把character_set_client
设置为binary二进制
当我们的mysql接受到客户端的数据后,会认为他的编码是character_set_client,然后会将之将换成character_set_connection的编码,然后进入具体表和字段后,再转换成字段对应的编码。然后,当查询结果产生后,会从表和字段的编码,转换成character_set_results编码,返回给客户端。
使用函数iconv('utf-8','gbk',$_GET['id'])
,也可能导致注入产生。
虽然character_set_client
设置成了binary,但是之后参数又被变回了gbk,可以使用“ 錦’ ”来进行注入:
这是因为錦’的GBK编码为0xe55c,所以就能发现后面为%5c%27,这个时候会出现两个%5c,刚好\,反斜杠被转义,导致‘逃出造成了注入。
总结:这里引用一下别人的解决方案
在编程的过程中尽量使用UTF-8的编码,从而避免宽字节的注入。即使使用GBK编码,也一定要做好防范。
1.设置character_set_client=binary;
2.单独的使用set names gbk或者mysql_real_escape_string时也需要使用mysql_set_charset来设置一下字符集;
3.尽量不要使用iconv来转换字符编码。
对于宽字节编码,有一种最好的修补就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。上述的两个条件是“与”运算的关系,少一条都不行。
参考链接:
SQL注入:宽字节注入(GBK双字节绕过)
https://drops.blbana.cc/2016/12/05/Mysql%E5%AE%BD%E5%AD%97%E8%8A%82%E6%B3%A8%E5%85%A5/