宽字节注入是因字符集不一致导致的实战漏洞:当PHP用addslashes()转义单引号为%5c%27,而MySQL连接设为gbk时,%df%5c被解析为汉字“運”,使后续%27逃逸成裸单引号,从而绕过过滤。

数据库字符集设置不一致,特别是连接层与表/列字符集不匹配时,可能绕过常规转义逻辑,直接触发宽字节注入——这不是理论风险,而是真实可复现的漏洞链。
为什么 SET NAMES gbk 会放大 addslashes() 的失效风险
当 PHP 使用 addslashes() 处理用户输入时,它只对 ASCII 范围内的单引号 '、反斜杠 等做简单转义,变成 ' 或 \。但如果数据库连接字符集设为 gbk(双字节),而 PHP 字符串实际是 UTF-8 编码或未声明编码,%df%5c 这样的字节序列就可能被 MySQL 解析为一个合法的 GBK 字符(如「運」),导致后面的 %27(即 ')逃逸转义,变成未闭合的单引号。
关键点在于:addslashes() 不感知字符集,它只是字节层面插入 ;而 MySQL 在 gbk 模式下按双字节解析,把 %df%5c 合并识别为一个字符,于是 %5c%27 就不再是「转义单引号」,而是「一个汉字 + 一个裸单引号」。
- 必须同时满足:PHP 开启了
magic_quotes_gpc(或手动调用addslashes())、MySQL 连接使用gbk或big5、后端未统一强制设置SET NAMES utf8mb4 -
utf8mb4和gb2312不具备该特性:前者不含0x5c作为尾字节的有效组合,后者是单字节编码,无法构造宽字节逃逸 - 常见错误写法:
mysql_query("SET NAMES gbk");放在addslashes()之后,等于主动打开攻击通道
如何验证当前连接与表的字符集是否真正一致
不能只看 SHOW CREATE TABLE users 返回的 DEFAULT CHARSET=gbk,还要确认连接层实际生效的字符集。二者不一致时,即使表定义是 utf8mb4,只要连接用 gbk,宽字节注入仍可能发生。
执行以下语句检查实时状态:
SELECT @@character_set_client, @@character_set_connection, @@character_set_results;
再查表字段级字符集:
SELECT character_set_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'username';
- 理想状态三者一致:比如全是
utf8mb4;若出现client=gbk但connection=utf8mb4,说明客户端声明和连接协商不匹配 - PHP 中应显式设置连接字符集,而不是依赖
SET NAMES:PDO 构造时加;charset=utf8mb4,mysqli 用$mysqli->set_charset('utf8mb4') - MySQL 配置文件中
init_connect="SET NAMES utf8mb4"是危险的——它对所有用户生效,包括可能被利用的低权限连接
mysqli_set_charset() 和 SET NAMES 的行为差异
表面上都用来设置字符集,但底层影响范围不同。SET NAMES 是纯 SQL 语句,只修改当前连接的三个变量(client/connection/results),不触碰 MySQL 内部协议层的字符集协商;而 mysqli_set_charset() 会同时更新连接变量,并在 MySQL 协议握手阶段同步告知服务端,确保从建连开始就走正确编码路径。
- 使用
mysqli_set_charset('utf8mb4')后,@@character_set_client必然等于utf8mb4;而mysql_query("SET NAMES utf8mb4")可能因执行时机或权限问题未生效 - 旧版
mysql_*函数已废弃,且不提供等效的协议层字符集设置能力,强行迁移时必须改用mysqli或PDO - 框架如 Laravel 默认使用
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",但这仍是 SQL 层设置,不如charset=utf8mb4参数可靠
最易被忽略的是:即便应用代码里写了 set_charset,如果中间有代理(如 ProxySQL)、连接池(如 MySQL Router)或云数据库网关,它们可能重置或覆盖连接字符集。上线前必须在真实链路末端抓包验证实际发送的初始化命令和返回的变量值。











