PHP项目如何防止SQL注入?

wen PHP项目 18

本文目录导读:

PHP项目如何防止SQL注入?

  1. 使用 PDO 或 MySQLi 的预处理语句(最佳实践)
  2. 永远不要手动转义(贬义做法)
  3. 输入验证与白名单(补充措施)
  4. 防范 ORDER BY、LIMIT 等特殊场景
  5. 其他安全措施(防御纵深)
  6. 关键原则

在PHP项目中防止SQL注入,最有效、最推荐的方法是使用预处理语句(Prepared Statements),它可以确保SQL语句的结构不会被用户输入的数据改变。

以下是几种核心的防护方法,按推荐程度从高到低排列:

使用 PDO 或 MySQLi 的预处理语句(最佳实践)

预处理语句将SQL模板与数据分开,即使数据中包含恶意SQL代码,它也只会被当作字符串处理,而不会改变SQL的逻辑。

方法 A:PDO(推荐,支持多种数据库)

<?php
// 1. 创建 PDO 连接(设置错误模式和模拟预处理关闭)
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$pdo = new PDO($dsn, 'username', 'password', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false, // 关闭模拟预处理,使用真实的数据库预处理
]);
// 2. 准备SQL语句(使用 ? 占位符 或 :name 命名占位符)
$sql = "SELECT * FROM users WHERE username = :username AND password = :password";
$stmt = $pdo->prepare($sql);
// 3. 绑定参数并执行
$stmt->execute([
    ':username' => $_POST['username'],
    ':password' => hash('sha256', $_POST['password']), // 示例:密码不要明文存储
]);
// 4. 获取结果
$user = $stmt->fetch();
?>

关键点

  • PDO::ATTR_EMULATE_PREPARES => false:让 PHP 使用数据库真实的预处理功能,而不是模拟,这能更彻底地防止注入。
  • 使用 或 name 占位符:不要手动拼接变量。

方法 B:MySQLi(面向对象风格)

<?php
// 1. 创建 MySQLi 连接
$conn = new mysqli('localhost', 'username', 'password', 'test');
// 检查连接
if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
}
// 2. 准备SQL语句
$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $conn->prepare($sql);
// 3. 绑定参数(s = 字符串,i = 整数,d = 双精度,b = 二进制)
$username = $_POST['username'];
$stmt->bind_param("s", $username);
// 4. 执行
$stmt->execute();
// 5. 获取结果
$result = $stmt->get_result();
$user = $result->fetch_assoc();
// 6. 关闭连接
$stmt->close();
$conn->close();
?>

永远不要手动转义(贬义做法)

不推荐的做法是使用 mysqli_real_escape_string()

  • 原因:它容易出错,需要正确设置数据库连接编码(如 set_charset('utf8')),且容易忘记处理某些字符,一旦忘记使用,就不安全。
  • 放弃 addslashes()mysqli_real_escape_string(),改用预处理语句。

输入验证与白名单(补充措施)

对于数值类型固定选项,验证数据类型可以增加一层防护。

// 示例:确保 ID 是整数
$id = (int)$_GET['id']; // 强制转换为整数,去掉所有非数字字符
// 示例:确保排序字段在白名单中
$allowedSort = ['name', 'date', 'id'];
$sort = $_GET['sort'] ?? 'id';
if (!in_array($sort, $allowedSort)) {
    $sort = 'id'; // 默认值,防止 SQL 注入
}

注意:验证不能替代预处理语句,只能作为额外保险。

防范 ORDER BY、LIMIT 等特殊场景

有些SQL子句(如 ORDER BYLIMIT、表名、列名)不能使用预处理语句绑定参数,此时需要严格的白名单验证:

$direction = strtoupper($_GET['dir']); // ASC 或 DESC
if (!in_array($direction, ['ASC', 'DESC'])) {
    $direction = 'ASC';
}
$sql = "SELECT * FROM products ORDER BY price $direction";

其他安全措施(防御纵深)

  • 最小权限原则:数据库用户只授予必要的权限,应用用户通常只要 SELECTINSERTUPDATEDELETE,不需要 DROPCREATE
  • 隐藏错误信息:生产环境关闭 display_errors,避免 SQL 报错信息被攻击者利用。
  • 使用 ORM:成熟的 ORM(如 Doctrine、Eloquent)多数情况下会自动使用预处理语句,但如果使用 raw 查询方法(如 DB::raw()),风险仍在,仍需手动处理。

关键原则

做法 是否推荐 说明
PDO 预处理语句 强烈推荐 最佳实践,兼容多数据库
MySQLi 预处理语句 ✅ 推荐 只用于 MySQL
mysqli_real_escape_string() ❌ 不推荐 容易遗漏,有编码风险
addslashes() ❌ 禁止 完全不可靠
输入验证(类型、白名单) ✅ 补充使用 不能替代预处理,但可以增加安全性

一句话总结:凡是SQL查询中出现用户输入的地方(包括GET参数、POST数据、Cookie、文件上传名等),都应当使用预处理语句插入数据。

抱歉,评论功能暂时关闭!