Appearance
16.1 防 SQL 注入
SQL 注入攻击原理
SQL 注入是一种常见的网络攻击方式,攻击者通过在用户输入中插入恶意 SQL 代码,来操纵数据库查询,从而获取敏感信息或执行未授权操作。
攻击示例
假设有一个登录表单,代码如下:
php
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);如果攻击者输入以下内容作为用户名:
admin' --那么生成的 SQL 查询将变成:
sql
SELECT * FROM users WHERE username = 'admin' --' AND password = '任意值'由于 -- 是 SQL 注释符号,后面的密码验证部分会被注释掉,攻击者可以无需密码登录系统。
防止 SQL 注入的方法
1. 使用预处理语句
预处理语句是防止 SQL 注入的最佳方法,它将 SQL 语句的结构与数据分开处理。
使用 mysqli 预处理语句
php
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();使用 PDO 预处理语句
php
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
$result = $stmt->fetchAll();2. 输入验证和过滤
对用户输入进行验证和过滤,确保输入符合预期格式。
php
// 验证用户名只包含字母、数字和下划线
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
die("用户名格式不正确");
}
// 验证密码长度
if (strlen($password) < 6) {
die("密码长度至少为 6 位");
}3. 转义特殊字符
如果无法使用预处理语句,可以使用 mysqli_real_escape_string() 函数来转义特殊字符。
php
$username = mysqli_real_escape_string($conn, $_POST['username']);
$password = mysqli_real_escape_string($conn, $_POST['password']);
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);4. 限制数据库用户权限
为应用程序创建专用的数据库用户,并限制其权限,只授予必要的权限。
sql
-- 创建只读用户
CREATE USER 'app_read'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON database.* TO 'app_read'@'localhost';
-- 创建读写用户
CREATE USER 'app_write'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON database.* TO 'app_write'@'localhost';5. 使用参数化查询
参数化查询是一种将 SQL 语句与参数分离的方法,可以有效防止 SQL 注入。
php
// 使用 mysqli 参数化查询
$stmt = $conn->prepare("INSERT INTO users (username, email) VALUES (?, ?)");
$stmt->bind_param("ss", $username, $email);
$stmt->execute();
// 使用 PDO 参数化查询
$stmt = $pdo->prepare("INSERT INTO users (username, email) VALUES (:username, :email)");
$stmt->execute([':username' => $username, ':email' => $email]);6. 避免使用动态 SQL
尽量避免使用动态构建的 SQL 语句,特别是当 SQL 语句中包含用户输入时。
php
// 不安全的动态 SQL
$sql = "SELECT * FROM users WHERE " . $_POST['condition'];
// 安全的做法
$allowed_conditions = ['id', 'username', 'email'];
$condition = in_array($_POST['field'], $allowed_conditions) ? $_POST['field'] : 'id';
$sql = "SELECT * FROM users WHERE $condition = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $_POST['value']);
$stmt->execute();7. 定期更新和补丁
保持数据库系统和应用程序框架的更新,及时安装安全补丁。
8. 使用 ORM 框架
使用 ORM(对象关系映射)框架,如 Laravel 的 Eloquent、Doctrine 等,这些框架会自动处理 SQL 注入防护。
php
// 使用 Laravel Eloquent
$user = User::where('username', $username)->where('password', $password)->first();
// 使用 Doctrine
$user = $em->getRepository(User::class)->findOneBy(['username' => $username, 'password' => $password]);实战演练
场景:用户登录系统
不安全的实现
php
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$conn = mysqli_connect('localhost', 'root', '', 'test');
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo "登录成功";
} else {
echo "用户名或密码错误";
}
?>安全的实现
php
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$conn = mysqli_connect('localhost', 'root', '', 'test');
// 使用预处理语句
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
if (mysqli_num_rows($result) > 0) {
echo "登录成功";
} else {
echo "用户名或密码错误";
}
?>场景:搜索功能
不安全的实现
php
<?php
$keyword = $_GET['keyword'];
$conn = mysqli_connect('localhost', 'root', '', 'test');
$sql = "SELECT * FROM products WHERE name LIKE '%$keyword%'";
$result = mysqli_query($conn, $sql);
?>安全的实现
php
<?php
$keyword = $_GET['keyword'];
$conn = mysqli_connect('localhost', 'root', '', 'test');
// 使用预处理语句
$stmt = $conn->prepare("SELECT * FROM products WHERE name LIKE ?");
$keyword = "%$keyword%";
$stmt->bind_param("s", $keyword);
$stmt->execute();
$result = $stmt->get_result();
?>总结
SQL 注入是一种严重的安全漏洞,可能导致数据库被攻击、敏感信息泄露等问题。通过使用预处理语句、输入验证、转义特殊字符、限制数据库用户权限等方法,可以有效防止 SQL 注入攻击。
在开发过程中,应始终将安全放在首位,遵循安全编码实践,定期进行安全审计和测试,确保应用程序的安全性。
