SDL中必须检查SQL注入风险的阶段是需求、设计和编码阶段:需求阶段明确输入校验规则,设计阶段标定数据库输入路径并规定安全机制,编码阶段禁用危险API并在CI中扫描拼接模式。

SDL中哪些阶段必须检查SQL注入风险
SDL不是一次性动作,而是在需求、设计、编码、测试、发布五个环节持续施压。SQL注入的防御点必须前移——等到代码写完再扫漏洞,90%的修复成本都花在返工上。
关键卡点有三个:
• 需求阶段就要明确“所有用户输入不可信”,在PRD里写死校验规则(比如手机号必须是11位数字)
• 设计阶段输出数据流图,标出所有外部输入进入数据库的路径,每条路径旁注明是否使用PreparedStatement或等效机制
• 编码阶段强制禁用Statement,CI流水线中加入静态扫描规则:匹配createStatement()、executeQuery(.*\+)、${.*}(MyBatis)等危险模式
为什么仅靠代码扫描无法覆盖SDL中的SQL注入防控
静态扫描工具能抓出String sql = "SELECT * FROM user WHERE id = " + id;这类明显拼接,但对以下场景完全失效:
- 动态表名/列名:JDBC不支持参数化这些部分,但开发者常误用
String tableName = request.getParameter("table");后直接拼进SQL - ORDER BY子句:参数化只支持值,不支持排序字段或方向,
ORDER BY ?会报错,必须白名单校验sortField参数 - 存储过程调用:如果
CallableStatement里用字符串拼接了过程名或参数占位符位置,扫描工具通常视其为“已预编译”而放过
这些都得靠SDL设计阶段的架构评审和编码规范来堵——比如规定“所有动态表名必须从ALLOWED_TABLES枚举中取值”,而不是依赖后期检测。
测试阶段如何验证SQL注入防护是否真正生效
单元测试只覆盖正常路径,而SQL注入是异常路径攻击。SDL要求在集成测试阶段做三件事:
- 用
' OR '1'='1、1; DROP TABLE users--等典型payload批量请求所有带参数的接口,检查返回状态码是否为400/403而非200,且响应体不包含数据库错误信息(如java.sql.SQLException) - 在数据库代理层(如MySQL的
general_log)开启日志,确认最终执行的SQL语句里,用户输入内容是否被完整包裹在引号内,且无额外语句被拼入 - 对ORM框架做专项验证:MyBatis中检查所有
${}用法是否都在白名单控制下;Hibernate中确认@Query(nativeQuery = true)注解的方法是否全部使用setParameter而非字符串拼接
上线后SDL监控如何捕获绕过防护的SQL注入
再严密的SDL也无法100%杜绝漏网之鱼。生产环境必须部署运行时防护,重点不是拦截,而是精准发现:
• 在JDBC驱动层埋点,记录所有PreparedStatement的SQL模板与实际绑定参数,当某次执行的模板突然变成SELECT * FROM users WHERE id = ? UNION SELECT password FROM admin(即模板本身被污染),立即告警
• 对Web服务器日志做实时模式匹配,提取UNION SELECT、SLEEP(、information_schema等高危关键词,但只告警不拦截——因为合法业务也可能含这些词,需人工研判上下文
• 关键业务库开启审计日志,定期比对“执行SQL的IP来源”与“该用户正常访问路径”,比如财务系统接口突然从办公网IP发起大量SELECT * FROM payroll,就是典型异常
真正难防的从来不是教科书式注入,而是那些藏在动态SQL生成逻辑里、连PreparedStatement都救不了的拼接点——SDL的价值,就是把这类点从“上线后才发现”逼到“设计文档里就被标记为高危”。

















