仅调用pdo::prepare()不防注入,必须同时禁用模拟预处理、sql中使用占位符、用户输入通过bindvalue()/execute()绑定、动态结构用白名单校验。

直接上结论:仅调用 PDO::prepare() 不防注入,必须同时满足四个条件——禁用模拟预处理、占位符写在 SQL 字符串里、所有用户输入走 bindValue() 或 execute([]) 绑定、动态结构部分(如表名、ORDER BY)用白名单硬校验。
为什么 PDO::prepare() 单独调用等于没做任何防护
常见错误是把变量拼进 SQL 字符串再传给 prepare(),比如:$pdo->prepare("SELECT * FROM users WHERE id = " . $_GET['id'])。这根本没触发预处理机制,PDO 只是编译了一条已拼好的 SQL,攻击者传 id=1 OR 1=1 会直接执行。
- 占位符必须是
?或:name,不能是 PHP 变量插值("{$id}"、.$id.都不行) -
prepare()只负责编译模板,不绑定也不执行;没调bindValue()、bindParam()或execute([]),参数就还是裸奔状态 - PHP 8.2 默认仍开启模拟预处理(
PDO::ATTR_EMULATE_PREPARES === true),MySQL 服务端根本没收到 PREPARE 指令
必须显式关闭模拟预处理并验证是否生效
MySQL 默认启用模拟预处理,PDO 在 PHP 层自己把 ? 替换成带引号的字符串再发给数据库——这和手动拼接只有毫秒级差别,' OR 1=1 -- 仍可能生效。
- 连接时强制关闭:
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_EMULATE_PREPARES => false]); - 验证是否成功:
var_dump($pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES));输出false才算到位 - 若关闭后报
Invalid parameter number,先检查占位符数量与bindValue()序号是否一致,别急着切回true - 开发环境可开 MySQL
general_log,看日志里是出现PREPARE stmt FROM '...'还是一条完整SELECT ... WHERE name = 'xxx'
数字类型必须用 PDO::PARAM_INT 显式绑定
数字字段不加引号,mysqli_real_escape_string() 和 addslashes() 完全无效。传 id=1 OR 1=1 会被当整数 1 截断,但仍有绕过风险(比如 ENUM 字段、tinyint 存布尔值时类型错配)。
- 正确写法:
$stmt->bindValue(1, (int)$_GET['id'], PDO::PARAM_INT);或$stmt->execute([(int)$_GET['id']]); - 别信
(int)强转能兜底——它只截断,不阻止恶意逻辑被带入;绑定时指定类型才是数据库层确认 -
WHERE、UPDATE SET、VALUES里所有来自$_GET、$_POST、$_COOKIE的值,只要进 SQL,就必须走占位符
ORDER BY、表名、字段名必须白名单硬校验
SQL 语法不允许 ORDER BY ? 或 FROM ?,数据库解析阶段就报错。这部分无法参数化,靠过滤或正则等于沙上筑塔。
- 排序字段:
$sort = in_array($_GET['sort'] ?? 'id', ['id', 'name', 'created_at'], true) ? $_GET['sort'] : 'id'; - 排序方向:
$dir = strtoupper($_GET['order'] ?? 'ASC') === 'ASC' || strtoupper($_GET['order'] ?? 'ASC') === 'DESC' ? strtoupper($_GET['order']) : 'ASC'; - 表名映射:
$tables = ['users' => 'users', 'logs' => 'admin_logs']; $table = $tables[$_GET['table'] ?? 'users'] ?? 'users'; - 拼接前必须终止脚本或抛异常:
if (!in_array($sort, $allowed)) { throw new InvalidArgumentException('Invalid sort field'); }
最易被忽略的是字符集和错误模式:不设 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,错误会静默失败;不显式指定 charset=utf8mb4,宽字节注入可能绕过。这两项不配齐,前面所有操作都可能埋雷。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











