在PHP项目开发里,数据库存储着用户信息、交易记录等核心数据,数据泄露会带来用户隐私曝光、业务受损等严重后果,因此做好数据库防泄露工作至关重要。

一、防范SQL注入攻击
SQL注入是数据库数据泄露最常见的诱因,攻击者通过构造恶意输入篡改SQL语句逻辑,从而获取未授权的数据。PHP中防范SQL注入最有效的手段是使用参数化查询,避免直接拼接SQL字符串。
1. 使用PDO实现参数化查询
PDO是PHP官方推荐的数据库扩展,支持参数化查询,能有效隔离用户输入和SQL逻辑。
<?php
// 数据库连接配置
$dsn = 'mysql:host=127.0.0.1;dbname=test_db;charset=utf8mb4';
$username = 'db_user';
$password = 'db_pass';
try {
// 创建PDO实例
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 错误示例:直接拼接SQL,存在注入风险
// $userId = $_GET['id'];
// $sql = "SELECT * FROM users WHERE id = " . $userId;
// $stmt = $pdo->query($sql);
// 正确示例:使用参数化查询
$userId = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = :user_id";
$stmt = $pdo->prepare($sql);
// 绑定参数,避免用户输入被当作SQL指令执行
$stmt->bindParam(':user_id', $userId, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($result);
} catch (PDOException $e) {
// 生产环境不要暴露详细错误信息
error_log("数据库查询错误:" . $e->getMessage());
echo "查询失败,请稍后重试";
}
?>
2. 使用mysqli扩展的参数化查询
如果项目使用的是mysqli扩展,同样可以通过预处理语句实现参数化查询。
<?php
$conn = new mysqli("127.0.0.1", "db_user", "db_pass", "test_db");
if ($conn->connect_error) {
error_log("数据库连接失败:" . $conn->connect_error);
die("数据库连接失败");
}
$userId = $_GET['id'];
// 预处理SQL语句
$stmt = $conn->prepare("SELECT username, email FROM users WHERE id = ?");
// 绑定参数,s表示字符串类型,i表示整数类型
$stmt->bind_param("i", $userId);
$stmt->execute();
$res = $stmt->get_result();
while ($row = $res->fetch_assoc()) {
print_r($row);
}
$stmt->close();
$conn->close();
?>
二、严格管控数据库访问权限
权限管控是防止数据泄露的基础防线,需要遵循最小权限原则,避免给PHP应用分配过高的数据库权限。
- 不要使用数据库的root账号连接PHP应用,单独创建仅拥有对应业务所需权限的数据库用户
- 仅授予应用需要的库表操作权限,比如查询业务只需要SELECT权限,不要授予DROP、ALTER等高危权限
- 限制数据库用户的访问来源IP,仅允许PHP应用所在服务器的IP连接数据库,禁止公网直接访问数据库端口
- 定期轮换数据库账号密码,避免密码长期不变被破解
三、敏感数据加密存储
即使数据库被非法访问,加密存储的敏感数据也很难被直接利用,因此需要对核心敏感数据进行加密处理。
1. 密码加密存储
用户密码绝对不能明文存储,PHP提供了password_hash()和password_verify()函数来实现安全的密码哈希处理。
<?php
// 用户注册时密码加密存储
$userPassword = $_POST['password'];
// 使用PASSWORD_DEFAULT算法,自动选择当前安全的哈希算法
$hashedPassword = password_hash($userPassword, PASSWORD_DEFAULT);
// 存入数据库的示例
$sql = "INSERT INTO users (username, password) VALUES (:username, :password)";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':username', $_POST['username']);
$stmt->bindParam(':password', $hashedPassword);
$stmt->execute();
// 用户登录时验证密码
$inputPassword = $_POST['password'];
$sql = "SELECT password FROM users WHERE username = :username";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':username', $_POST['username']);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($inputPassword, $user['password'])) {
echo "登录成功";
} else {
echo "用户名或密码错误";
}
?>
2. 其他敏感数据加密
除了密码,用户的手机号、身份证号、银行卡号等敏感信息,需要使用对称加密算法加密后存储,加密密钥不要硬编码在代码中,建议存放在环境变量或者独立的配置服务里。
<?php
// 使用AES-256-CBC算法加密敏感数据
function encryptData($data, $key, $iv) {
return openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
}
function decryptData($encryptedData, $key, $iv) {
return openssl_decrypt($encryptedData, 'AES-256-CBC', $key, 0, $iv);
}
// 从环境变量获取加密密钥和IV,不要硬编码
$encryptKey = getenv('DATA_ENCRYPT_KEY');
$encryptIv = getenv('DATA_ENCRYPT_IV');
$userPhone = "13800138000";
$encryptedPhone = encryptData($userPhone, $encryptKey, $encryptIv);
// 将$encryptedPhone存入数据库
echo "加密后的手机号:" . $encryptedPhone;
// 从数据库取出后解密
$decryptedPhone = decryptData($encryptedPhone, $encryptKey, $encryptIv);
echo "解密后的手机号:" . $decryptedPhone;
?>
四、其他防护补充措施
- 关闭PHP的错误回显,生产环境将错误信息记录到日志文件,避免数据库结构、查询语句等信息通过错误提示泄露给攻击者
- 对数据库操作日志进行审计,记录所有查询、修改操作的来源IP和操作内容,便于发生泄露后追溯原因
- 定期对数据库进行安全扫描,排查弱密码、未授权访问等安全隐患
- 对返回给前端的数据进行脱敏处理,比如手机号显示138****8000,避免敏感数据在前端泄露
所有涉及数据库操作的代码都要经过安全校验,不要信任任何用户输入,同时定期关注PHP和数据库组件的安全更新,及时修复已知漏洞。