ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用大模型自动甄别 SAST 扫描结果的误报:我的实践与思考

用大模型自动甄别 SAST 扫描结果的误报:我的实践与思考 为什么需要 LLM 来做误报甄别如果你用过 SAST静态应用安全测试工具大概率有过这样的体验扫描报告里塞满了“SQL Injection”“SSRF”“Command Injection”的告警逐个点开一看大部分都是误报。有研究显示在某些语言和框架中SAST 标记的命令注入告警误报率可以高到99.5%一项覆盖 2166 个告警的分析发现SAST 工具产生的噪声比例高达91%。传统 SAST 工具的设计哲学是“宁可错杀一千不可放过一个”——优先保证不漏报代价就是大量误报。安全工程师每天的工作变成了在告警海洋里“淘金”效率低且容易疲劳。大模型的出现提供了一个新的思路让 LLM 充当“第二意见裁判”。SAST 已经完成了“找可疑点”这个最难的部分LLM 只需要回答一个相对简单的问题“在这个具体的代码上下文里这个被标记的路径真的可达、真的可被利用吗”近一两年的研究趋势也印证了这一点LLM 直接做漏洞检测效果一般但用来做 SAST 结果的误报过滤效果显著。一项 2025 年的 benchmark 显示LLM 过滤器可以将 Semgrep 的误报减少88.6%同时只损失 3.1% 的真阳性。我的方案从数据流提取到 Prompt 构造我的核心思路和很多研究者的方向一致不让 LLM 从零找漏洞而是给它 SAST 已经找到的“线索”让它做判断。第一步提取 SAST 结果中的关键信息从 SAST 报告中提取数据流Data Flow或调用栈从 source 到 sink 的完整路径文件名和行号精确定位可疑代码的位置这一步是基础。很多现代 SAST 工具如 CodeQL支持 SARIFStatic Analysis Results Interchange Format格式输出SARIF 结构化地包含了 issue 位置、CWE 分类、数据流 trace 等信息非常适合作为 LLM 的输入接口。第二步提取调用链上的代码片段仅有行号是不够的。我需要提取从函数入口到漏洞触发行之间的代码。这样 LLM 才能看到调用这个函数的函数做了什么数据在传递过程中经过了什么处理是否有条件判断、清洗逻辑# 伪代码示意从调用栈提取代码片段 def extract_code_context(file_path, function_entry_line, vuln_line, num_lines50): 提取从函数入口到漏洞行的代码片段 with open(file_path, r) as f: lines f.readlines() # 提取函数入口到漏洞行的范围 start max(0, function_entry_line - 1) end min(len(lines), vuln_line num_lines) context .join(lines[start:end]) return context第三步构造 PromptPrompt 的质量直接决定了判断的准确性。我的 Prompt 包含以下要素漏洞类型SQL Injection、SSRF、Command Injection、XXE 等项目特定的 sanitizer/validator 信息这是最关键的部分。比如“这个项目中以validate开头的函数是输入校验函数”“Sanitizers.escape()是经过安全审计的转义函数”框架信息比如 Java Spring、Python Flask、Node.js Express调用栈信息从 SAST 报告中提取的 source → sink 路径相关代码片段从上一步提取的代码你是一个安全代码审计专家。以下是一段被 SAST 工具标记为潜在 SQL Injection 的代码路径。 项目背景 - 使用 Java Spring 框架 - 数据访问层使用 MyBatis - 项目中的输入校验函数以 validate 开头 - 项目中的 SQL 参数绑定通过 #{} 语法实现 SAST 数据流 UserController.searchUser(request.getParameter(name)) → UserService.search(String name) → UserMapper.findByName(String name) 相关代码 [此处插入提取的代码片段] 请判断这个 SAST 告警是真实的漏洞还是误报 输出格式 - 是否误报是/否 - 判断理由 - 自信心0-100%第四步解析 LLM 输出要求 LLM 返回结构化输出JSON 格式最佳便于后续自动化处理{ is_false_positive: true, reason: 代码在第 42 行调用了 validateInput() 函数该函数在项目中用于校验用户输入且 MyBatis 的 #{} 语法实现了参数化查询不存在 SQL 注入风险。, confidence: 0.85 }实践中效果好的场景根据我的实验以下场景 LLM 判断准确率较高1. 硬编码密钥/密码类漏洞这类漏洞的误报特征非常明确。我在 Prompt 中详细描述了 SAST 的判定逻辑和常见误报模式密钥长度大于 16 字节密钥包含完整的英文单词可能是测试用占位符密钥出现在配置示例文件中LLM 对这类规则的理解和执行相当准确。2. 有明确 sanitizer 的注入类漏洞SQL Injection、SSRF、Command Injection、XXE 这几类如果项目中存在命名规范清晰、用法一致的 sanitizer 函数LLM 能够很好地识别。比如看到StringEscapeUtils.escapeSql()或PreparedStatement的使用LLM 会判断为误报。有研究验证了这一点Qwen2.5-Coder-7B 在 CWE-22路径遍历上实现了83.6% 的误报减少。仍然难以判断的场景及原因分析a) 正则表达式校验如果 sanitizer 是通过正则表达式实现的LLM 需要理解正则的逻辑才能判断输入是否被充分校验。这对 LLM 来说比较困难——正则表达式的语义对模型来说不够“自然”。b) 复杂的 if 条件判断if (input ! null input.length() 100 !input.contains() Pattern.matches([a-zA-Z0-9], input)) { // 安全的路径 }LLM 需要追踪多个条件之间的逻辑与关系容易遗漏或误判。c) 通过 throw exception 实现的校验这是我在实践中发现的一个特别棘手的情况public void validateInput(String input) throws ValidationException { if (input.contains()) { throw new ValidationException(Invalid input); } // 没有返回值 }SAST 工具的数据流分析通常追踪的是数据依赖。validateInput没有返回值被调用后数据流“看起来”没有变化SAST 可能认为这个调用不影响 taint 传播从而不把它纳入 sanitizer 的范畴。但 LLM 看到这段代码后理论上应该理解“如果输入包含单引号函数会抛异常后续代码不会执行”。然而实践中LLM 经常忽略这种控制流层面的保护。BugLens 的研究者也发现了类似问题静态分析器因为“简化漏洞建模”而忽略路径约束条件导致误报LLM 需要被显式引导去推理这些约束才能纠正。d) 外部库中的 sanitizerimport com.company.internal.security.InputValidator; public String process(String input) { String safe InputValidator.sanitize(input); // 公司内部库 return query(safe); }如果InputValidator.sanitize()的实现代码不在当前项目中LLM 无从知道这个函数到底做了什么。它可能只是检查长度也可能做了完整的 SQL 转义。没有源码LLM 无法判断。学术界的最新进展ZeroFalseLLM 辅助静态分析的精度提升ZeroFalse 是一个直接针对 SAST 误报问题的框架其核心洞察是SAST 工具为了保证“不漏报”采用了保守近似假设所有代码路径都可达从而产生大量误报。ZeroFalse 的做法是将 SAST 的 SARIF 输出与 LLM 的语义推理结合。SARIF 提供了结构化的数据流 traceLLM 在此基础上做“语义 adjudication”——判断这个特定的路径在语义上是否真的可达、是否真的危险。论文中给出了一个经典示例CodeQL 标记了一段 SQL 注入因为 tainted 的param流入了 SQL 查询字符串。但代码逻辑中param被放入 list 后移除了第一个元素最终赋值的是受控的moresafe。静态分析器只看到param到达了 sink却无法理解 list 操作的语义。LogiSec of Thoughts反证法推理框架LogiSec of Thoughts 采用了一种有趣的策略它不直接问 LLM“这是不是漏洞”而是引导 LLM 用反证法推理——“如果这不是漏洞需要什么条件成立”然后逐步验证这些条件。这种方法在减少“过度自信的错误判断”方面有潜力。因为 LLM 如果被直接问“是不是漏洞”可能会基于表面模式给出高置信度的错误答案而反证法迫使它走一步想一步。SAST-Genius实证效果SAST-Genius 的报告给出了一个很有说服力的数据在 25 个开源项目上混合 LLMSAST 方案将误报从225 个减少到 20 个精度从35.7% 提升到 89.5%。这个数据说明LLM 辅助的 SAST 误报过滤在真实项目上可以达到生产可用的精度水平。对未来的思考结合我的实践和学术界的进展我认为有几个方向值得探索第一反馈循环。目前的方案是单方向的SAST → LLM → 结果。但如果 LLM 的判断结果能够反馈给 SAST 工具用于调整规则的灵敏度就形成了双向协作。有研究提出了这种“检测-过滤-反馈”的三阶段框架。第二针对控制流保护的显式建模。对于 throw exception 类的 sanitizer可以在 Prompt 中显式要求 LLM 检查“是否有异常抛出路径会阻止后续代码执行”。BugLens 的做法是引入“约束评估器”Constraint Assessor专门推理路径约束条件。第三外部库的“信任声明”。对于公司内部库的 sanitizer可以维护一个“信任函数清单”在 Prompt 中声明“以下函数已经过安全审计视为有效 sanitizer”。这样 LLM 就不需要看实现代码也能做出判断。第四小模型的本地化部署。大模型 API 调用有成本和数据隐私问题。有研究表明代码专用的小模型如 Qwen2.5-Coder-7B在特定 CWE 类别上可以达到不错的效果适合对数据安全要求高的场景。总结用大模型甄别 SAST 误报核心思路是让 SAST 做它擅长的找可疑点让 LLM 做它擅长的理解上下文。我的实践证明对于有明确 sanitizer 的注入类漏洞和硬编码密钥类问题这种方法效果显著。但对于依赖正则、复杂条件判断、控制流保护或外部库的场景LLM 的理解能力仍有明显不足。学术界的研究方向与我的观察高度一致混合方案SAST LLM是目前最务实的选择纯 LLM 扫描的精度还远不够可靠。而 90% 以上的误报减少率在真实项目上已经可以实现这让 LLM 辅助的误报甄别从一个“有趣的想法”变成了一个“可以投入生产的方案”。
返回列表