Skip to content

12.7 防止 SQL 注入 ​

什么是 SQL 注入? ​

SQL 注入是一种常见的网络安全漏洞,攻击者通过在用户输入中插入恶意 SQL 代码,从而操纵数据库操作。

攻击示例 ​

1. 登录绕过 ​

正常登录 SQL:

sql
SELECT * FROM users WHERE username = 'admin' AND password = 'password123';

攻击输入:

  • 用户名:admin' --
  • 密码:任意值

实际执行的 SQL:

sql
SELECT * FROM users WHERE username = 'admin' --' AND password = '任意值';

2. 数据窃取 ​

正常查询 SQL:

sql
SELECT * FROM products WHERE id = 1;

攻击输入:

  • ID:1' UNION SELECT username, password FROM users --

实际执行的 SQL:

sql
SELECT * FROM products WHERE id = 1' UNION SELECT username, password FROM users --';

3. 数据库破坏 ​

攻击输入:

  • ID:1'; DROP TABLE users --

实际执行的 SQL:

sql
SELECT * FROM products WHERE id = 1'; DROP TABLE users --';

防止 SQL 注入的方法 ​

1. 使用预处理语句 ​

预处理语句是防止 SQL 注入的最有效方法,它将 SQL 语句与数据分离。

示例:用户登录 ​

php
<?php
require_once 'db.php';
$conn = getDbConnection();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'];
    $password = $_POST['password'];
    
    // 使用预处理语句
    $stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
    $stmt->bind_param("ss", $username, $password);
    $stmt->execute();
    $result = $stmt->get_result();
    
    if ($result->num_rows > 0) {
        echo "登录成功";
    } else {
        echo "用户名或密码错误";
    }
    
    $stmt->close();
}

$conn->close();
?>

2. 使用 mysqli_real_escape_string ​

对于不使用预处理语句的情况,可以使用 mysqli_real_escape_string 函数转义特殊字符。

示例:查询产品 ​

php
<?php
require_once 'db.php';
$conn = getDbConnection();

if (isset($_GET['id'])) {
    // 转义特殊字符
    $id = mysqli_real_escape_string($conn, $_GET['id']);
    
    $sql = "SELECT * FROM products WHERE id = '$id'";
    $result = $conn->query($sql);
    
    if ($result->num_rows > 0) {
        $product = $result->fetch_assoc();
        echo "产品名称: " . $product['name'];
    } else {
        echo "产品不存在";
    }
    
    $result->free();
}

$conn->close();
?>

3. 输入验证 ​

对用户输入进行验证,确保输入符合预期格式。

示例:验证数字输入 ​

php
<?php
require_once 'db.php';
$conn = getDbConnection();

if (isset($_GET['id'])) {
    // 验证是否为数字
    if (is_numeric($_GET['id'])) {
        $id = (int)$_GET['id'];
        
        $sql = "SELECT * FROM products WHERE id = $id";
        $result = $conn->query($sql);
        
        if ($result->num_rows > 0) {
            $product = $result->fetch_assoc();
            echo "产品名称: " . $product['name'];
        } else {
            echo "产品不存在";
        }
        
        $result->free();
    } else {
        echo "无效的 ID";
    }
}

$conn->close();
?>

4. 最小权限原则 ​

为数据库用户分配最小必要的权限,避免使用 root 账户。

示例:创建受限用户 ​

sql
-- 创建只具有 SELECT 权限的用户
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON database_name.* TO 'app_user'@'localhost';

-- 创建具有 SELECT、INSERT、UPDATE 权限的用户
CREATE USER 'app_writer'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE ON database_name.* TO 'app_writer'@'localhost';

5. 转义输出 ​

在输出数据时,使用 htmlspecialchars 函数转义特殊字符,防止 XSS 攻击。

示例:输出用户数据 ​

php
<?php
require_once 'db.php';
$conn = getDbConnection();

$sql = "SELECT * FROM users";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    while ($row = $result->fetch_assoc()) {
        // 转义输出
        echo "用户名: " . htmlspecialchars($row['username']) . "<br>";
        echo "邮箱: " . htmlspecialchars($row['email']) . "<br>";
    }
}

$result->free();
$conn->close();
?>

最佳实践 ​

1. 始终使用预处理语句 ​

对于所有包含用户输入的 SQL 语句,都应该使用预处理语句。

2. 验证所有用户输入 ​

对所有用户输入进行验证,确保输入符合预期格式。

3. 使用参数绑定 ​

使用参数绑定而不是字符串拼接来构建 SQL 语句。

4. 限制数据库用户权限 ​

为数据库用户分配最小必要的权限。

5. 转义输出 ​

在输出数据时,使用 htmlspecialchars 函数转义特殊字符。

6. 定期更新数据库软件 ​

保持数据库软件的最新版本,修复已知的安全漏洞。

7. 使用 ORM 框架 ​

考虑使用 ORM(对象关系映射)框架,如 Laravel 的 Eloquent、CodeIgniter 的 Active Record 等,它们内置了防止 SQL 注入的机制。

常见错误示例 ​

错误:直接拼接 SQL ​

php
// 错误!容易受到 SQL 注入攻击
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = $conn->query($sql);

正确:使用预处理语句 ​

php
// 正确!防止 SQL 注入攻击
$username = $_POST['username'];
$password = $_POST['password'];
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();

错误:不验证输入 ​

php
// 错误!没有验证输入
$id = $_GET['id'];
$sql = "SELECT * FROM products WHERE id = $id";
$result = $conn->query($sql);

正确:验证输入 ​

php
// 正确!验证输入
if (is_numeric($_GET['id'])) {
    $id = (int)$_GET['id'];
    $sql = "SELECT * FROM products WHERE id = $id";
    $result = $conn->query($sql);
} else {
    echo "无效的 ID";
}

练习 ​

  1. 识别并修复以下代码中的 SQL 注入漏洞:

    php
    $name = $_GET['name'];
    $sql = "SELECT * FROM users WHERE name = '$name'";
    $result = $conn->query($sql);
  2. 使用预处理语句重写以下代码:

    php
    $username = $_POST['username'];
    $email = $_POST['email'];
    $sql = "INSERT INTO users (username, email) VALUES ('$username', '$email')";
    $conn->query($sql);
  3. 实现一个安全的用户登录系统,防止 SQL 注入攻击。

  4. 为数据库创建一个受限权限的用户,并在应用中使用该用户。

© 2026 编程马·菜鸟教程 版权所有