PHP PDO 防止 SQL 注入最佳实践-PHP中文网

PDO防注入核心是全程使用预处理语句与参数绑定,禁用拼接;表名、字段名等无法参数化部分须白名单校验;配置ATTR\_EMULATE\_PREPARES=false、ERRMODE=EXCEPTION及utf8mb4字符集。

php pdo 防止 sql 注入最佳实践

使用 PDO 防止 SQL 注入,核心在于绝不拼接用户输入到 SQL 字符串中,全程依赖预处理语句(Prepared Statements)与参数绑定。只要正确使用 prepare() + execute()bindValue()/bindParam(),PDO 会自动对参数做类型安全的转义和隔离,从根本上阻断注入路径。

始终用预处理语句执行动态查询

所有含用户输入的查询(如 WHERE 条件、INSERT 值、ORDER BY 字段名除外)都必须走预处理流程。即使变量看起来“安全”,也不应例外。

  • ✅ 正确:用占位符(? 或命名参数 :name),再绑定值
  • ❌ 错误:用字符串拼接或 sprintf 插入变量

示例:

\`\`\`php
// ✅ 安全:位置占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = ? AND age > ?");
$stmt->execute(['active', 18]);

// ✅ 安全:命名参数
$stmt = $pdo->prepare("SELECT * FROM posts WHERE author\_id = :uid AND category = :cat");
$stmt->bindValue(':uid', $\_GET['id'], PDO::PARAM\_INT);
$stmt->bindValue(':cat', $\_GET['category'], PDO::PARAM\_STR);
$stmt->execute();
\`\`\`

慎处理无法参数化的部分(如表名、字段名、排序方向)

SQL 语法规定,表名、列名、ORDER BY 子句中的字段或 ASC/DESC 不允许用参数占位符。此时必须白名单校验,不可依赖过滤或转义。

立即学习“PHP免费学习笔记(深入)”;

下载

  • 定义允许的字段列表(如 ['id', 'title', 'created_at']),用 in_array() 严格比对
  • 排序方向只接受 'ASC''DESC',强制转换为大写并校验
  • 禁止将任何用户输入直接代入 SQL 字符串拼接

示例:

\`\`\`php
$allowed\_columns = ['name', 'email', 'created\_at'];
$sort\_column = $\_GET['sort'] ?? 'created\_at';
$sort\_order = strtoupper($\_GET['order'] ?? 'ASC');

if (!in\_array($sort\_column, $allowed\_columns)) {
  throw new InvalidArgumentException('Invalid sort column');
}
if (!in\_array($sort\_order, ['ASC', 'DESC'])) {
  throw new InvalidArgumentException('Invalid sort order');
}

$sql = "SELECT \* FROM users ORDER BY {$sort\_column} {$sort\_order}";
$stmt = $pdo->query($sql); // 注意:这里没参数,但字段和方向已白名单控制
\`\`\`

设置正确的 PDO 属性与错误模式

默认情况下 PDO 可能静默失败或返回不安全的错误信息。需主动配置以增强安全性与可观测性。

  • 禁用模拟预处理:PDO::ATTR_EMULATE_PREPARES => false,确保数据库原生支持预处理,避免绕过
  • 开启异常模式:PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,便于捕获和处理错误,防止敏感信息泄露
  • 设置默认字符集(如 utf8mb4),避免因编码不一致导致的绕过场景

连接示例:

\`\`\`php
$options = [
  PDO::ATTR\_ERRMODE => PDO::ERRMODE\_EXCEPTION,
  PDO::ATTR\_EMULATE\_PREPARES => false,
  PDO::MYSQL\_ATTR\_INIT\_COMMAND => "SET NAMES utf8mb4"
];
$pdo = new PDO($dsn, $user, $pass, $options);
\`\`\`

其他关键细节

补充几个容易被忽略但影响安全性的点:

  • 对整数型参数,显式使用 PDO::PARAM_INT 绑定,避免字符串被当作表达式解析(如 '1 OR 1=1'
  • 批量插入时,用单条预处理语句循环 execute(),不要拼接多值 VALUES (?,?),(?,?) 后再绑定(除非确认所有参数类型一致且可控)
  • 避免在 execute() 数组中混用未过滤的原始输入;优先用 bindValue() 分步控制类型
  • 关闭 PDO::ATTR_STRINGIFY_FETCHES(保持数值类型原样),减少类型隐式转换风险

已有 80 条评论

    1. Nina Liu Nina Liu

      看完后立即去检查了自己的项目代码,发现好几个地方都需要改进,这篇文章太有价值了。

    2. Kevin Anderson Kevin Anderson

      总结得非常好,特别是那句"绝不拼接用户输入到SQL字符串",应该成为每个PHP开发者的座右铭。

    3. Sarah Chen Sarah Chen

      Excellent best practices guide. The code examples are clear and easy to follow.

    4. David Wilson David Wilson

      短小精悍,干货满满。建议再加上一些实际攻击案例的演示,这样更能说服开发者重视安全问题。

    5. Jessica Lee Jessica Lee

      之前遇到过一次SQL注入漏洞,就是因为没有对ORDER BY字段做白名单校验,这篇文章简直就是救星。