gbk宽字节注入的根本原因是\(0x5c)在gbk中可能作为汉字高位字节“吞掉”后续字节,使\'逃逸闭合;addslashes()因不感知编码边界而失效;唯一可靠防御是参数化查询。

GBK 编码下 \ 无法正确转义单引号的原因
根本原因不是转义本身失效,而是 \(ASCII 0x5C)在 GBK 中可能被解释为某个汉字的高位字节,从而“吞掉”后续字节,导致实际参与 SQL 解析的字符串结构被破坏。例如 \' 在 GBK 下若被当作一个双字节字符的首字节(如 \x5c\x27 → \x5c 和 \x27 被错认为属于同一个汉字),则后面的 ' 就逃逸出转义上下文,变成真正的语句闭合符。
宽字节注入中 addslashes() 为什么形同虚设
addslashes() 是纯字节操作函数,它只对 ASCII 范围内的 '、"、\、\0 做简单前缀添加,完全不感知字符编码边界。当数据库连接使用 gbk,而 PHP 字符串以 UTF-8 存储或传输时,addslashes() 插入的 \ 很可能落在某个 GBK 双字节字符的中间位置,使原意的“转义”变成“构造新汉字”,反而促成注入。
- 典型触发条件:
mysql_set_charset('gbk')+addslashes()+ 用户输入含高位字节(如%A1类 GBK 首字节) - 常见 payload:
%A1%5C%27 OR 1=1#→%5C%27被 GBK 合并为一个汉字,%27(即')裸露出来 - PHP 7.4+ 默认禁用
mysql_*扩展,但 mysqli/pdo 若未显式设置set_charset('gbk'),仍可能因历史配置残留导致隐式 GBK 解析
如何验证当前是否处于宽字节注入风险环境
关键不是看页面编码声明,而是确认三处编码是否对齐:HTTP 请求头、PHP 输入流解码方式、MySQL 连接层 charset。最直接的验证方式是发一个已知 GBK 双字节 payload 并观察报错或行为变化。
- 发送
?id=1%df%27(%df是 GBK 下合法高位字节,%df%27组合成一个汉字,使'脱离引号) - 如果返回 MySQL 语法错误(如
You have an error in your SQL syntax),说明'成功闭合了字符串,存在宽字节注入可能 - 用
mb_detect_encoding($_GET['id'], ['GBK', 'UTF-8'], true)检查输入实际编码,但注意该函数不可靠——它只检测字节模式,不反映 MySQL 实际如何解析
真正安全的防御姿势:别依赖 \,改用参数化
所有基于手动拼接 SQL + 字符串转义的方案,在多编码混用场景下都不可靠。唯一能绕过编码博弈的是让数据和语句彻底分离。
- mysqli 推荐写法:
$stmt = $mysqli->prepare("SELECT * FROM user WHERE name = ?"); $stmt->bind_param("s", $_GET['name']); $stmt->execute(); - PDO 必须显式关闭模拟预处理:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);,否则 PDO 会在客户端“假装”预处理,退化为字符串拼接 - 如果必须用
mysql_real_escape_string()(已废弃),它内部会调用 MySQL 客户端库读取当前连接 charset 并做对应转义,比addslashes()多一层编码感知,但仍不如预处理可靠
宽字节问题的本质,是把字符编码的解析权交给了不同层级(PHP、MySQL client、MySQL server),而它们对同一段字节的理解不一致。只要还在拼 SQL,就永远要担心下一个 %a1 会不会让 \ 变成画蛇添足。











