Appearance
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";
}练习
识别并修复以下代码中的 SQL 注入漏洞:
php$name = $_GET['name']; $sql = "SELECT * FROM users WHERE name = '$name'"; $result = $conn->query($sql);使用预处理语句重写以下代码:
php$username = $_POST['username']; $email = $_POST['email']; $sql = "INSERT INTO users (username, email) VALUES ('$username', '$email')"; $conn->query($sql);实现一个安全的用户登录系统,防止 SQL 注入攻击。
为数据库创建一个受限权限的用户,并在应用中使用该用户。
