本文目录导读:

在 PHP 中防御 SQL 注入的核心原则是:绝不信任用户输入,始终使用参数化查询(预处理语句)。
以下是 PHP 防御 SQL 注入的几种标准方法,按推荐程度从高到低排列:
最佳实践:使用 PDO 预处理语句
PDO(PHP Data Objects)提供了一个统一的接口来访问多种数据库,并且天然支持预处理语句,能有效防止 SQL 注入。
<?php
// 1. 创建 PDO 连接
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$username = 'root';
$password = 'password';
try {
$pdo = new PDO($dsn, $username, $password);
// 设置错误模式为异常,便于调试
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('数据库连接失败:' . $e->getMessage());
}
// 2. 使用预处理语句(推荐)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');
$stmt->execute([
':email' => $_POST['email'],
':status' => 'active'
]);
$user = $stmt->fetch();
// 或者使用问号占位符
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$_POST['email'], 'active']);
$user = $stmt->fetch();
?>
为什么有效?
预处理语句将 SQL 逻辑与数据分离,数据库引擎先编译 SQL 模板(SELECT ... WHERE email = ?),然后再将用户输入作为纯数据绑定进去,即使用户输入包含 ' OR 1=1 --,它也不会被解析为 SQL 命令,而只是字符串值。
次选方案:使用 MySQLi 预处理语句
如果你必须使用 MySQLi,同样支持预处理语句。
<?php
$mysqli = new mysqli('localhost', 'root', 'password', 'testdb');
// 检查连接
if ($mysqli->connect_error) {
die('连接失败:' . $mysqli->connect_error);
}
// 预处理语句
$stmt = $mysqli->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->bind_param('ss', $name, $email); // s=string, i=integer, d=double, b=blob
$name = $_POST['name'];
$email = $_POST['email'];
$stmt->execute();
echo "插入成功,ID: " . $stmt->insert_id;
$stmt->close();
?>
极不推荐:mysql_real_escape_string()
在旧的 mysql_* 函数中(已废弃),有人用 mysql_real_escape_string() 转义特殊字符,这种方法存在以下问题:
- 容易忘记对某个输入进行转义
- 编码设置不正确时仍可能被绕过
- 无法防御某些复杂的注入场景(如宽字节注入)
// 已废弃,不推荐使用 $unsafe_input = mysql_real_escape_string($_POST['input']); $sql = "SELECT * FROM users WHERE name = '$unsafe_input'";
你不需要使用这种方法,除非你在维护一个无法升级的 PHP 5.x 旧项目。
其他重要防御措施
最小权限原则
- 为数据库连接创建专用的低权限用户,只赋予它必需的权限(如 SELECT、INSERT,避免 DROP、ALTER)。
永远不要直接拼接 SQL
// 危险!这是典型的 SQL 注入 $sql = "SELECT * FROM users WHERE id = " . $_GET['id']; $result = mysqli_query($conn, $sql);
严格验证数据格式(输入白名单)
对于特定的数据类型(如整数、邮箱、枚举值),在存入数据库前先验证。
// ID 必须是整数
if (!ctype_digit($_GET['id'])) {
die('无效的 ID');
}
// 或者使用 filter_var
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if ($email === false) {
die('无效的邮箱');
}
禁止直接显示数据库错误
在生产环境中,关闭 PHP 的 display_errors,并将错误记录到日志中,防止攻击者通过错误信息推断数据库结构。
使用 ORM(对象关系映射)框架
使用成熟的 ORM 如 Eloquent(Laravel)、Doctrine、RedBeanPHP,它们底层已经封装好了预处理语句,能大大降低人为犯错的风险。
// Laravel Eloquent 示例
$user = User::where('email', $request->input('email'))->first();
// 底层已自动使用参数绑定
你现在应该怎么做?
| 场景 | 推荐做法 |
|---|---|
| 新项目 | 使用 PDO 预处理语句,配合框架(Laravel/Symfony)自带 ORM |
| 现有项目(PHP 7+) | 将原生的 mysql_query 迁移到 PDO 或 MySQLi |
| 只需查询少量数据 | 使用 PDO + 命名参数(name 形式) |
| 涉及批量插入/更新 | 继续使用预处理语句,循环绑定参数 |
一句话:从今天开始,在 PHP 中写 SQL 时,一律使用预处理语句。