位置:首页 > SQL > 如何用 SAST 工具扫描 SQL 注入风险

如何用 SAST 工具扫描 SQL 注入风险

时间:2026-08-25  |  作者:极客少年  |  阅读:0

目录

  1. 为什么 SAST 能发现 SQL 注入,但不能直接全信
  2. 扫描前必须确认的 3 个配置点
  3. 常见误报和漏报,通常出在哪些地方
  4. 扫描结果怎么验证,才能形成闭环
  5. 比调参更重要的,是改掉危险写法

前言

SAST 能扫出不少 SQL 注入问题,但报告里的每一条告警都不能直接当成漏洞结论。要让扫描结果真正可用,关键不是只看工具有没有报,而是搞清它如何识别数据流、哪些配置会影响准确率,以及怎样用人工复核和 DAST 把风险验证闭环。

SAST 扫 SQL 注入,难点通常不在“能不能扫出来”,而在“扫出来的东西能不能信”。很多团队把代码丢进工具后,要么告警太多无法处理,要么线上出了问题却没在报告里提前看到。

这类问题的根源,往往是对 SAST 的能力边界、规则配置和验证方式理解不够。下面按“为什么能发现、扫描前怎么配、误报漏报怎么处理、结果怎么验证”四个环节梳理,帮助你判断一条 SQLi 告警到底值不值得优先修。

为什么 SAST 能发现 SQL 注入,但不能直接全信

SAST 之所以能识别 SQL 注入,本质上靠的是 AST 分析和污点追踪。工具会把 HTTP 参数、Cookie、路径变量这类用户输入标记为污染源,然后沿着数据流往后看,判断这些值是否在未净化、未参数化的情况下流入数据库执行函数。

例如下面这种最典型的字符串拼接:

String sql = "SELECT * FROM users WHERE id = " + userId;

在这类场景里,SAST 往往能比较稳定地命中问题,因为它既能看到字符串拼接,也能识别后续是否调用了数据库执行接口,例如 Statement.execute()jdbcTemplate.query()

但这并不意味着扫描报告可以直接等同于漏洞清单。SAST 擅长找“明显的数据流问题”,却不擅长理解所有运行时上下文。像二次注入、动态表名或列名拼接、ORM 中错误使用 ${} 这类场景,经常会漏掉;而某些看起来危险、实际并不会执行 SQL 的代码路径,又可能被误报出来。

因此,正确的理解应该是:SAST 很适合做首轮发现和批量筛查,但不能替代人工判断,更不能替代真实请求下的验证。

扫描前必须确认的 3 个配置点

SQL 注入扫描不是把仓库交给工具就结束了。规则没开、source 没标、safe sink 没排除,最后得到的报告参考价值会很低。上线扫描前,至少要先确认下面三个点。

展示 SAST 识别 SQL 注入时的污点流转和关键配置点关系图
SQL 注入扫描配置关系图把 source、数据流分析与安全调用排除配置对齐,扫描结果才有参考价值。

1. 启用污点分析或数据流分析

如果工具没有真正进入污点分析模式,很多 SQLi 规则只会停留在“关键词匹配”层面,结果要么太浅,要么噪声很多。

常见例子包括:

  • SonarQube 中启用对应规则,例如 java:S2077
  • CodeQL 中加载 sql-injection.ql 查询

这一步的目的,是让工具知道“不只是找 SQL 字符串”,而是要看“用户输入是否流到了执行点”。

2. 明确哪些入口属于用户可控输入

默认情况下,Spring Controller 中的 @RequestParam@PathVariable 往往会被当作污染源处理。但如果项目用了自定义封装对象来承接请求参数,SAST 不一定能自动识别这些字段的污染属性。

这时就需要在工具配置里显式声明:哪些类、哪些字段属于 source。否则,明明用户可控的数据可能在扫描视角里变成“普通变量”,后续再危险的拼接也未必会报。

3. 确认已知安全调用能被正确排除

SQL 注入检测不能只会报问题,也要能识别哪些写法已经做了参数化处理。像下面这些常见安全调用,原则上不应和字符串拼接一起被混为一谈:

  • PreparedStatement
  • NamedParameterJdbcTemplate
  • MyBatis 的 #{} 语法

这里还有一个容易忽视的点:规则库版本必须跟得上框架语义。比如旧版 SonarJava 在某些 Hibernate HQL 参数化查询上就可能出现误报。如果规则库过旧,即便代码已经按规范写了,报告也可能把你带偏。

常见误报和漏报,通常出在哪些地方

实际项目里,最让人头疼的往往不是“没有告警”,而是“不知道哪些该信”。下面几类场景最常见,也最值得优先处理。

展示 MyBatis、日志打印和 JPA nativeQuery 等场景中的误报与漏报类型对比
误报与漏报高发场景误报和漏报往往集中在动态 SQL、日志链路和框架语义识别不足这几类位置。

MyBatis 动态 SQL 容易漏报

典型例子是:

select * from ${tableName}

如果工具没有启用动态 SQL 上下文分析,或者 MyBatis 适配能力不足,这类写法很可能直接被跳过。可它在风险上又偏偏很高,因为 ${} 是直接拼接,不做参数化。

更稳妥的处理方式有两种:

  • 改成 #{tableName} 并增加白名单校验
  • 在 SAST 配置中显式启用 MyBatis 插件或相关规则支持

需要注意的是,表名、列名这类对象本身很多时候并不能简单参数化,因此白名单往往比“转义一下”更关键。

日志打印经常制造误报

另一类高频噪声来自日志。例如:

log.info("executing: " + sql)

这里的 sql 虽然出现在字符串拼接里,但日志打印本身并不是数据库执行点。部分规则如果对调用链判断不够精确,就可能把这种语句误认为“SQL 被执行”。

遇到这种情况,通常有两个处理方向:

  • 在代码里加 // NOSONAR 注释,明确标记该处已人工确认
  • 在规则配置中忽略日志相关方法调用链

前者适合少量例外,后者适合项目里这类模式很多、长期会反复出现的情况。

框架语义识别不到位也会失真

框架封装越多,SAST 越依赖对框架语义的理解。比如 Spring Data JPA 的 @Query(nativeQuery = true),如果里面还夹带了字符串拼接,部分工具未必能准确识别危险性。

这类问题的治理思路,通常不只是“调规则”,还要尽量统一编码方式。一个常见建议是:能不用 nativeQuery 就不用,优先改成 JPQL 或 Criteria API。这样既减少真实风险,也降低工具识别难度。

扫描结果怎么验证,才能形成闭环

真正有价值的工作,不是统计报表里有多少高危,而是把每条告警放回代码路径里核实。判断一条 SQLi 是否成立,至少要追三件事:输入是否可控、中间是否真的安全、结尾是否落到执行点。

展示 SAST 告警从人工复核到 DAST 验证的闭环步骤图
SQLi 告警验证闭环只有把告警放回真实数据路径,再结合动态验证,SQL 注入扫描才算真正闭环。

先人工走通输入到执行的链路

对每条告警,都应该反向追溯数据流:

  • 源头是不是用户真实可控输入
  • 中间有没有所谓“过滤”函数,但实际上防护无效
  • 最终是否真的进入了 executeUpdate()queryForObject() 这类执行点

例如 StringEscapeUtils.escapeSql() 这类处理,很多时候并不能真正防住 UNION 注入。如果工具因为看到“有过滤动作”就降低风险级别,人工复核就必须把这一层补回来。

重点盯 WHERE、ORDER BY、GROUP BY 的动态拼接

不少团队会优先关注 WHERE 条件里的拼接,但实际排查时,ORDER BYGROUP BY 也必须重点看。原因很简单:SAST 对排序字段拼接的检测通常不够强,而这又是盲注和绕过场景里很常见的入口。

例如:

"ORDER BY " + sortField

这种写法在业务上看似只是排序字段可配,实际如果没有白名单控制,同样可能演变成注入面。

再用轻量 DAST 做交叉确认

人工看过数据流后,最好再做一次轻量动态验证。一个常见做法,是用 OWASP ZAP 对告警接口发送类似下面的测试载荷:

' OR 1=1 --

如果 SAST 报风险,而 ZAP 也能在接口行为上打出异常结果,比如返回了不应出现的数据、过滤逻辑失效或报错信息异常,那么这条问题的优先级就应显著上升。

也就是说,真正闭环的判断方式不是“工具说有”,而是“SAST 指出了代码路径,人工确认了可达性,DAST 证明了风险可以触发”。做到这一步,扫描结果才算从告警变成了可处置的问题。

比调参更重要的,是改掉危险写法

多数团队真正卡住的,从来不是工具不会扫,而是面对告警时不愿意改代码习惯。SAST 报出的每一条 SQL 注入风险,背后通常都对应着一个没有参数化的 Statement,或者一个被滥用的 ${}

所以,扫描配置当然要做,规则调优也有必要,但最终决定效果的还是代码本身:能否统一使用参数化查询,能否减少动态拼接,能否限制框架里那些高风险、低收益的写法。把这些问题改掉,往往比继续在工具参数里打转更有价值。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多