核心是所有外部输入必须通过参数绑定传入SQL,绝不拼接字符串;需禁用模拟预处理(PDO::ATTR_EMULATE_PREPARES=>false),使用prepare()+execute()流程,配合命名或问号占位符绑定,表名等结构化内容须白名单校验。

PHP 8 中用 PDO 预处理防止 SQL 注入,核心就一条:**所有外部输入(如用户提交的表单、URL 参数、文件内容等)都必须通过参数绑定传入 SQL,绝不拼接字符串**。
确保使用 PDO 并启用预处理模式
PDO 默认支持预处理,但要注意两点:一是连接时禁用模拟预处理(PDO::ATTR_EMULATE_PREPARES => false),二是明确使用 prepare() + execute() 流程,而不是直接 query()。
- 开启真实预处理可让数据库服务端解析 SQL 模板,参数作为独立数据传输,从根本上隔离代码与数据
- 模拟预处理(emulate=true)由 PHP 自己“假装”预处理,对某些边界情况(如含引号的 JSON 字段、特殊编码)可能绕过防护
正确写法:命名参数或问号占位符 + bindParam/bindValue
推荐优先用命名参数(清晰、可复用、顺序无关),也可用问号占位符(适合简单场景)。
- 命名参数示例:
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = :status AND name LIKE :pattern");<br>$stmt->bindValue(':status', 'active', PDO::PARAM_STR);<br>$stmt->bindValue(':pattern', "%{$search}%", PDO::PARAM_STR);<br>$stmt->execute(); - 问号占位符示例:
$stmt = $pdo->prepare("INSERT INTO logs (ip, action) VALUES (?, ?)");<br>$stmt->execute([$userIP, $action]);—— 此处execute()直接传数组,等价于逐个bindValue() -
bindValue()适合固定值(如字符串、整数),bindParam()适合变量引用(如循环中反复赋值),一般场景用bindValue()更安全
不能防注入的常见错误写法
这些操作哪怕用了 prepare(),也依然存在注入风险:
立即学习“PHP免费学习笔记(深入)”;
- 把变量拼进 SQL 字符串里:
"SELECT * FROM {$table} WHERE id = ?"—— 表名、字段名、ORDER BY 子句等**不能参数化**,需白名单校验或硬编码 - 手动拼接条件:
"WHERE name = '" . $_GET['name'] . "'"—— 即使后面调了prepare()也无效 - 用
quote()或addslashes()替代预处理 —— 这些函数不解决多字节编码、宽字符、上下文(如数字/布尔/JSON)等复杂场景,已不推荐
配套建议:连接配置与错误处理
安全不是只靠预处理,还需基础加固:
- 连接时设置错误模式为异常:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,避免静默失败掩盖问题 - 关闭全局魔法引号(PHP 8 已移除,无需操作),但要确认未在应用层自行做不可靠转义
- 对非字符串类型(如 ID、状态码)执行类型校验(
filter_var($id, FILTER_VALIDATE_INT))再进绑定,双重保险



















