宽字节sql注入根治方式是全程强制utf8mb4,且连接层、传输层、存储层三者字符集必须显式对齐;set names gbk会主动打开攻击通道,因addslashes()字节级转义的%5c在gbk下与前字节如%df组合成汉字,致%27裸露逃逸。

宽字节SQL注入不是转义函数写得不够好,而是数据库连接层、传输层、存储层三者字符集没对齐——只要其中一环是 gbk 或 latin1,而其他环节是 UTF-8,漏洞就可能被触发。根治方式只有一条:全程强制 utf8mb4,且每个环节都显式声明,不能依赖默认或隐式推断。
为什么 SET NAMES gbk 会主动打开攻击通道
这条语句等价于同时设置 character_set_client、character_set_results 和 character_set_connection 为 gbk。但 PHP 的 addslashes() 或 mysqli_real_escape_string() 是在字节层面操作,不感知字符集;当它把 ' 变成 \'(即 %5c%27),而 MySQL 按 gbk 解析时,若前一个字节是 %df 或 %bf,就会和 %5c 组合成一个汉字,导致 %27 裸露。
常见错误场景:
- 在调用
addslashes()后才执行SET NAMES gbk - 用
mysql_query("SET NAMES gbk")替代连接时的字符集声明 - PHP 文件声明了
header("Content-Type: text/html; charset=utf-8"),但数据库连接仍走gbk
PHP + MySQLi 必须显式设置 utf8mb4 的三个位置
漏掉任意一个,utf8mb4 就形同虚设:
- 连接建立后立即调用
mysqli_set_charset($conn, 'utf8mb4')—— 这比SET NAMES utf8mb4更可靠,它同步更新客户端与服务器端的字符集变量 - 建表/改字段时确认
COLLATION是utf8mb4_unicode_ci或utf8mb4_general_ci,用SHOW CREATE TABLE users查看,避免出现gbk_chinese_ci - DSN 或连接初始化参数中带
charset=utf8mb4,例如:new mysqli($host, $user, $pass, $db, $port, $socket, MYSQLI_CLIENT_FOUND_ROWS)后紧跟set_charset
注意:utf8 在 MySQL 中是别名,实际对应 utf8mb3,不支持 emoji,某些旧版本甚至仍存在边缘宽字节解析路径;必须写死 utf8mb4。
预处理语句才是根本解法,但字符集仍要对齐
PDO::prepare() 或 mysqli_prepare() 能彻底隔离 SQL 结构与数据,让输入永远无法参与语法解析——这是目前最可靠的防护手段。但它不豁免字符集问题:
- 如果连接层是
gbk,而绑定的字符串是 UTF-8 编码,MySQL 可能错误截断或乱码,导致查询失败或逻辑异常 - 某些驱动(如旧版
mysql2)在charset: 'UTF8'配置下仍回退到utf8mb3,需明确写charset: 'utf8mb4' - 即便用了预处理,若响应头或 HTML meta 是
gbk,浏览器可能误判编码,引发前端二次编码错乱,间接影响后端解析
所以预处理 + 全栈 utf8mb4 是双保险,缺一不可。
最容易被忽略的是:数据库服务端默认字符集(character_set_server)只是新库/新表的默认值,它不控制客户端行为;真正起效的是连接时声明的字符集。哪怕表定义全是 utf8mb4,只要连接用 gbk,addslashes() 类函数就依然失效。










