SQL 注入(SQL Injection)是一种常见的 Web 安险漏洞,攻击者通过恶意构造 SQL 查询语句,从而绕过身份验证、读取、修改或删除数据库中的数据,甚至可以执行系统命令。

在 PHP 中防止 SQL 注入有很多有效的方法。以下是 主要的防止 SQL 注入的技术与策略


一、使用 预处理语句(Prepared Statements)

推荐方法:

使用 PDOMySQLi 提供的 预处理语句,它可以确保 SQL 查询和用户输入的内容是分离的,极大地减少了 SQL 注入的风险。

1️⃣ PDO 示例(推荐)

步骤:

  • 使用 prepare() 方法准备 SQL 语句。
  • 使用 bindParam()bindValue() 方法绑定参数,避免用户输入直接进入 SQL 查询中。

示例:

<?php
try {
    // 创建 PDO 实例,连接到 MySQL 数据库
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    // 使用预处理语句
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
    
    // 绑定用户输入的参数
    $stmt->bindParam(':username', $username, PDO::PARAM_STR);
    $stmt->bindParam(':password', $password, PDO::PARAM_STR);
    
    // 设置实际的输入值
    $username = $_POST['username'];
    $password = $_POST['password'];
    
    // 执行 SQL 查询
    $stmt->execute();
    
    // 获取查询结果
    $result = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if ($result) {
        echo "Login successful!";
    } else {
        echo "Invalid username or password!";
    }
} catch (PDOException $e) {
    echo "Error: " . $e->getMessage();
}
?>

解释:

  • prepare() 方法将 SQL 查询语句和用户输入分开,避免 SQL 注入。
  • 使用 bindParam()bindValue() 将参数绑定到查询中,PDO 会自动处理转义和类型安全。

2️⃣ MySQLi 示例(支持面向对象和过程化风格)

<?php
// 使用 MySQLi 连接数据库
$mysqli = new mysqli("localhost", "root", "", "test");

// 检查连接是否成功
if ($mysqli->connect_error) {
    die("Connection failed: " . $mysqli->connect_error);
}

// 使用预处理语句
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);

// 获取用户输入的参数
$username = $_POST['username'];
$password = $_POST['password'];

// 执行查询
$stmt->execute();

// 获取结果
$result = $stmt->get_result();

if ($result->num_rows > 0) {
    echo "Login successful!";
} else {
    echo "Invalid username or password!";
}

// 关闭连接
$stmt->close();
$mysqli->close();
?>

解释:

  • 通过 prepare()bind_param(),MySQLi 也确保用户输入与 SQL 语句分离,防止了 SQL 注入。

二、使用 参数化查询(Parameterized Queries)

通过将用户输入作为参数而不是直接拼接在 SQL 语句中,可以避免 SQL 注入攻击。

示例:

<?php
// 使用 MySQLi 执行参数化查询
$query = "SELECT * FROM users WHERE username = :username AND password = :password";
$stmt = $db->prepare($query);

$stmt->bindParam(':username', $_POST['username']);
$stmt->bindParam(':password', $_POST['password']);

$stmt->execute();
?>

解释:

  • :username:password 是占位符,用户输入将通过 bindParam() 方法安全绑定。

三、避免直接拼接 SQL 语句

在没有预处理语句的情况下,避免直接拼接用户输入到 SQL 语句中。直接拼接可能会导致 SQL 注入漏洞。

错误的示例:

<?php
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($db, $query);
?>

解释:

  • 直接将 $username$password 拼接到 SQL 语句中,攻击者可以通过特殊字符(例如 ')修改查询语句。

安全的做法:

使用 prepare()bindParam()bindValue() 来避免拼接用户输入。


四、使用 数据库权限最小化

在 PHP 与数据库交互时,确保使用的数据库账户 权限最小化。即数据库用户只应该有执行查询的必要权限,避免授予删除、更新等高风险权限。

步骤:

  • 不要使用 root 用户进行 Web 应用访问。
  • 创建专门的数据库用户,限制其仅能读取数据,执行查询等操作。
  • 定期检查并更新数据库用户的权限。

五、避免使用 危险的 SQL 函数

某些 SQL 函数可能会带来安全风险,避免在用户输入的地方使用它们:

  • mysql_query()mysql_* 系列函数(已废弃)
  • eval()(不要动态执行 SQL 语句)

通过 PDOMySQLi 等现代数据库库,避免使用这些不安全的函数。


六、数据验证与过滤

除了 SQL 注入,还要对用户输入进行其他形式的验证和过滤:

1. 验证输入格式

<?php
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "Invalid email format!";
}
?>

2. 使用正则表达式

例如,限制用户名只能包含字母和数字:

<?php
if (!preg_match("/^[a-zA-Z0-9]*$/", $username)) {
    echo "Invalid username!";
}
?>


七、定期更新数据库和应用程序

定期更新 PHP 和数据库驱动,以确保应用程序中没有已知的漏洞。保持系统更新,以防止攻击者利用已知的 SQL 注入漏洞。


八、总结

防范方法描述
预处理语句使用 PDO 或 MySQLi 的预处理语句,避免 SQL 拼接。
参数化查询使用占位符和绑定参数的方法,确保输入安全。
避免拼接 SQL永远不要直接拼接用户输入到 SQL 语句中。
最小权限原则限制数据库用户的权限,避免过高的权限操作。
输入验证对所有用户输入进行格式验证和过滤,确保数据合法。
定期更新保持数据库和 PHP 环境更新,避免已知的安全漏洞。

防止 SQL 注入的关键是: 永远不要直接将用户输入放入 SQL 查询语句中。使用 预处理语句参数化查询 是最有效的防护手段。