宽字节注入

环境

Ubuntu16+apache+Mysql+PHP

数据库中有两个数据表,一个为test数据表,存储着希望用户查询的信息。另一个为admins数据表,存储着不希望用户查看的敏感信息。

SQL注入

首先编写一个最简单的SQL注入环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$servername = "localhost";
$username = "root";
$password = "root";

$conn = mysqli_connect($servername, $username, $password,"wide_character");
$name = $_GET["name"];
$sql = "select password from test where name='". $name . "';";
$result = $conn->query($sql);
if($result->num_rows>0)
{
while($row = $result->fetch_assoc()) {
echo "Name: " . $name. "<br>Password: ". $row["password"]. "<br>";
}
} else {
echo $name." 0 结果<br>";
}
echo "SQL: ".$sql;
$conn->close();
?>

没有任何过滤,没有任何转义。

可以通过闭合查询语句中的单引号进行SQL注入,得到admin数据表中的信息。

修复

可以在php脚本中添加意义函数,将引号等特殊字符转义。

1
$sql = mysqli_real_escape_string($conn,$sql);

此函数会将

\

\x00

\n

\r

进行转义,在前面添加一个反斜线/

宽字节注入

原理

当一个sql语句从脚本处生成发送到数据库进行查询的时候要经过三个主要的节点:脚本 => 连接层 => 数据库,在这三个节点中,sql语句可以以三种不同的编码方式存在。脚本处sql编码方式就是脚本(PHP)设置的编码方式,服务器会将sql语句从客户端编码方式也就是下图中的character_set_client,转换为连接层的字符编码方式,也就是下图中的character_set_connection之后进行16进制编码。完成查询之后会将结果按照下图中character_set_results设置的字符集进行返回。

执行SQL语句之前,会将请求按照从字段 > 数据表 > 数据库 > character_set_server 的规则进行转换,当前的test数据表的编码方式如下图

宽字节注入指的是当mysql使用gbk编码时,并且将特殊字符进行了转义,在单/双印号前面添加了一个反斜线/,反斜线的编码是16进制的%5c,MySQL的gbk编码认为%df%5c是一个宽字节汉子,相当于在16进制编码之中把反斜线给“吃掉了”,以此进行引号的闭合。

宽字节注入的整个过程就是

%df%27(character_set_client => %df%5c%27(mysqli_real_escape_string) => %df%5c%27(GBK)

在最终的SQL执行中数据库看到的是運’,而不是%df/',成功闭合了单/双引号,造成注入。

实现

先将数据表的编码方式修改为gbk

如果要查询其他表中的数据,需要使用union的时候需要另一个表的编码也是gbk,否则会抛出错误Illegal mix of collations (gbk_chinese_ci,IMPLICIT) and (latin1_swedish_ci,IMPLICIT) for operation 'UNION'.

还需要将mysql启动时的默认编码方式设置为gbk,在mysql命令行中进行更改没用,必须要从配置文件进行更改。在/etc/mysql/mysql.cnf文件中按照如下图设置

重启mysql,执行SQL注入。看到使用gbk编码将%df%5c识别称为了汉子,成功吞噬了反斜线,闭合引号完成SQL注入。