PHP 8.4中仅new PDO不防SQL注入,必须配合prepare()+execute()(或bindParam)实现参数化查询,并设置charset=utf8mb4、禁用模拟预处理(ATTR_EMULATE_PREPARES=false)、前置输入验证,否则仍存在宽字节、报错注入等风险。

PHP 8 中用 PDO 防 SQL 注入,关键不是“用了 PDO”,而是必须用 prepare() + execute()(或 bindParam())组合,且严禁任何字符串拼接。PDO 本身不防注入,只有参数化查询才能切断用户输入与 SQL 语法的绑定关系。
为什么只 new PDO 不够安全
PDO 对象创建只是连接数据库,不涉及查询逻辑。如果后续用 query() 或 exec() 拼接变量,照样中招:
$pdo->query("SELECT * FROM users WHERE id = " . $_GET['id']); // 危险!这种写法在 PHP 8.4 中仍会触发宽字节注入、报错注入等漏洞。PDO 的防护能力完全取决于你是否把用户数据作为「参数」传入,而不是「字符串的一部分」。
必须设置 charset=utf8mb4 并禁用模拟预处理
MySQL 默认启用模拟预处理(PDO::ATTR_EMULATE_PREPARES = true),此时 PDO 会在 PHP 层自己做转义,绕过 MySQL 原生预处理机制,导致 LIKE、ORDER BY 等场景失效。
立即学习“PHP免费学习笔记(深入)”;
- 连接时强制指定字符集:
mysql:host=localhost;dbname=test;charset=utf8mb4 - 立即关闭模拟:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) - 开启异常模式便于定位问题:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
漏掉任一环节,都可能让攻击者通过 %df' OR 1=1 -- 这类宽字节 payload 绕过防护。
占位符怎么选:? 还是 :name
两种都安全,但行为不同,选错会直接报错:
- 用问号
?:必须按顺序传索引数组,如$stmt->execute([$_POST['email'], $status]) - 用命名占位符
:email:传关联数组,键名必须严格匹配,如$stmt->execute(['email' => $_POST['email']]) - 混用会触发
PDOException: SQLSTATE[HY093]: Invalid parameter number -
LIKE查询必须在 PHP 层加通配符:$keyword = '%' . trim($_GET['q']) . '%'; $stmt->execute([$keyword]),不能写成WHERE name LIKE :q%
输入验证不能交给数据库层兜底
预处理语句防的是「语法注入」,不是「业务非法数据」。比如用户传来负数 ID、超长邮箱、非白名单排序字段,数据库照样执行成功,但可能引发越权、慢查询或日志污染。
- 整数 ID 必须用
filter_var($id, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]),不用(int)强转 - 枚举值如
order、sort必须白名单校验:in_array($_GET['sort'] ?? '', ['created_at', 'score']) - 自由文本长度用
strlen()截断或拒绝,避免LIKE '%xxx%' + 1MB 字符拖垮索引
最常被忽略的一点:即使所有代码都用了预处理,只要数据库账号有 DROP 或 FILE 权限,配合报错注入或 SELECT ... INTO OUTFILE,仍可能被提权或写 WebShell。



















