PDO 安全需四层叠加:关闭预处理模拟(setAttribute(PDO::ATTR_EMULATE_PREPARES, false))、统一 utf8mb4 字符集、禁用错误泄露(ERRMODE_SILENT)、最小化数据库权限。

PDO 默认不开启预处理模拟,这是安全前提
PHP 的 PDO 本身是安全的,但“安全”不等于“默认防 SQL 注入”。关键在是否真正使用预处理(prepared statements),而非字符串拼接。很多开发者误以为调用 prepare() 就万事大吉,其实如果启用了 PDO::ATTR_EMULATE_PREPARES = true(MySQL 驱动默认行为),PDO 会在客户端模拟预处理——此时若绑定的变量含特殊编码或 MySQL 服务端字符集配置不当,仍可能绕过参数化逻辑。
- 务必显式关闭模拟:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) - 该设置必须在连接建立后、任何查询执行前完成,放在
new PDO()之后立即设 - 仅当 MySQL 服务端版本 ≥ 5.1.17 且字符集统一(如全部用
utf8mb4)时,禁用模拟才稳定生效
字符集没对齐,prepare 也白搭
即使用了真实预处理,若 PHP 连接层、MySQL 服务端、数据表三者字符集不一致,恶意输入仍可能触发多字节截断或宽字节注入。典型表现是:' OR 1=1 -- 看似被参数化过滤了,但在 gbk 环境下,前面加个 %A1 可能让单引号逃逸。
- 连接 DSN 中强制指定字符集:
mysql:host=localhost;dbname=test;charset=utf8mb4 - 不要依赖
SET NAMES或exec("SET NAMES utf8mb4"),它不保证与预处理通道同步 - 检查 MySQL 全局配置:
character_set_server、collation_server均应为utf8mb4_*
错误信息泄露会暴露数据库结构
PDO 默认把 MySQL 错误原样抛出,比如 SQLSTATE[42S22]: Column not found 直接暴露字段名,SQLSTATE[42000] 可能暗示语法结构。线上环境必须关掉详细报错。
- 构造
PDO时传入选项:[PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT](推荐)或PDO::ERRMODE_EXCEPTION(需自行 catch 并记录,不返回给前端) - 绝对避免
PDO::ERRMODE_WARNING,它会把 SQL 和上下文直接输出到页面 - Web 服务器层面也要关掉
display_errors,防止其他模块错误泄露路径或配置
权限最小化比 PDO 设置更重要
再严格的 PDO 配置,也挡不住一个拥有 DROP TABLE 权限的账号执行恶意语句。PDO 不限制 SQL 类型,只负责传输和参数化。
立即学习“PHP免费学习笔记(深入)”;
- 数据库账号只授予必要权限:
SELECT, INSERT, UPDATE, DELETE,删库、建表、写文件(INTO OUTFILE)等一律禁止 - 不同业务模块用不同账号,比如后台管理用
admin_user,前端 API 用api_user - 密码不用硬编码在代码里,通过环境变量或配置中心注入,避免提交到 Git
PDO 的安全性不是开关式配置,而是连接参数、字符集、错误策略、数据库权限四层叠加的结果。少设一层,就可能让预处理形同虚设。











