SQL注入防护需贯穿开发、测试、运维三阶段:开发阶段严查参数化绑定与拼接漏洞;测试阶段用真实数据库验证三类payload及权限配置;运维阶段通过审计日志、定制WAF规则和权限比对实现可发现可阻断。

SQL注入防护检查清单必须按阶段拆解,不能只靠WAF或ORM
靠 PreparedStatement 或开启 ORM 的自动转义,不等于安全。真实漏洞常出现在动态拼接日志查询、权限校验 SQL、存储过程调用、甚至数据库链接池配置里。检查清单得贯穿代码编写、测试部署、线上巡检三个环节,每个环节有不可跳过的硬性动作。
开发阶段:所有 SQL 构建点必须人工确认参数绑定方式
不是“用了 MyBatis 就安全”,而是要看 #{} 和 ${} 是否混用;不是“用了 JDBC 就危险”,而是要看是否出现 String.format("SELECT * FROM user WHERE id = %s", userId) 这类拼接。
- 所有含用户输入的 SQL 字符串,必须定位到具体行,确认是否经过
PreparedStatement、NamedParameterJdbcTemplate、queryParam(Spring Data JPA)等显式参数化接口 - 禁用
mysql_real_escape_string(PHP)、addslashes(通用)、escape(JavaScript 拼接 SQL 场景)等字符逃逸方案——它们在多字节编码、宽字节注入、JSON 嵌套 SQL 等场景下完全失效 - 存储过程调用也需检查:如
CALL check_user(?)是安全的,但CALL check_user('' + @input + '')是高危的 - 日志中若记录 SQL(如慢查日志模板),确保未把原始请求参数直接塞进 SQL 字符串再打日志
测试与上线前:必须跑通三类边界验证,不能只信单元测试
单元测试容易漏掉编码差异、驱动行为、权限上下文等真实环境因素。必须用真实数据库连接执行以下验证:
- 对每个接受用户输入的接口,发送
' OR '1'='1、1; DROP TABLE users--、1' UNION SELECT password FROM users--三类典型 payload,观察是否返回异常数据、报错信息是否泄露表结构、HTTP 状态码是否为 500 而非 200 - 检查数据库账号权限:应用账号是否拥有
CREATE、DROP、LOAD_FILE、INTO OUTFILE等高危权限?生产库应仅授予SELECT、INSERT、UPDATE、DELETE,且限定到具体表 - 确认数据库错误未回显:如 MySQL 的
sql_mode=STRICT_TRANS_TABLES开启后,部分注入会触发更严格的报错,但 Web 层必须捕获并返回泛化错误,而非原样输出MySQLSyntaxErrorException
运维与监控阶段:靠日志规则和权限审计堵住漏网之鱼
很多注入攻击不立即造成破坏,而是长期潜伏读取敏感字段。单靠“防”不如“可发现+可阻断”。
- 数据库审计日志中启用关键词过滤:匹配
UNION SELECT、information_schema.tables、CONCAT(0x7c,version())、SLEEP(5)等典型指纹,告警并自动 kill 对应会话 - 应用层 WAF 规则不能只依赖开源规则集(如 OWASP CRS),必须补充业务特有模式:例如电商系统中
WHERE sku_id IN (???)后跟超长逗号分隔列表,就可能是批量注入探测 - 定期执行权限比对脚本:对比当前数据库账号实际拥有的权限与最小必要权限清单,发现新增的
GRANT OPTION或跨库访问即触发告警
真正难防的从来不是 ' OR 1=1--,而是那些藏在 JSON 字段解析后二次拼接、Base64 解密后未校验、前端传来的加密 token 在服务端解密再拼进 SQL 的链路。这类点不会出现在静态扫描报告里,只能靠每个环节的“人工确认+边界验证+权限收紧”三重交叉验证来覆盖。

















