addslashes 不是防 sql 注入的可靠方法,它仅简单转义特定字符而不感知 sql 语义,无法防御宽字节截断、十六进制绕过等攻击,且在 pdo 真实预处理下会导致双重转义;正确方案是 pdo 预处理绑定参数并禁用模拟预处理。

直接说结论:addslashes 不是防 SQL 注入的可靠方法,它不能替代预处理(PDO::prepare 或 mysqli_prepare),且对 PDO 的 PDO::ATTR_EMULATE_PREPARES = false 场景完全无效。
为什么 addslashes 不能防注入?
它只是在单引号、双引号、反斜杠和 NULL 字节前加反斜杠,不感知 SQL 语义,也不处理十六进制编码、宽字节绕过、多字节字符集(如 gbk)下的截断问题。攻击者可绕过方式包括:
- 使用
%df%27(gbk 双字节截断)触发引号逃逸 - 用
0x756e696f6e2073656c656374十六进制绕过单引号过滤 -
PDO开启真实预处理时,addslashes会把参数里的反斜杠原样送入 SQL,导致数据被错误转义(比如存入O'Reilly变成O\'Reilly)
正确做法:用 PDO 预处理 + 绑定参数
这才是真正切断 SQL 逻辑与数据的方案。关键点:
- 必须设置
PDO::ATTR_EMULATE_PREPARES = false(禁用模拟预处理) - 参数一律用
bindValue或bindParam,不要拼接字符串 - 字符集要在 DSN 中指定,例如
charset=utf8mb4,不能靠set names
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = ? AND status = ?');
$stmt->bindValue(1, $_GET['name'], PDO::PARAM_STR);
$stmt->bindValue(2, $_GET['status'], PDO::PARAM_INT);
$stmt->execute();
什么时候能用 addslashes?
仅限极少数遗留场景,且必须满足全部条件:
立即学习“PHP免费学习笔记(深入)”;
- 数据库字符集明确为
latin1或严格utf8(非utf8mb4) - 确认未开启
magic_quotes_gpc(PHP 5.4+ 已移除,但老系统可能有自定义兼容层) - 仅用于生成日志、调试输出等非 SQL 上下文的简单转义
- 绝对不用在
mysqli_real_escape_string已可用的情况下替代它
哪怕满足以上,也建议改用 mysqli_real_escape_string($link, $str)——它依赖当前连接的字符集,比 addslashes 安全得多,但仍不如预处理。
addslashes 和特殊符号 _ 的关系?
它对下划线 _ 完全不做处理——_ 在 SQL 中是通配符(LIKE 模式匹配),但 addslashes 不管这个。如果你要安全地做模糊查询,得手动转义 _ 和 %,并指定 ESCAPE 字符:
$search = str_replace(['_', '%'], ['_', '%'], $_GET['q']);
$stmt = $pdo->prepare("SELECT * FROM posts WHERE title LIKE ? ESCAPE '\'");
$stmt->execute(["%{$search}%"]);
注意:这里的 是 SQL 的转义符,不是 PHP 的;PDO 绑定后不会二次解析,所以必须由你控制好转义逻辑。
真正麻烦的地方不在函数怎么写,而在很多人把「看起来没报错」当成「防住了」——只要没走预处理,SQL 注入风险就一直存在,哪怕用了 addslashes、htmlspecialchars 或正则过滤。











