参数化查询修复后必须验证执行路径是否真走预处理逻辑,确认PDO或MySQLi实际调用了prepare/execute,且bind_param类型字符串与变量严格匹配,杜绝拼接、未执行、异常分支绕过等隐性漏洞。

参数化查询改完不等于漏洞消失,必须验证执行路径是否真走预处理逻辑
确认 PDO 或 MySQLi 是否真的用了 prepare/execute
很多团队改完代码后只检查语法,却没验证实际执行的 SQL 是否被参数化。常见错误是:把 query() 换成 prepare(),但没调用 execute(),或者用 bind_param() 时传了变量名而非变量值。
- PHP 中检查
$stmt->execute()是否被调用,而不是只写$pdo->prepare(...)后直接丢弃语句对象 - MySQLi 需确认
bind_param()的第一个参数类型字符串(如"s"、"i")与后续变量一一对应,且变量未在绑定前被拼接进 SQL 字符串 - 用 Xdebug 或日志在
execute()前打点,输出$stmt->queryString(注意:它不含参数值,只含占位符),确认没有动态拼接痕迹
测试用例必须覆盖边界输入和错误分支
修复有效性不是看正常 ID 能查出来,而是看恶意输入能否触发非预期行为。光测 id=1 和 id=1' OR '1'='1 不够,得测真正容易漏掉的场景:
- 数字型参数传入空字符串或负数:
id=(空)、id=-1,观察是否仍走预处理(有些框架对非数字自动过滤,掩盖了未修复本质) - 字符型字段传入单引号+注释符:
username=admin'--,检查返回是否仍是“用户不存在”,而非报错或返回所有用户 - URL 编码绕过测试:
id=1%27%20OR%201%3D1(即1' OR 1=1的编码),确认中间件未在参数解析阶段就解码并拼接进 SQL
别依赖自动化扫描工具做最终判断
sqlmap 等工具能发现显式报错或布尔盲注,但对已修复的接口常给出“无注入”假阳性——因为它只看响应差异,不看底层执行逻辑。更危险的是:某些 ORM 封装(如 Laravel 的 whereRaw()、Django 的 extra())表面看是参数化,实则内部拼接字符串。
- 手工构造 payload 时,优先用
' AND SLEEP(3)--(MySQL)或'; SELECT pg_sleep(3);--(PostgreSQL),观察响应延迟,比看错误信息更可靠 - 检查所有数据库交互入口,包括日志记录、导出功能、搜索建议等次要路径,这些地方常被跳过修复
- 如果项目用了 Doctrine、Sequelize 等 ORM,逐个确认每个
query()、raw()、literal()调用是否真受参数化保护,而不是靠文档“应该安全”就放行
最常被忽略的点:修复只改了主流程,但异常处理里的日志记录、监控上报、缓存键生成等辅助逻辑,仍可能把原始用户输入拼进 SQL —— 这些代码往往不在 PR diff 里,也不在常规测试覆盖范围内。


















