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

宽字节注入不是“没过滤好”,而是字符集在连接层、传输层、存储层三处没对齐,只要其中一环是 gbk 或 latin1,而其他环节是 utf8mb4,漏洞就可能被触发。根治方式只有一条:全程强制 utf8mb4,且每个环节都显式声明,不能依赖默认或隐式推断。
为什么 SET NAMES gbk 会主动打开攻击通道
SET NAMES gbk 等价于同时设置 character_set_client、character_set_connection 和 character_set_results 为 gbk,但它不绑定连接层的字符集语义。PHP 的 addslashes() 是纯字节操作,把 ' 变成 \'(即 %5c%27);MySQL 按 gbk 解析时,若前一字节是 %df,就会和 %5c 组合成汉字(如 “運”),导致 %27 裸露逃逸。
- 常见错误:在调用
addslashes()后才执行SET NAMES gbk - 更隐蔽的错:PHP 文件写了
header("Content-Type: text/html; charset=utf-8"),但数据库连接仍走gbk - 危险组合:
mysql_query("SET NAMES gbk")替代连接时的字符集声明
PHP + MySQLi 必须显式设置 utf8mb4 的三个位置
漏掉任意一个,utf8mb4 就形同虚设。注意:utf8 在 MySQL 中是别名,实际对应 utf8mb3,不支持 emoji,某些旧版本甚至存在边缘宽字节解析路径;必须写死 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
Node.js + mysql2 的典型错误配置
很多项目写 charset: 'UTF8' 或 charset: 'utf8',这在 mysql2 中实际对应 MySQL 的 utf8(即 utf8mb3),不支持 emoji,更重要的是——它仍可能触发某些边缘宽字节解析路径(尤其配合 sql_mode=NO_BACKSLASH_ESCAPES 时)。
- 正确做法:连接选项中明确写
charset: 'utf8mb4' - 执行
SET NAMES utf8mb4作为初始化查询(即使已设charset,也建议双保险) - 检查
process.env.NODE_ENV是否在测试/开发环境误启用了 GBK 兼容模式 - 确认环境变量
MYSQL_CHARSET或配置文件里没覆盖这个值
预处理语句不是万能的,字符集仍要对齐
PDO::prepare() 或 mysqli_prepare() 能彻底隔离 SQL 结构与数据,让输入永远无法参与语法解析——这是目前最可靠的防护手段。但它不豁免字符集问题:
- 如果连接层是
gbk,而绑定的字符串是 UTF-8 编码,MySQL 可能错误截断或乱码,导致查询失败或逻辑异常 - 某些驱动(如旧版
mysql2)在charset: 'UTF8'配置下仍回退到utf8mb3,需明确写charset: 'utf8mb4' - 即便用了预处理,若响应头或 HTML
<meta>是gbk,浏览器可能误判编码,引发前端二次编码错乱,间接影响后端解析
真正容易被忽略的是:字符集对齐不是“设一次就完事”,它横跨 PHP/JS 层、HTTP 响应头、数据库连接、表结构、索引、甚至连接池(如 HikariCP)的 connectionInitSql。任何一个环节松动,%df%5c%27 就可能重新变成有效载荷。











