返回

如何防止 PHP 中的 SQL 注入:终极指南

php

如何防止 PHP 中的 SQL 注入

SQL 注入是一种常见的 Web 安全漏洞,攻击者通过在用户输入中插入恶意的 SQL 代码,从而操控后台数据库。这可能导致数据泄露、数据篡改甚至服务器被完全控制。 理解 SQL 注入的原理并采取有效的预防措施至关重要。

使用预处理语句(Prepared Statements)

预处理语句是防止 SQL 注入最有效的方法。 它的原理是将 SQL 查询语句进行预编译, 将用户输入的数据视为参数,而不是 SQL 代码的一部分。这样,即使输入中包含恶意 SQL 代码,数据库也不会将其执行,而仅仅作为普通数据处理。

预处理语句的工作流程:

  1. 预编译: 数据库接收到 SQL 语句模板后进行编译,但不包含具体的参数值。
  2. 参数绑定: 将用户输入的数据作为参数绑定到预编译的 SQL 语句模板中。
  3. 执行: 数据库使用绑定好的参数执行预编译的语句。

PHP 中使用 PDO(PHP Data Objects) 扩展实现预处理语句:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 预处理语句
    $stmt = $conn->prepare("INSERT INTO users (username, email) VALUES (:username, :email)");

    // 绑定参数
    $stmt->bindParam(':username', $username_input);
    $stmt->bindParam(':email', $email_input);

    // 获取用户输入
    $username_input = $_POST['username'];
    $email_input = $_POST['email'];

    // 执行语句
    $stmt->execute();

    echo "新用户注册成功";

} catch(PDOException $e) {
    echo "Error: " . $e->getMessage();
}

$conn = null;
?>

操作步骤:

  1. 建立数据库连接。
  2. 使用 $conn->prepare() 方法准备 SQL 语句模板,并将参数位置使用命名占位符(如 :username)或问号占位符 (?) 标记。
  3. 使用 $stmt->bindParam() 方法将用户输入的数据绑定到对应的占位符。第一个参数是占位符名称,第二个参数是变量名,第三个参数可以指定数据类型(可选)。
  4. 执行预处理语句 $stmt->execute()

使用 mysqli 扩展实现预处理语句:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);

// 检测连接
if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
}

// 预处理及绑定
$stmt = $conn->prepare("INSERT INTO users (username, email) VALUES (?, ?)");
$stmt->bind_param("ss", $username, $email);

// 获取值并执行
$username = $_POST['username'];
$email = $_POST['email'];
$stmt->execute();

echo "新用户注册成功";

$stmt->close();
$conn->close();
?>

操作步骤:

  1. 建立数据库连接。
  2. 使用 $conn->prepare() 方法准备 SQL 语句模板,并将参数位置使用问号占位符 (?) 标记。
  3. 使用 $stmt->bind_param() 方法将用户输入的数据绑定到对应的占位符。第一个参数是字符串,包含每个参数的数据类型指示符 (例如:"s" 代表字符串, "i" 代表整数, "d" 代表双精度浮点数,"b" 代表 blob)。 随后是需要绑定的变量。
  4. 获取用户输入。
  5. 执行预处理语句 $stmt->execute()
  6. 关闭预处理语句和数据库连接。

转义用户输入

另一种防止 SQL 注入的方法是对用户输入进行转义。转义的原理是将用户输入中的特殊字符(如单引号、双引号等)进行处理,使其失去原有的意义,从而防止数据库将其误认为是 SQL 代码的一部分。

PHP 中提供了多种转义函数:

  • mysqli_real_escape_string(): 转义 SQL 语句中使用的特殊字符, 需要一个数据库连接作为第一个参数。
  • PDO::quote(): 转义字符串以便安全地用于 SQL 语句,适用于 PDO 扩展。
  • htmlspecialchars(): 将特殊字符转换为 HTML 实体,主要用于防止 XSS 攻击, 但在某些情况下也可以用于防止 SQL 注入,例如将用户输入显示在 HTML 表单中时。

使用 mysqli_real_escape_string() 函数转义用户输入:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

$conn = mysqli_connect($servername, $username, $password, $dbname);

if (!$conn) {
    die("连接失败: " . mysqli_connect_error());
}

$username_input = mysqli_real_escape_string($conn, $_POST['username']);
$email_input = mysqli_real_escape_string($conn, $_POST['email']);

$sql = "INSERT INTO users (username, email) VALUES ('$username_input', '$email_input')";

if (mysqli_query($conn, $sql)) {
    echo "新用户注册成功";
} else {
    echo "Error: " . $sql . "<br>" . mysqli_error($conn);
}

mysqli_close($conn);
?>

操作步骤:

  1. 建立数据库连接。
  2. 使用 mysqli_real_escape_string() 函数对用户输入数据进行转义。该函数接受数据库连接和待转义的字符串作为参数。
  3. 将转义后的字符串拼接到 SQL 查询语句中。
  4. 执行 SQL 查询。

使用PDO::quote() 转义用户输入:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $username_input = $_POST['username'];
    $email_input = $_POST['email'];

    //使用PDO::quote()进行转义
    $safe_username = $conn->quote($username_input);
    $safe_email = $conn->quote($email_input);

    $sql = "INSERT INTO users (username, email) VALUES ($safe_username, $safe_email)";
    $conn->exec($sql);
    echo "新用户注册成功";

} catch(PDOException $e) {
    echo "Error: " . $e->getMessage();
}

$conn = null;
?>

操作步骤:

  1. 建立数据库连接。
  2. 使用 PDO::quote() 方法对用户输入的数据进行转义。
  3. 将转义后的字符串拼接到 SQL 查询语句中。
  4. 执行SQL查询。

需要注意,虽然转义可以一定程度上防止 SQL 注入, 但其安全性不如预处理语句。 预处理语句从根本上区分了代码和数据, 而转义只是对特殊字符进行替换。 在某些情况下,如果转义不完全或者使用了错误的转义函数,仍然可能存在 SQL 注入的风险。 所以优先推荐使用预处理语句。

输入验证和过滤

除了预处理语句和转义用户输入外,对用户输入进行验证和过滤也是防止 SQL 注入的重要措施。

输入验证: 检查用户输入的数据是否符合预期的数据类型、格式和长度。 例如,如果期望用户输入一个整数, 则可以验证输入是否为数字, 并且在合理的范围内。

输入过滤: 移除或替换用户输入中的非法字符或恶意代码。例如,可以过滤掉 HTML 标签、JavaScript 代码等。

PHP 提供了一系列函数用于输入验证和过滤:

  • is_numeric(): 检查变量是否为数字。
  • intval(): 将变量转换为整数。
  • filter_var(): 使用指定的过滤器过滤变量。
  • preg_match(): 使用正则表达式进行匹配。

示例:

<?php

$username_input = $_POST['username'];
$email_input = $_POST['email'];

//验证用户名是否为字母和数字,且长度在3到20之间
if (!preg_match('/^[a-zA-Z0-9]{3,20}$/', $username_input)) {
   exit