本文目录导读:

我来通过一个用户登录系统的案例,演示PHP如何防止SQL注入攻击。
不安全的代码示例(容易受到SQL注入攻击)
<?php
// 不安全的登录验证
$username = $_POST['username'];
$password = $_POST['password'];
// 直接拼接SQL语句 - 非常危险!
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo "登录成功";
} else {
echo "登录失败";
}
?>
攻击者可以输入:
- 用户名:
admin' -- - 密码:
这会变成:SELECT * FROM users WHERE username = 'admin' --' AND password = '...',绕过了密码验证!
安全的代码示例(使用预处理语句)
<?php
// 安全的登录验证 - 使用PDO预处理语句
// 数据库连接配置
$host = 'localhost';
$dbname = 'user_system';
$username = 'root';
$password = '';
try {
// 创建PDO连接
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
// 设置错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 获取用户输入
$userInput = $_POST['username'];
$passInput = $_POST['password'];
// 使用预处理语句(推荐方式)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
// 绑定参数(自动处理转义)
$stmt->bindParam(':username', $userInput, PDO::PARAM_STR);
$stmt->bindParam(':password', $passInput, PDO::PARAM_STR);
// 执行查询
$stmt->execute();
// 获取结果
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
// 使用password_verify验证密码哈希(更安全)
if (password_verify($passInput, $user['password'])) {
echo "登录成功!欢迎 " . htmlspecialchars($user['username']);
} else {
echo "密码错误";
}
} else {
echo "用户不存在";
}
} catch (PDOException $e) {
// 记录错误但不显示敏感信息给用户
error_log("数据库错误: " . $e->getMessage());
echo "系统错误,请稍后再试";
}
?>
更完整的注册和登录示例
<?php
class SecureUserAuth {
private $pdo;
public function __construct($host, $dbname, $username, $password) {
$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
$this->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 // 使用真实的预处理语句
]);
}
// 安全注册
public function register($username, $password) {
// 参数验证
if (strlen($username) < 3 || strlen($username) > 20) {
return "用户名长度必须在3-20之间";
}
if (strlen($password) < 6) {
return "密码长度至少6位";
}
try {
// 检查用户名是否已存在(使用预处理语句)
$checkStmt = $this->pdo->prepare("SELECT id FROM users WHERE username = :username");
$checkStmt->execute([':username' => $username]);
if ($checkStmt->fetch()) {
return "用户名已存在";
}
// 密码哈希(绝不要明文存储密码)
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// 安全插入数据
$insertStmt = $this->pdo->prepare(
"INSERT INTO users (username, password, created_at) VALUES (:username, :password, NOW())"
);
$insertStmt->execute([
':username' => $username,
':password' => $hashedPassword
]);
return "注册成功";
} catch (PDOException $e) {
error_log("注册错误: " . $e->getMessage());
return "注册失败,请稍后重试";
}
}
// 搜索功能(同样是安全的)
public function searchUsers($keyword) {
// 使用LIKE查询也安全
$stmt = $this->pdo->prepare(
"SELECT id, username FROM users WHERE username LIKE :keyword LIMIT 10"
);
// LIKE查询需要特殊处理
$searchTerm = '%' . $keyword . '%';
$stmt->execute([':keyword' => $searchTerm]);
return $stmt->fetchAll();
}
}
// 使用示例
$auth = new SecureUserAuth('localhost', 'user_system', 'root', 'password');
// 安全注册
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['register'])) {
$result = $auth->register($_POST['username'], $_POST['password']);
echo $result;
}
// 安全搜索
if (isset($_GET['search'])) {
$users = $auth->searchUsers($_GET['keyword']);
foreach ($users as $user) {
echo htmlspecialchars($user['username']) . "<br>";
}
}
?>
数据库表结构
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键防护措施总结
- 使用预处理语句(PDO或MySQLi) - 最有效的防御
- 参数绑定 - 避免直接拼接SQL
- 密码哈希 - 使用
password_hash()和password_verify() - 输入验证 - 检查数据类型、长度、格式
- 输出转义 - 使用
htmlspecialchars()防止XSS - 最小权限原则 - 数据库用户只授予必要的权限
- 错误处理 - 不向用户显示敏感错误信息
通过这种方式,即使攻击者输入 ' OR '1'='1 这样的恶意内容,预处理语句也会将其视为普通字符串,不会影响SQL语句的结构。