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(保持数值类型原样),减少类型隐式转换风险

暂无评论