Skip to content

防止 SQL 注入的最佳实践

SQL 注入是最常见的 Web 安全漏洞之一。本文介绍 PHP 中防止 SQL 注入的几种方法。

使用 PDO 预处理语句(推荐)

PDO 的预处理语句是防止 SQL 注入最安全的方式:

php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');

// 命名参数
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id AND status = :status");
$stmt->execute([':id' => $userId, ':status' => $status]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 位置参数
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$name, $email]);

使用 MySQLi 预处理语句

php
$mysqli = new mysqli('localhost', 'user', 'password', 'test');

$stmt = $mysqli->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param('s', $email); // 's' 表示字符串
$stmt->execute();
$result = $stmt->get_result();

危险做法(不要这样做)

php
// ❌ 直接拼接 SQL - 极易受到 SQL 注入攻击
$query = "SELECT * FROM users WHERE id = '$userId'";

// ❌ 仅使用 addslashes() - 不够安全
$query = "SELECT * FROM users WHERE name = '" . addslashes($name) . "'";

// ❌ 依赖 magic_quotes(已废弃)

IN 子句的参数化处理

IN 子句需要特殊处理:

php
function buildInClause($pdo, $column, array $values) {
    $placeholders = implode(',', array_fill(0, count($values), '?'));
    $sql = "$column IN ($placeholders)";
    return ['sql' => $sql, 'values' => $values];
}

// 使用示例
$ids = [1, 2, 3, 4, 5];
$inClause = buildInClause($pdo, 'id', $ids);
$stmt = $pdo->prepare("SELECT * FROM users WHERE {$inClause['sql']}");
$stmt->execute($inClause['values']);

LIKE 查询的安全处理

php
$search = '%' . $keyword . '%';
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->execute([':search' => $search]);

注意事项

  1. 永远不要 信任用户输入,包括 $_GET, $_POST, $_COOKIE
  2. 预处理语句不仅对安全有益,还能提高查询性能(查询缓存)
  3. 对于动态表名、列名不能使用预处理,需要白名单校验:
php
$allowedTables = ['users', 'posts', 'comments'];
if (!in_array($table, $allowedTables)) {
    throw new Exception('Invalid table name');
}

Binstork