pdo和mysqli预处理是php 8.5.5防sql注入唯一可靠手段,须禁用pdo模拟预处理、统一占位符风格、白名单限制标识符、密码校验在php层完成。

PDO 和 MySQLi 的预处理语句是 PHP 8.5.5 中防止 SQL 注入的唯一可靠手段,其他方法(如 addslashes()、mysql_real_escape_string() 或简单类型转换)都不足以应对现代绕过手法。
必须关闭 PDO 模拟预处理
PHP 8.5.5 默认仍启用 PDO::ATTR_EMULATE_PREPARES => true,这意味着 prepare() 只是 PHP 层字符串替换,不是真正的数据库预处理——等于没防。
- 显式禁用:
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_EMULATE_PREPARES => false]); - 验证是否生效:
var_dump($pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES));必须输出false - 若返回
true,且 MySQL 版本 ≥ 5.1.17,则说明配置未生效或驱动不支持原生预处理
命名参数和问号占位符不能混用
一个 prepare() 语句里只能用一种占位符风格。混用会导致 execute() 失败或绑定错位。
- 命名参数(如
:username)必须配关联数组:$stmt->execute(['username' => $u]); - 问号占位符必须配索引数组:
$stmt->execute([$u, $p]); - 别写
$stmt->execute([':username' => $u])—— 这是无效键名,PDO 不认 - 也别图省事用
$stmt->execute($_POST),除非你确保键名/顺序与 SQL 完全匹配,且已过滤非法字段
表名、列名、ORDER BY 不能进预处理绑定
prepare() 只能绑定数据值,不能绑定标识符(identifier)。直接拼接用户输入的表名等于开门揖盗。
- 错误:
"SELECT * FROM " . $_GET['table'] . " WHERE id = ?" - 正确做法:白名单硬编码限定范围:
$tables = ['users', 'posts', 'comments']; $table = in_array($_GET['table'] ?? '', $tables) ? $_GET['table'] : 'users'; - 再拼:
"SELECT * FROM {$table} WHERE id = ?"—— 此时$table已是可信值 - 同理适用于
ORDER BY字段:只允许['id', 'created_at', 'title']等有限选项
密码校验必须在 PHP 层完成
把明文密码塞进 SQL 去比对哈希值,既暴露逻辑又绕过 password_verify() 的常量时间防护。
- 先查哈希:
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE username = :user LIMIT 1"); - 只取一行:
$stmt->execute(['user' => $_POST['username']]); $row = $stmt->fetch(); - 再校验:
password_verify($_POST['password'], $row['password_hash']) - 绝不写:
"WHERE username = :user AND password_hash = SHA2(?, 256)"—— 这类写法让密码策略、加盐方式全暴露给 DB 层
真正容易被忽略的点在于:预处理只是工具,它不自动识别哪些该绑、哪些不该拼。开发者得自己划清「数据」和「结构」的边界——而这个边界,在登录、搜索、分页、多租户等场景里,往往比想象中模糊得多。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











