唯一可靠方式是预处理语句(PDO::prepare或mysqli_prepare),因addslashes仅转义特定字符、不防数字型注入和宽字节绕过,且无法处理表名等非值上下文。

mysqli 和 PDO 的预处理语句是 PHP 7 中修复 SQL 注入最可靠的方式。其他方法如 addslashes() 或手动过滤,要么失效、要么绕过成本低,不建议作为主力防御手段。
为什么不能只用 addslashes()?
它只对单引号、双引号、反斜杠和 NULL 字符做转义,但完全无法防御数字型注入(比如 id=1 OR 1=1),也不处理字符集差异导致的宽字节绕过(如 GBK 下的 %A1%27)。更关键的是,一旦数据被存储后再取出拼接(二次注入),addslashes() 就彻底失效。
- 它不区分上下文:字符串、数字、标识符都用同一套规则,而数据库引擎对这三类值的解析逻辑完全不同
- 现代 MySQL 默认使用
utf8mb4,但若连接层字符集未显式设置(如没调用mysqli_set_charset()),仍可能触发宽字节漏洞 - PHP 7.4+ 已废弃
mysql_*系列函数,而addslashes()在mysqli_real_escape_string()面前也属于降级方案
PDO::prepare() 和 mysqli_prepare() 怎么选?
两者都能隔离 SQL 结构与数据,但行为细节有差别。优先用 PDO,因它抽象层更稳定,错误模式统一;若项目已重度依赖 mysqli,则确保开启 MYSQLI_USE_PREPARE 模式并显式设置字符集。
-
PDO占位符支持命名(:username)和问号(?),后者更兼容旧代码迁移 -
mysqli只支持问号,且绑定参数必须用bind_param(),类型字符(s/i/d)写错会导致静默失败 - 二者都要求在 prepare 前完成连接初始化,且不能跨连接复用预处理语句对象
数字型参数必须用 intval() 或 filter_var($x, FILTER_VALIDATE_INT) 吗?
不是必须,但强烈建议。预处理语句本身能防注入,但业务逻辑常需要数值校验——比如用户 ID 为负数、超大整数或浮点数,可能引发非预期行为(如越权访问、数据库溢出、类型隐式转换漏洞)。
立即学习“PHP免费学习笔记(深入)”;
- 仅靠预处理不能阻止
WHERE id = ?中传入"1 OR 1=1"(字符串),因为mysqli会尝试类型转换,而PDO默认启用PDO::ATTR_EMULATE_PREPARES = true时也可能出问题 - 所以应在 bind 前做输入清洗:
$id = filter_var($_GET['id'], FILTER_VALIDATE_INT) ?: 0;,再传给预处理语句 - 对枚举类参数(如 status=active/inactive),应白名单校验而非依赖预处理
最容易被忽略的三个执行环节
修复常卡在“写了预处理但还是被绕过”,问题往往不在 prepare 本身,而在周边链条断裂:
- 忘记关闭自动提交或未检查
execute()返回值,导致错误被吞掉,掩盖了参数绑定失败 - 用
mysqli_query()执行 DDL(如CREATE TABLE)时仍拼接字符串——预处理不支持动态表名/列名,这类场景必须白名单校验后硬编码 - 日志记录中把原始
$_POST直接写进文件或数据库,即使业务层安全,也可能被日志注入利用(如 CRLF 注入伪造日志条目)



















