PDO预处理非自动防注入,需禁用ATTR_EMULATE_PREPARES、白名单校验动态对象名、LIKE中通配符由PHP拼接、IN子句须校验并强转参数类型。

PDO 的 prepare() 本身不是“自动防注入”的开关,它只在正确配置和规范使用时才真正生效。一旦配置错误、用法越界或绕过参数化逻辑,预编译就会形同虚设,SQL 注入风险依然存在。
关键失效点:ATTR_EMULATE_PREPARES 被启用
这是最隐蔽也最常见的一类失效。当 PDO 设置 PDO::ATTR_EMULATE_PREPARES => true(默认值在部分旧版本中为 true),PDO 就不会把预处理语句真正发给 MySQL 服务端编译,而是在 PHP 层自己做字符串替换——本质还是拼接,只是看起来像预处理。
- 攻击者仍可利用宽字节、编码绕过等手法注入,例如传入
%df' OR 1=1 --(配合 gb2312 环境) - 解决方法:显式关闭模拟预处理:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - 同时确保连接 DSN 中声明了安全字符集,如
charset=utf8mb4,防止字符集不一致导致的解析偏差
动态对象名无法参数化:表名、列名、排序字段
占位符(? 或 :name)只能用于数据值,不能用于 SQL 结构部分。若用户输入直接拼进表名、ORDER BY 字段或 GROUP BY 表达式,预处理完全不起作用。
- 危险写法:
$table = $_GET['tab']; $stmt = $pdo->prepare("SELECT * FROM {$table} WHERE id = ?"); - 安全做法:对这类结构项使用白名单校验,例如
in_array($table, ['users', 'posts', 'comments']),匹配失败则拒绝请求 - 排序字段同理,
ORDER BY title可接受,但ORDER BY $_GET['sort']必须映射到固定字段列表
LIKE 子句中通配符被拼接进 SQL 字符串
很多人误以为 WHERE name LIKE '%?%' 是合法写法,其实占位符不能出现在引号内,这样写会导致语法错误;更危险的是手动拼接:"%{$_GET['kw']}%" 再传入 execute —— 这等于把用户输入又放回了 SQL 上下文。
- 正确方式:通配符由 PHP 拼,占位符只包住完整值,例如
$kw = '%' . $pdo->quote($_GET['kw']) . '%';不推荐;更稳妥是$search = '%' . trim($_GET['kw']) . '%'; $stmt->execute([$search]); - 注意:不要用
addcslashes()或正则替换替代参数化,只要最终进了 SQL 字符串,就可能被解析
IN 子句参数数量不确定时硬编码占位符
当需要查询多个 ID(如 WHERE id IN (1,2,3)),无法用单个占位符覆盖全部。若简单拼接 "(?, ?, ?)" 并按数组长度生成,却未同步校验输入合法性,就可能引入类型混淆或截断漏洞。
- 风险操作:
$ids = explode(',', $_GET['ids']); $placeholders = str_repeat('?,', count($ids) - 1) . '?'; $sql = "SELECT * FROM users WHERE id IN ($placeholders)";—— 若未过滤$ids全为整型,仍可能注入 - 安全做法:先清理并强转每个值为整型或字符串,再生成对应数量占位符;或改用临时表 + 批量插入后 JOIN 查询
- 也可借助
str_pad()+array_map('intval', $ids)做双重保障

















