MySQL宽字节注入仅在GBK、GB2312等双字节字符集下生效,因其高位字节(如%df)可与后续ASCII字符(如%27)组合成合法多字节字符,绕过转义;而UTF8/UTF8MB4为单字节独立解析,无法触发该漏洞。

为什么 MySQL 宽字节注入只在特定字符集下生效
宽字节注入本质是利用 gbk、gb2312 等双字节编码中「%df」这类高位字节与后续 ASCII 字符(如 %27 单引号)组合,绕过转义逻辑,让 \' 变成合法的多字节字符从而逃逸。它在 utf8 或 utf8mb4 下完全无效——因为这些编码里每个字节都独立解析,不存在「吃掉下一个字节」的行为。
所以问题根源不在 SQL 写法,而在连接层和表结构的字符集不一致:比如数据库默认 utf8mb4,但 JDBC 连接字符串里写了 characterEncoding=GBK,或连接池(如 HikariCP)未显式指定 connectionInitSql 设置会话编码。
- MySQL 服务端字符集(
character_set_server)决定新库/表默认编码,但不强制客户端行为 - 客户端连接时声明的字符集(
SET NAMES gbk)才真正影响 SQL 解析流程 - 即使表字段是
utf8mb4,只要连接层用gbk,mysql_real_escape_string(或旧版 PHP 的addslashes)就可能被绕过
如何验证当前连接实际使用的字符集
别只看配置文件或连接 URL,运行时的会话级设置才是关键。直接查 SHOW VARIABLES LIKE 'character\_set%':
mysql> SHOW VARIABLES LIKE 'character_set%'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | gbk | | character_set_connection | gbk | | character_set_database | utf8mb4 | | character_set_results | gbk | | character_set_server | utf8mb4 | +--------------------------+----------------------------+
只要 character_set_client 或 character_set_connection 是 gbk 或 gb2312,风险就存在。注意:character_set_database 是当前 USE 的库的默认编码,不影响连接解析。
- PHP mysqli:调用
$mysqli->query("SHOW VARIABLES LIKE 'character_set_client'")实时确认 - JDBC:执行
SELECT @@character_set_client, @@collation_connection - Spring Boot + HikariCP:在
application.yml中加connection-init-sql: SET NAMES utf8mb4
统一字符集的实操配置清单
必须同时控制三处:MySQL 服务端、连接驱动层、应用代码层。漏掉任意一环都可能被绕过。
- MySQL 配置文件(
my.cnf)追加:[client]default-character-set = utf8mb4[mysqld]character-set-server = utf8mb4collation-server = utf8mb4_unicode_ci - JDBC URL 显式声明:
jdbc:mysql://x.x.x.x:3306/db?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai—— 注意characterEncoding是驱动解码参数,不是服务端设置 - PHP PDO 构造时传
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",且确保mysql.default_charset在php.ini中未设为gbk - Node.js mysql2:在连接选项中加
charset: 'utf8mb4',而非依赖SET NAMES
为什么不能只靠预处理语句(Prepared Statement)就高枕无忧
预处理语句确实能从根本上防止注入,但前提是它真正在起作用。常见失效场景包括:
- 动态拼接表名/列名(
SELECT * FROM ?不合法,?只能代入值,不能代入标识符) - ORM 框架底层仍用字符串拼接生成 SQL(如某些旧版 ThinkPHP 的
where条件构造) - 连接字符集错误导致预处理元数据解析异常,退化为普通查询(极少见但有日志可查)
- 开发误用
mysqli_real_escape_string()+ 手动拼接,以为比预处理“更可控”
字符集统一不是“锦上添花”,而是预处理能正常工作的前提条件之一。如果连接层还在用 gbk,即便用了 prepare(),某些边界 case(如含特殊字节的二进制数据)仍可能触发解析歧义。
最易被忽略的是连接池初始化阶段——HikariCP 默认不执行任何 init SQL,除非你显式配了 connection-init-sql;Druid 则默认执行 SELECT 1,但不会改字符集。这个空档期,第一个连接可能带着错误的会话变量进来。










