宽字节注入
宽字节案例引入
宽字节注入准确来说不是注入手法,而是另外一种比较特殊的情况。为了说明宽字节注入问题,我们以SQLi-labs 32 关为例子。 使用?id=1' 进行测试的时候,发现提交的单引号会被转义[\']。此时,转义后的单引号会被作为普通字符带入数据库查询。也就是说,我们提交的单引号不会影响到原来SQL 语句的结构
addslashes
是 PHP 中的一个内置函数,用于在字符串的某些字符前面添加反斜线 \
,以确保这些字符在 SQL 查询或其他需要转义的场景中被正确处理。这有助于防止注入攻击,尤其是在构建动态 SQL 语句时。
函数定义:
string addslashes(string $str) 参数:
$str
:需要添加反斜线的字符串。
返回值:
返回一个新的字符串,其中的反斜线字符 \
、单引号 '
、双引号 "
和 NULL 字符 \0
都被加上了反斜线。
示例用法:
$str = "It's a beautiful day."; $safeStr = addslashes($str); // $safeStr 现在等于 "It\'s a beautiful day." 在上面的例子中,单引号'被转义,这样当这个字符串被插入到 SQL 查询中时,不会导致任何问题。
注意:
addslashes
主要用于单引号'
包围的字符串。如果你的字符串使用双引号"
,应该使用addcslashes($str, "\\\\")
来转义反斜线。- 在 PHP 7.3.0 之后,推荐使用更为安全的
quotemeta
函数,因为它会自动为所有可能的正则表达式元字符添加转义,而不仅仅是几个特定的字符。
安全性:
虽然 addslashes
可以防止某些类型的注入,但最佳实践是使用参数化查询(prepared statements)来避免 SQL 注入攻击,因为它们可以自动处理特殊字符的转义,并且不依赖于手动转义。
仔细看该函数,其利用正则匹配将 [ /,'," ]这些三个符号都过滤掉了
关于preg_replace的正则用法可详看——> 命令与执行漏洞 中搜索preg_replace
而我们要绕过这个转义处理,使单引号发挥作用不再被转义,有两个思路:
让斜杠(\)失去作用
让斜杠(\)消失
第一个思路就是借鉴程序员的防范思路,对斜杠(\)转义,使其失去转义单引号的作用,成为普通的内容。第二个思路就是宽字节注入。
关于编码
在理解宽字节注入之前,我们需要先了解编码的有关知识,关于什么是编码,为什么要编码,可以详看 ——> 计算机中的编码问题
某字符的大小为一个字节时,称其字符为窄字节.
当某字符的大小为两个字节时,称其字符为宽字节.
所有英文默认占一个字节,汉字占两个字节
常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等等
宽字节注入
宽字节是指多个字节宽度的编码,GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。转义函数在对这些编码进行转义时会将转义字符 ‘\’ 转为 %5c ,于是我们在他前面输入一个单字符编码与它组成一个新的多字符编码,使得原本的转义字符没有发生作用。
由于在数据库查询前使用了GBK多字节编码,即在汉字编码范围内使用两个字节会被编码为一个汉字(前一个ascii码要大于128,才到汉字的范围)。然后mysql服务器会对查询语句进行GBK编码,即下面所说的
我们在前面加上 %df' ,转义函数会将%df’改成%df\’ , 而\ 就是%5c ,即最后变成了%df%5c',而%df%5c在GBK中这两个字节对应着一个汉字 “運” ,就是说 \ 已经失去了作用,%df ' ,被认为運' ,成功消除了转义函数的影响。
- ' %27
- \ %5c
- %df\' %df%5c' =》 運'
我们输入 ?id=1%df',按道理来说将转义符吃掉了,结果应该是 id=' 運' ' ,为什么这里转变成了中文后后面还有一个反斜杠了?那个反斜杠是哪里来的?
其实这个是浏览器显示编码的问题,我们将浏览器编码切换为GB2312即简体中文,如下就正常了。
联合注入如下
GB2312与GBK的不同
gb2312和gbk应该都是宽字节家族的一员。但我们来做个小实验。把源码中set names修改成gb2312
结果就不能注入了,我开始不信,然后再把数据库编码也改成gb2312,也是不成功的。虽然执行的语句还是显示被转换成了中文了,但就是注入不成功
为什么,这归结于gb2312编码的取值范围。它的高位范围是0xA1~0xF7,低位范围是0xA1~0xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以自然也是不会被吃掉的。
所以,把这个思路扩展到世界上所有多字节编码,我们可以这样认为:只要低位的范围中含有0x5c的编码,就可以进行宽字符注入。
宽字节注入注入方法
1. 黑盒
就是上面所述的,在注入点后面加%df,然后按照正常的注入流程开始注入即可。如果我们需要使用sqlmap进行检测注入的话也需要在注入点后面加%df然后再用sqlmap跑,否则是注入不出来的,如
sqlmap.py -u "http://localhost/sqli-labs-master/Less-32/?id=1%df%27"
2. 白盒
查看mysql是否为GBK编码,且是否使用preg_replace()把单引号转换成\'或自带函数addslashes()进行转义
如果存在上面说的,则存在宽字节注入
宽字节注入修复
1. mysql_real_escape_string
听说这个函数能抵御宽字节注入攻击。mysql_real_escape_string — 转义 SQL 语句中使用的字符串中的特殊字符,并考虑到连接的当前字符集。mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集。
于是,把addslashes替换成mysql_real_escape_string,来抵御宽字符注入。但是我们发现还是一样注入成功了
为什么,明明我用了mysql_real_escape_string,但却仍然不能抵御宽字符注入?
原因就是,你没有指定php连接mysql的字符集。我们需要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk。
mysqli_set_charset(connection,charset);
参数 | 描述 |
---|---|
connection | 必需。规定要使用的 MySQL 连接。 |
charset | 必需。规定默认字符集。 |
这样就防止了注入
即先调用mysql_set_charset函数设置连接所使用的字符集为gbk,再调用mysql_real_escape_string来过滤用户输入。
2. 设置参数,character_set_client=binary
3. 使用utf-8编码