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

addslashes() 为什么挡不住 %df%27
addslashes() 是纯字节层面操作:看到 ' 就加 \,完全不关心当前是什么字符集。它把输入 %df%27(即字节序列 0xDF 0x27)变成 %df%5c%27(0xDF 0x5C 0x27)。但 MySQL 在 GBK 连接下解析时,会把 0xDF 0x5C 当作一个宽字符(如「運」),于是 0x27 被单独留下——成了裸单引号。
这不是函数“没写好”,而是两端对字节流的解读错位。关键点在于:
-
addslashes()输出的是字节,不是字符 - MySQL 按 GBK 解析时,“吃掉”了
0x5C,导致转义失效 - 只要第一个字节在
0x81–0xFE范围(如%df、%a1),就可能触发;%df最常用,因兼容性高、不易中断连接
宽字节注入成立的三个硬性条件
缺一不可,否则 payload 就是无效垃圾:
- MySQL 连接层字符集必须是
gbk/gb2312/big5等多字节编码(常见于SET NAMES gbk或旧版mysql_set_charset('gbk')) - PHP 层用了
addslashes()、mysql_escape_string()这类无上下文感知的转义函数,而不是PDO::quote()或预编译参数绑定 -
数据库表/字段实际存储编码也得支持宽字节(如
CHARSET=gbk),否则 server 层内部转换可能截断或报错
注意:SET NAMES gbk 会同时设置 character_set_client、character_set_connection、character_set_results,但只设这个,addslashes() 仍按 PHP 字节流处理,无法对齐 MySQL 的解析视角。
MySQL 8.0+ 安全配置必须端到端统一 utf8mb4
UTF-8(准确说是 utf8mb4)每个字节独立解析,0x5C 和 0x27 不会组合,宽字节注入天然无效。但光改一处没用,必须全链路一致:
- PHP 连接时显式设置:
mysqli_set_charset($conn, 'utf8mb4')或 PDO DSN 中加charset=utf8mb4 - MySQL 服务端配置
my.cnf:设character_set_server = utf8mb4和collation_server = utf8mb4_unicode_ci - 建库/建表/改表时指定:
CREATE DATABASE db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 - 检查生效:
SELECT @@character_set_client, @@character_set_connection, @@character_set_results三者都应返回utf8mb4
特别注意:只写 SET NAMES utf8mb4 不够稳定,某些连接池(如 HikariCP)可能忽略;SET NAMES utf8 更危险——MySQL 的 utf8 实为 utf8mb3,不支持 emoji 且部分版本存在兼容问题。
为什么现代开发中还在踩坑
真正该防的不是 %df,而是设计惯性与配置疏漏:
- 仍在用已废弃的
mysql_*扩展(如 Xiuno BBS 4.0.4 的db_mysql.class.php),无法调用mysql_set_charset(),只能靠SET NAMES,埋下错配温床 - 把
addslashes()当万能盾,却不知道它对\x00、\n、\r、\x1a处理不一致,连基本 SQL 字符边界都守不住 - HTML 写了
<meta charset="utf-8">、PHP header 设了Content-Type: text/html; charset=utf-8,但数据库连接层仍是latin1或gbk,字符集割裂照旧
最省事也最可靠的做法:删掉所有拼接 SQL 的 addslashes(),改用预编译参数绑定;再把整个链路字符集钉死在 utf8mb4。前者解决逻辑漏洞,后者堵住编码侧信道——两者缺一,都算没防住。











