宽字节sql注入仅在gbk/gb2312等双字节字符集下有效,因高位字节(如%df)可与%5c组合成汉字使单引号%27逃逸;utf-8/utf8mb4中各字节独立解析,无此问题,但须确保连接层、服务端、表结构三者字符集严格统一为utf8mb4。

宽字节SQL注入只在GBK/GB2312等双字节字符集下有效,UTF-8或utf8mb4完全免疫——但前提是连接层、服务端、表结构三者字符集必须严格统一。
为什么SET NAMES gbk会直接触发漏洞
当执行SET NAMES gbk时,MySQL会同时设置character_set_client、character_set_connection和character_set_results为gbk。此时若PHP用addslashes()转义'成\'(即%5c%27),攻击者传入%df%27,MySQL GBK解析器会把%df%5c当作一个汉字(如“運”),导致%27单独剩下一个未转义的单引号。
- 该行为不发生在utf8mb4:每个字节独立解析,
%5c就是反斜杠,%27就是单引号,不会被“吞” -
SET NAMES utf8也不安全:MySQL的utf8实际是utf8mb3,不支持emoji且存在兼容性陷阱 - 仅HTML里写
<meta charset="utf-8">或PHP输出头header("Content-Type: text/html; charset=utf-8")完全无效——它们不影响数据库连接层
PHP + MySQLi连接时必须显式设置utf8mb4
光靠配置文件或建库语句远远不够,PHP每次建立连接都需主动声明字符集,否则默认走MySQL服务器级配置(通常是latin1或gbk)。
- 使用
mysqli_set_charset($conn, 'utf8mb4')——这是最可靠方式,优先于SET NAMES - 若用PDO,DSN中必须带
charset=utf8mb4:"mysql:host=localhost;dbname=test;charset=utf8mb4" - 避免只写
SET NAMES utf8mb4:它可能被连接池(如HikariCP)或中间件拦截,且不如set_charset底层稳定 - 检查是否生效:连接后执行
SELECT @@character_set_client, @@character_set_connection, @@character_set_results,三者都应返回utf8mb4
MySQL服务端与表结构必须同步为utf8mb4
即使PHP连接正确,若数据库、表或字段本身用gbk或utf8,MySQL在存储/比较时仍会做隐式转换,可能绕过连接层设置。
- 全局配置(
my.cnf)需包含:character_set_server = utf8mb4和collation_server = utf8mb4_unicode_ci - 新建库必须指定:
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci - 已有表要升级:
ALTER TABLE tbl CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci - 特别注意字段级定义:如果某
VARCHAR字段显式声明CHARACTER SET gbk,哪怕连接是utf8mb4,该字段读写仍按gbk处理
永远不要依赖addslashes()或magic_quotes_gpc
这些函数对宽字节注入毫无防御力,且magic_quotes_gpc早在PHP 5.4.0就已废弃,7.0+彻底移除。
-
addslashes()只按ASCII处理,不知道%df%5c在GBK里是个汉字 - 参数化查询(
PDO::prepare()/mysqli_prepare())才是根本解法——值不参与SQL语法解析,编码问题自然消失 - 若必须拼接SQL(如动态列名),应严格白名单校验,而非尝试“修复”转义逻辑
最容易被忽略的是字段级字符集定义:哪怕整个库设为utf8mb4,只要某个TEXT字段写着CHARACTER SET gbk,对该字段的查询就可能重新触发宽字节路径。检查SHOW CREATE TABLE输出里的每一处CHARACTER SET声明。











