«

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,即使传入字符串也会被强转,多一层保险。

六、哪些地方仍会出问题

  1. 动态表名、列名、ORDER BY 字段不能用占位符绑定,数据库不允许占位符代替标识符,这类要走白名单校验:
    $allow = ['gid', 'title', 'created_at'];
    $order = in_array($sort, $allow) ? $sort : 'gid';
  2. 用字符串拼接 SQL 再预处理,等于白做;
  3. 仅对 WHERE 条件预处理,LIKE、IN 子句照样拼接;
  4. 关闭了 PDO 错误模式,注入发生时无法及时发现。

七、额外加固

八、常见误区

  1. 以为转义 addslashes 就够,特殊字符绕过仍可能注入;
  2. 占位符只绑定值,却把排序字段、表名直接拼接;
  3. 用 mysql_ 旧扩展,早已废弃不安全;
  4. 预处理只对一条语句,循环里每次重新 prepare 反而低效,应复用 stmt。

九、总结

PDO 预处理靠「先编译模板、再传参数」把用户输入和 SQL 语法隔离,是防注入的标准方案。值用占位符绑定,标识符走白名单,指定参数类型,配合异常模式和最小权限,就能从根源杜绝 SQL 注入。任何时候都不要把用户输入直接拼进 SQL 字符串。

标签: PHP SQL注入 PDO Web安全