addslashes()挡不住%df%27,因其纯字节转义('→%5c%27),而MySQL在GBK下将%df%5c解析为汉字“運”,使%27逃逸成裸单引号;成立需三条件:连接层为GBK等宽字节编码、PHP用addslashes类无上下文函数、表字段支持宽字节。

addslashes() 为什么挡不住 %df%27 这种输入
因为 addslashes() 是纯字节层面的转义,它只管“看到单引号就加 ”,完全不感知字符集。而 MySQL 在 GBK 模式下解析 SQL 时,会把两个连续字节(如 %df%5c)当作一个汉字,导致原本用来转义的 (即 %5c)被“吃掉”,后面紧跟的 '(%27)就裸奔进 SQL 语句里了。
典型链路是:id=1%df' → PHP 层 addslashes() 处理成 1%df'(即 1%df%5c%27)→ MySQL 用 GBK 解析时,把 %df%5c 合成一个无效汉字(如「運」),剩下 %27 变成独立单引号 → 最终执行的是 WHERE id='1運'',语法错误或逻辑逃逸。
宽字节注入成立的三个硬性条件
缺一不可,否则 %df 类 payload 就是无效垃圾:
- MySQL 连接层字符集必须是 GBK / GB2312 / BIG5 等多字节编码(常见于
SET NAMES gbk或旧版mysql_set_charset('gbk')) - PHP 层必须用
addslashes()、mysql_escape_string()这类非上下文感知的转义函数,而不是PDO::quote()或预编译参数绑定 - 数据库表字段实际存储编码也得支持宽字节(如
CHARSET=gbk),否则 server 层在内部转换时可能二次截断或报错
为什么 %81–%fe 都能用,但 %df 最常见
只要第一个字节 ASCII > 128(即十六进制 81–fe),和紧随其后的 %5c 就可能被 MySQL 当作合法双字节字符。但不同字节组合在不同 MySQL 版本/配置下兼容性不一:
-
%df稳定性高:在绝大多数 GBK 环境下都能与%5c组成有效宽字符,且不会触发连接中断 -
%a1–%a9在部分 MySQL 5.5+ 下可能被识别为控制字符,导致 query 失败 -
%5c自身不能单独出现——如果输入里本来就有反斜杠,addslashes()会变成%5c%5c,这时再拼%df就可能变成%df%5c%5c%27,MySQL 解析为「籠\\'」,四个恰好转义成两个,依然漏掉单引号
现代开发中真正该防的不是 %df,而是设计缺陷
宽字节注入本质是历史包袱叠加配置错位的结果。现在还踩坑,往往是因为:
- 仍在用已废弃的
mysql_*扩展(如 Xiuno BBS 4.0.4 的db_mysql.class.php),无法设置character_set_client=binary - 用
SET NAMES gbk而非更精确的SET character_set_connection=gbk, character_set_client=binary,导致 client 层仍按多字节解码 - 把
addslashes()当万能盾,却没意识到它连
