SQL 注入(SQL Injection)是一种常见的 Web 安险漏洞,攻击者通过恶意构造 SQL 查询语句,从而绕过身份验证、读取、修改或删除数据库中的数据,甚至可以执行系统命令。
在 PHP 中防止 SQL 注入有很多有效的方法。以下是 主要的防止 SQL 注入的技术与策略。
一、使用 预处理语句(Prepared Statements)
推荐方法:
使用 PDO 或 MySQLi 提供的 预处理语句,它可以确保 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 语句)
通过 PDO 或 MySQLi 等现代数据库库,避免使用这些不安全的函数。
六、数据验证与过滤
除了 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 查询语句中。使用 预处理语句 和 参数化查询 是最有效的防护手段。