PHP PDO 预处理防 SQL 注入
时间:2026-11-14 07:27 作者:emer 分类: 无
PHP PDO 预处理防 SQL 注入
一、什么是 SQL 注入
用户提交的数据被拼进 SQL 语句,攻击者输入恶意片段篡改查询逻辑,例如登录处传入 ' OR '1'='1 直接绕过密码校验,甚至拖库、删表。根本原因是把用户输入当成 SQL 语法的一部分拼接。防御核心是让用户输入永远只作为数据,不参与语法解析,PDO 预处理正是解决这个问题。
二、预处理原理
预处理分两步:先把 SQL 模板发给数据库编译,占位符 ? 或 :name 位置已定;再单独把参数发给数据库,参数只当值传递,不会被解析成 SQL 命令。即使参数里带引号、注释、OR,也只是普通字符串。
三、正确写法:问号占位符
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=blog', 'root', '123456', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare('SELECT * FROM user WHERE username = ? AND password = ?');
$stmt->execute([$username, $password]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
prepare 只放固定 SQL 和占位符,用户变量全部放进 execute 数组,绝不手写拼接。
四、命名占位符
参数多时用命名占位符更清晰:
$stmt = $pdo->prepare(
'INSERT INTO article (title, content, uid) VALUES (:title, :content, :uid)'
);
$stmt->execute([
':title' => $title,
':content' => $content,
':uid' => $uid,
]);
五、查询结果与防止数值型注入
$stmt = $pdo->prepare('SELECT * FROM article WHERE gid = ?');
$stmt->bindValue(1, $gid, PDO::PARAM_INT); // 指定整数类型
$stmt->execute();
用 bindValue 并指定 PARAM_INT,即使传入字符串也会被强转,多一层保险。
六、哪些地方仍会出问题
- 动态表名、列名、ORDER BY 字段不能用占位符绑定,数据库不允许占位符代替标识符,这类要走白名单校验:
$allow = ['gid', 'title', 'created_at']; $order = in_array($sort, $allow) ? $sort : 'gid'; - 用字符串拼接 SQL 再预处理,等于白做;
- 仅对 WHERE 条件预处理,LIKE、IN 子句照样拼接;
- 关闭了 PDO 错误模式,注入发生时无法及时发现。
七、额外加固
- 输入层做类型与格式校验,最小权限数据库账号,不给库删权限;
- 开启 PDO ERRMODE_EXCEPTION,错误抛异常由全局捕获;
- 敏感操作配合参数白名单和二次确认。
八、常见误区
- 以为转义 addslashes 就够,特殊字符绕过仍可能注入;
- 占位符只绑定值,却把排序字段、表名直接拼接;
- 用 mysql_ 旧扩展,早已废弃不安全;
- 预处理只对一条语句,循环里每次重新 prepare 反而低效,应复用 stmt。
九、总结
PDO 预处理靠「先编译模板、再传参数」把用户输入和 SQL 语法隔离,是防注入的标准方案。值用占位符绑定,标识符走白名单,指定参数类型,配合异常模式和最小权限,就能从根源杜绝 SQL 注入。任何时候都不要把用户输入直接拼进 SQL 字符串。