0x00 前言
SQL注入细分下来姿势有很多。如果想要精通SQL注入漏洞,需要进行系统地学习与实践。本文仅对SQL注入中的宽字节注入进行分析。对于SQL注入其他姿势的学习,笔者在这里提供几个文章供大家参考:
DVWA SQL注入总结、SQL注入环境搭建和代码编写、SQL注入漏洞详解
0x01 宽字节注入
宽字节注入是由于不同编码中中英文所占字符的不同所导致的。通常来说,在GBK编码当中,一个汉字占用2个字节。而在UTF-8编码中,一个汉字占用3个字节。在php中,我们可以通过输入 echo strlen(“中”) 来测试。当为GBK编码时,输出2。当为UTF-8编码时,输出3。除了GBK编码以外,所有的ANSI编码都是中文占用两个字节。
在说之前,我们先说一下php中对于sql注入的过滤,这里就不得不提到几个函数了。
addslashes()函数
这个函数在预定义字符前添加反斜杠\
。预定义字符:单引号'
、双引号"
、反斜杠\
、NULL
。但是这个函数有一个特点就是虽然会添加\
进行转义,但是\
并不会插入到数据库中。这个函数的功能和魔术引号完全相同,所以当打开了魔术引号时,不应使用这个函数,因为这样会导致双层转义。默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行addslashes()
。遇到这种情况时可以使用函数get_magic_quotes_gpc()
进行检测。
mysql_real_escape_string()函数
这个函数用来转义SQL语句中使用的字符串中的特殊字符。下列字符受其影响:\x00
、\n
、\r
、\
、'
、"
、\x1a
。如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
PHP mysql_real_escape_string() 函数
魔术引号
当打开时,所有的单引号'
、双引号"
、反斜杠\
、NULL字符
都会被自动加上一个反斜杠\
来进行转义,这个和addslashes()
函数的作用完全相同。所以,如果魔术引号打开了,就不要使用addslashes()
函数了。如下,一共有三个魔术引号指令。
magic_quotes_gpc
影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。 参见get_magic_quotes_gpc()
。magic_quotes_runtime
如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件所返回的数据都会被反斜线转义。该选项可在运行时改变,在 PHP 中的默认值为 off。 参见set_magic_quotes_runtime()
和get_magic_quotes_runtime()
。magic_quotes_sybase
如果打开的话,将会使用单引号对单引号进行转义而非反斜杠。此选项会完全覆盖magic_quotes_gpc
。如果同时打开两个选项的话,单引号将会被转义成"
。而双引号"
、反斜杠\
和NULL字符
将不会进行转义。 如何取得其值参见ini_get()
。
测试代码:
<?php
// 设置GBK编码,解决php中文乱码
header("content-type:text/html;charset=GBK");
echo "SQL宽字节注入环境"."<hr>";
// 连接mysql
$conn = mysqli_connect('192.168.202.128', 'root', 'root');
// 判断连接是否成功
if(!$conn){
die ("连接MySQL发生错误:" . mysqli_connect_error($conn));
}
// 选择库 test
mysqli_select_db($conn, 'test') or die ("无法正确连接到数据库!");
// 设置GBK编码,解决mysql中文乱码
mysqli_set_charset($conn,"GBK");
// 取消报错显示
error_reporting(0);
// 接收参数id
if(isset($_GET['id'])){
$id=$_GET['id'];
// addslashes()函数,在预定义符号前添加反斜杠
$id=addslashes($id);
// 执行sql语句并返回结果
$sql = "SELECT * FROM user WHERE id='$id' LIMIT 0,1";
$result = mysqli_query($conn, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row) {
echo 'Your username:'.$row['username'] . '<br>';
echo 'Your password:'.$row['password'] . '<hr>';
}else{
echo mysqli_connect_error($conn) . "<hr>";
}
}
else{ echo "Please input the ID as parameter with numeric value" . "<hr>";}
echo '执行的SQL语句:'. $sql;
?>
对应页面如下:
我们要想绕过这个转义,就得把\'
的\
给去掉,那么怎么去掉呢?
1.在转义之后,想办法让\
前面再加一个\
,或者是凑成偶数个\
,这样就变成了\\'
,\
被转义了,而'
逃出了限制。
2. 在转义之后,想办法把\
吃掉,只留下一个'
。
这里我们利用第2中方法,即宽字节注入。宽字节注入利用的是MySQL的一个特性:MySQL在使用GBK编码的时候,会认为两个字符是一个汉字,前提是前一个字符的 ASCII 值大于128。
注入语句:
/?id=-1%df' union select 1,(select @@datadir),(select @@version_compile_os)--+
注入结果:
防御措施:
把 addslashes()
函数换成mysql_real_escape_string()
函数,因为php官方文档说了这个函数会考虑到连接的当前字符集。
所以防止宽字节注入的第一个方法就是在调用mysql_real_escape_string()
函数之前,先设置连接所使用的字符集为GBK :
mysql_set_charset=('gbk',$conn)
这个方法虽然可行,但是还有很多网站是使用addslashes()
函数进行过滤的,我们不可能把所有的addslashes()
函数都换成mysql_real_escape_string()
。
所以防止宽字节注入的另一个方法就是在所有SQL语句前将character_set_client
指定连接的形式设置为binary(二进制)。
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);
当我们的MySQL收到客户端的请求数据后,会认为他的编码是character_set_client
所对应的编码,也就是二进制。然后再将它转换成character_set_connection
所对应的编码。然后进入具体表和字段后,再转换成字段对应的编码。当查询结果产生后,会将查询表和字段的编码转换成character_set_results
所对应的编码,返回给客户端。所以,如果我们将character_set_client
编码设置成了binary,就不存在宽字节注入的问题了,数据将以二进制的形式传递。
0x02 SQL注入的防御
代码方向
- 对用户输入数据进行转义,例如
addslashes()
函数在指定的预定义字符前添加反斜杠,把'
转义为\'
- 对特定的关键词进行匹配过滤。如可以检测
select
、from
、union
等关键词,将其过滤。 - 对数据类型及数据长度进行严格限定,防止用户输入过多的无用数据。
- 采用sql语句预编译,SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面无论输入的是什么,都不会影响该SQL语句的语法结构。
- 使用正则表达式过滤传入的参数。
服务器方向
- 对数据库采用最小权限分配,如普通用户与系统管理员用户的权限进行严格的区分,这样即使可以拿到权限也不会造成更大的损失。
- 避免显示SQL执行报错的信息,防止报错信息被利用。
- 数据层的编码统一,防止过滤模型被绕过。