基于宽字节(GBK编码)的特殊SQL注入漏洞如何修复

作者:袖梨 2026-07-01
SET NAMES 'gbk'会触发宽字节注入,因其仅设置character_set_client等会话变量,未同步连接层编码上下文,导致addslashes()按单字节转义('→%5c%27),而MySQL用GBK解析时将%df%5c组合成汉字,“吃掉”反斜杠使%27逃逸;必须改用mysqli_set_charset()或SET character_set_client=binary等方案对齐编码。

为什么 set names gbk 会触发宽字节注入

因为 SET NAMES 'gbk' 只修改了 MySQL 的 character_set_clientcharacter_set_resultscharacter_set_connection,但没真正绑定连接层的字符集语义。PHP 调用 addslashes() 时按单字节处理(比如把 '',即 %27%5c%27),而 MySQL 在解析时把前面一个高位字节(如 %df)和 %5c 拼成一个 GBK 宽字符(如 “運”),导致 %5c 被“吃掉”,%27 逃逸成裸单引号。

mysql_set_charset() 和 mysqli_set_charset() 是必须替换的点

这两个函数不仅发 SET 命令,还会同步设置连接层的字符集上下文,让 MySQL 在接收数据时严格按指定编码解析,避免宽字节“吞字”行为。不替换 SET NAMES 就等于没修。

  • mysql_set_charset("gbk", $conn)(已废弃,仅用于遗留代码)
  • mysqli_set_charset($conn, "gbk")(推荐,需在 mysqli_real_escape_string() 前调用)
  • 如果用 PDO,必须显式设置 PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",且不能只靠 DSN 中的 charset=gbk

mysql_real_escape_string() 不能替代预编译,但它是宽字节场景下的兜底手段

该函数内部会读取当前连接的实际字符集(由 mysqli_set_charset() 设置),再做符合该编码规则的转义——比如对 GBK,它知道 %df%5c 是合法双字节,不会盲目拼接反斜杠。而 addslashes() 完全无视编码,纯字节流操作,必然出问题。

  • 必须确保 mysqli_real_escape_string() 调用前,连接已通过 mysqli_set_charset() 设定好字符集
  • 若传入空连接或未初始化连接,函数返回 false,容易被忽略导致绕过
  • 它只防字符串型注入,数字型参数仍需 intval() 或参数化

character_set_client=binary 是最硬核的防御方式

在查询前执行 mysqli_query($conn, "SET character_set_client = binary"),会让 MySQL 把客户端输入当作原始字节流处理,不再尝试按 GBK 解码。此时 %df%5c%27 不会被识别为“運'”,而是三个独立字节,MySQL 直接报错或截断,无法构成有效 SQL 语法。

  • 这个设置只影响当前 query,需每次查询前重设(或封装进统一 query 函数)
  • 它不解决乱码问题,但能彻底阻断宽字节注入链路
  • 注意:不能和 SET NAMES 混用,否则后者会覆盖前者
关键点在于:宽字节注入不是 PHP 层漏了过滤,而是字符集声明与实际转义逻辑脱节。修复必须从连接层编码一致性切入,任何只改 SQL 拼接方式或只换过滤函数的方案,只要底层还是 SET NAMES gbk + addslashes(),就等于门没锁死。

相关文章

精彩推荐