首页 > 奇闻 > 正文内容

手把手教你用预处理语句防止PHP数据库注入攻击

奇闻2025-05-27 20:57:06

你写的登录功能正在被黑客当后门用?

前两天有个学员问我:"老师,我照着网上的教程写了登录功能,怎么老是有陌生账号登进来?" 一查代码差点没背过气去——他在SQL语句里直接拼接用户输入,活脱脱给黑客开了个VIP通道。今天咱们就来把这个漏洞焊死,保证你看完就能写出铁桶般的安全代码!


第一步:认识你的敌人——SQL注入攻击长啥样?

(打开记事本写个反面教材)

php复制
// 这是自杀式写法!千万别学!
$username = $_POST['username'];
$password = md5($_POST['password']);
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";

假设用户输入用户名是' OR 1=1 -- ,这句SQL就会变成:

sql复制
SELECT * FROM users WHERE username='' OR 1=1 -- ' AND password='...'

(敲黑板)看见没?--后面的内容全被注释掉了,1=1永远成立,黑客直接登录管理员账号!


第二步:PDO预处理语句——给SQL穿防弹衣

(掏出Visual Studio Code开始演示)
??正确姿势四部曲:??

  1. ??连数据库要像对待初恋??
php复制
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 错误必须抛出!
  1. ??写SQL留空位??
php复制
$sql = "SELECT * FROM users WHERE username = :user AND password = :pass";
  1. ??准备语句就像套模板??
php复制
$stmt = $db->prepare($sql);
  1. ??绑定参数要像安检员??
php复制
$stmt->execute([
    ':user' => $_POST['username'],
    ':pass' => md5($_POST['password'])
]);

(突然拍大腿)对了!参数绑定支持各种数据类型,数字、字符串自动处理,再也不用手动加引号了!


第三步:实战演练——注册功能防护指南

假设要写用户注册功能,看我怎么用预处理套三层防护:

php复制
try {
    // 过滤层
    $username = trim(htmlspecialchars($_POST['username']));
    $age = intval($_POST['age']);
    
    // 预处理层
    $stmt = $db->prepare("INSERT INTO users (username, age) VALUES (?, ?)");
    $stmt->bindParam(1, $username, PDO::PARAM_STR);
    $stmt->bindParam(2, $age, PDO::PARAM_INT);
    
    // 执行层
    $stmt->execute();
} catch (PDOException $e) {
    // 错误处理要友好,别暴露数据库信息
    die("哎哟,出错了!错误代码:".$e->getCode());
}

??三个必须注意的坑:??

  1. 表名和字段名不能用占位符(所以要用白名单校验)
  2. bindParambindValue的区别(前者绑定变量,后者绑定值)
  3. 记得指定参数类型(比如PDO::PARAM_INT防字符串注入)

第四步:突发情况处理——当预处理语句也不管用时

去年遇到个奇葩案例:有个老系统非得用动态表名,像$table = 'order_'.date('Ym');这种。这时候怎么办?

??应急方案:??

  1. 白名单校验法
php复制
$allowedTables = ['order_202308', 'order_202309'];
if (!in_array($table, $allowedTables)) {
    die("表哥,别乱改表名啊!");
}
  1. 严格正则验证
php复制
if (!preg_match('/^order_\d{6}$/', $table)) {
    die("你这表名不对劲啊!");
}

(突然压低声音)说真的,这种情况能避免就避免,实在不行记得用框架的查询构造器!


个人观点:安全防护不是选修课

干了十多年开发,见过太多"先上线再说"的惨剧。2019年某电商平台数据泄露,根源就是个没做预处理的查询语句,直接导致700万用户信息被扒。现在PHP8.2的PDO已经支持更安全的参数绑定,但还有很多人在用mysql_connect这种上古函数。

最后给新人一句忠告:当你觉得"这样写应该没问题"的时候,先假设用户会输入' OR 1=1 -- ,多问自己"这代码能防住使坏的人吗?"。记住,每个未处理的用户输入都是定时炸弹,而预处理语句就是最好的拆弹工具!

搜索