预处理语句能彻底防御UPDATE/DELETE注入,因数据库先编译固定SQL模板,再将用户输入仅作为纯值绑定,不参与语法解析;动态标识符(如表名、ORDER BY字段)须白名单校验,且PDO需禁用模拟预处理、MySQLi须严格匹配bind_param类型。

直接用预处理语句绑定参数,就能让恶意修改操作彻底失效——数据库只把输入当“值”,不把它当“代码”。
为什么 prepare() + execute() 能拦住 UPDATE/DELETE 注入
攻击者想篡改数据,典型手法是往表单里填:' OR '1'='1 或 '; DROP TABLE users; --。如果代码是拼接 SQL,这些字符串会直接混进语句结构里,变成合法的语法片段。
而预处理语句分两步走:先由数据库解析并编译 SQL 模板(比如 "UPDATE users SET email = ? WHERE id = ?"),此时语法已固定;再把用户输入作为纯数据“塞进去”,数据库底层根本不允许它影响执行逻辑。
常见错误现象:
- 用了
prepare()但没调execute(),或漏掉bind_param(),结果还是拼接执行 - 在同一个
prepare()语句里,对不同字段混用类型(比如把 ID 当字符串传,却用"s"绑定),导致类型隐式转换失败或绕过校验
PDO::prepare() 和 mysqli_prepare() 的关键区别
两者都能防注入,但底层行为有差异,选错可能埋坑:
-
PDO默认开启模拟预处理(PDO::ATTR_EMULATE_PREPARES = true),此时占位符其实是 PHP 层转义后拼接的,并非真正交给 MySQL 预编译——必须显式关掉:$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) -
MySQLi面向对象方式中,bind_param()的第一个参数是类型字符串,顺序必须和?一一对应,且"i"(整数)、"s"(字符串)、"d"(浮点)不能错位,否则绑定失败或值被截断 - 命名参数(如
:email)只在PDO中原生支持;MySQLi只认?,强行用命名会报错
UPDATE/DELETE 场景下最容易忽略的动态部分
WHERE 条件、SET 字段名、LIMIT 偏移量这些地方没法用 ? 占位——数据库语法不允许。硬拼进去就等于开后门。
正确做法只有白名单校验:
- 字段名(如排序字段):定义
$allowed_fields = ['name', 'status', 'updated_at'];,再用in_array($field, $allowed_fields, true)判定 - 操作类型(如软删标记):只允许
$action = $_POST['action'] ?? 'update';是'update'或'disable',其他一律拒收 - LIMIT 数值:必须是整数且非负,用
filter_var($limit, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])校验,别信(int)$limit强转——它会把"10; DROP TABLE logs"变成10,但拼接时已经晚了
绑定参数后仍被绕过的三个真实原因
不是预处理失效,而是开发时没守住边界:
- WHERE 子句里用了
IN (?)想批量查,但?只能代一个值,传数组会变成字符串拼接——得用str_repeat('?,', count($ids) - 1) . '?'动态生成占位符,再逐个绑定 - ORDER BY 后直接拼
$_GET['sort'],哪怕前面所有 UPDATE 都用了预处理,这里照样被注入 - 事务里混合使用预处理和拼接语句,只要有一处松动,整个事务的原子性保护就形同虚设
最常被忽略的是:动态 SQL 片段永远不该出现在 prepare 的字符串里,哪怕只有一行。











