pdo::prepare和mysqli_prepare是php 8.5.5防sql注入唯一可靠手段,须禁用pdo模拟预处理、统一占位符风格、白名单校验标识符、数字参数强制类型转换且不可拼接。

PDO::prepare 和 mysqli_prepare 是 PHP 8.5.5 中防止 SQL 注入的唯一可靠手段,其他方法(如 addslashes()、mysqli_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() 语句里只能用一种占位符:要么全用 ?,要么全用 :name。混用会导致 execute() 失败或绑定错位。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 命名参数必须配关联数组:
$stmt->execute(['username' => $u]); - 问号占位符必须配索引数组:
$stmt->execute([$u, $p]); - 别写
$stmt->execute([':username' => $u])——键名带冒号是无效的,PDO 不认 - 也别直接传
$_POST,除非你已白名单校验字段名且顺序完全匹配
表名、列名、ORDER BY 不能进绑定
prepare() 只能绑定数据值,不能绑定标识符。拼接用户输入的表名等于开门揖盗。
- 错误示例:
"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字段和排序方向:in_array($sort, ['id', 'created_at', 'title'])和in_array($dir, ['ASC', 'DESC'])
LIMIT 和数字型参数要特别处理
LIMIT 后的数字不能直接塞进 ? 占位符——MySQL 要求它是整数字面量,不是参数值。
- 必须强转:
$limit = (int)($_GET['limit'] ?? 20);或filter_var($_GET['limit'], FILTER_VALIDATE_INT) - 再拼:
"SELECT * FROM posts LIMIT {$limit}"(前提是已校验为正整数) - 数字型 WHERE 条件仍要用占位符:
WHERE id = ?,而不是WHERE id = " . (int)$_GET['id']——后者一旦漏掉强转或被绕过,风险仍在 - 批量插入别循环
execute(),应一次性绑多组:INSERT INTO t VALUES (?, ?), (?, ?)
真正安全的边界不在“有没有用 prepare()”,而在“有没有让所有用户输入都经过绑定或白名单校验”。最容易被忽略的是 ORDER BY、LIMIT、动态表名,以及误以为 (int) 就万无一失的数字上下文。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










