addslashes不能防住所有SQL注入,根本原因在于它不感知SQL语法上下文和数据库字符集,对数字型参数完全无效,且易受宽字节注入绕过;预编译+参数绑定才是可靠解法。

addslashes 不能防住所有 SQL 注入,根本原因在于它不感知 SQL 语法上下文和数据库字符集
addslashes 对数字型参数完全无效
当 SQL 中的字段是数字类型(如 id = ?),却用字符串拼接方式写成 "id = '$id'",addslashes 就毫无意义。攻击者直接传 id=1 OR 1=1,不会触发任何单引号,转义逻辑压根不生效。
- 典型场景:
$sql = "SELECT * FROM users WHERE id = $id";—— 这里$id没有引号包裹,addslashes加的反斜杠反而可能破坏整数解析 - 正确做法:数字参数必须强制类型转换(如
(int)$id)或统一走参数绑定 - 隐患点:很多老代码把所有输入“习惯性”过一遍
addslashes,误以为一劳永逸
宽字节注入可绕过 addslashes
在 MySQL 使用 GBK、GB2312 等多字节编码时,addslashes 的转义会被吃掉一个字节。例如输入 %df%27(%df 是 GBK 下的合法首字节),MySQL 会将 %df%5c(即 %df\)识别为一个汉字,导致后续的 %27(单引号)逃逸出来。
- 触发条件:PHP 连接未显式设置字符集(如
SET NAMES gbk),且 MySQL 配置为character_set_client=gbk -
mysqli_real_escape_string能规避此问题,因为它依赖实际连接的字符集做转义;但addslashes完全无视这点 - 现代 PHP 默认用
utf8mb4,但遗留系统或中间件仍可能降级到 GBK
预编译 + 参数绑定才是可靠解法
预编译的本质是把 SQL 结构和数据分离传输:先发模板(如 SELECT * FROM users WHERE username = ?),再单独发参数值。MySQL 在服务端解析时,参数永远作为“数据”处理,不会参与语法解析。
- MySQLi 示例:
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?"); $stmt->bind_param("s", $_GET['username']); $stmt->execute(); - PDO 示例:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$_GET['username']]); - 注意:
PDO::ATTR_EMULATE_PREPARES必须设为false,否则 PDO 会在 PHP 层模拟拼接,退化为字符串替换 - 无法参数化的部分(如表名、ORDER BY 字段)必须用白名单校验,不能靠任何转义函数
真正容易被忽略的是:预编译只保值,不保结构。哪怕用了 prepare,如果把用户输入拼进表名、字段名或 UNION SELECT 子句里,照样中招。防御边界必须划清楚——哪些能参数化,哪些只能白名单,这个判断比写几行转义代码难得多。











