☰
AI代码审计实战:从SAST到RAG,如何提升漏洞召回率与降低误报
2026/10/13 11:45:42 网站建设 项目流程

1. 先搞清楚:AI做代码审计到底在审什么

1.1 代码审计的本质不是找Bug,而是找“能被利用的Bug”

很多人一听到“代码审计”四个字,脑子里第一反应就是“找Bug”。这个理解不能说错,但太粗糙了。Bug和漏洞是两码事。一个数组越界可能导致程序崩溃,这是Bug;但如果这个越界恰好能被外部输入触发,并且能覆盖到返回地址或者关键变量,那它就是一个可被利用的安全漏洞。代码审计的核心目标,是找出后者——那些在特定条件下能被攻击者转化为实际控制权的缺陷。

我做了这么多年安全审计,最深的体会是:审计报告里写“存在SQL注入风险”和写“第237行拼接了未过滤的userId参数,通过构造单引号闭合可绕过鉴权查询到全表数据”,这两者的价值差着十万八千里。AI目前能做的,大部分时候是前者;而真正值钱的,是后者。

所以当我们问“AI做代码审计靠谱吗”的时候,真正在问的是:AI能不能理解代码的上下文语义、能不能追踪数据流、能不能判断可达性、能不能评估利用条件。这四个问题,每一个都是硬骨头。

1.2 传统代码审计的三条路,AI分别能插在哪

传统代码审计大致分三种做法:

  • 人工审计:靠经验丰富的安全工程师逐行读代码,结合业务逻辑推理。优点是能发现逻辑漏洞和复杂调用链问题,缺点是慢、贵、不可规模化。
  • SAST静态分析工具:比如基于AST、CFG、数据流分析的自动化工具。优点是快、可集成CI/CD,缺点是误报率高得离谱,经常一个项目扫出几千条告警,真正能利用的不到百分之几。
  • DAST动态扫描:跑起来打流量,看响应。优点是误报相对低,缺点是覆盖不全,很多需要特定前置条件的漏洞根本触发不到。

AI切入的位置,目前主要集中在SAST这一层——用大模型来替代或增强传统的规则引擎和模式匹配。但这里有个关键区别:传统SAST是基于规则的,AI是基于概率的。规则说“这个函数参数没过滤就是危险”,AI说“根据我见过的代码模式,这段代码有87%的概率存在注入风险”。前者确定但死板,后者灵活但不可靠。

1.3 一个真实的对比场景

我拿一个模拟项目做过测试。项目里有一段代码,从HTTP请求里取了一个参数,经过两层函数调用后拼接到SQL语句里。中间有一层做了白名单校验,但白名单的匹配逻辑写错了——用的是startsWith而不是全等匹配,导致admin' OR '1'='1这种输入能绕过。

传统SAST工具扫出来的结果是:第45行存在SQL注入风险(因为检测到字符串拼接)。但它没发现白名单校验其实已经部分生效,也没发现绕过条件。AI模型扫出来的结果是:第45行存在SQL注入风险,置信度中等,建议人工确认第32行的校验逻辑是否完备。它多给了一个线索,但依然没有精确指出startsWith的问题。

最后是我人工看出来的。这说明什么?AI能帮你缩小范围,但最后一公里的判断,目前还得靠人。

2. AI代码审计的四种主流玩法与各自的天花板

2.1 直接让大模型读代码:最省事,也最不靠谱

最简单的做法就是把代码文件贴给大模型,问它“这段代码有什么安全问题”。我试过用几个主流模型跑同一个有漏洞的Java文件,结果很有意思。

有的模型能准确指出“这里存在硬编码密钥”,因为这是模式化的东西,训练数据里见得太多了。但当我问它“这个反序列化入口能否被外部可控数据触发”时,模型开始胡言乱语,因为它看不到调用方代码,无法判断数据来源。

这种玩法的天花板很低,原因有三:第一,上下文窗口有限,大项目根本贴不进去;第二,模型没有全局数据流视图,只能做局部模式匹配;第三,模型倾向于“讨好”提问者,你问它有没有漏洞,它总能给你编出几条来。

如果你只是想快速过一遍小脚本或者单个文件,直接问大模型是可以的。但千万别拿它去审一个几十万行的企业级项目,那是自欺欺人。

2.2 RAG增强:把知识库和代码库一起喂给模型

RAG(检索增强生成)是这两年比较务实的做法。思路是:先把项目的代码结构、历史漏洞库、安全编码规范都向量化存起来,审计的时候先检索相关片段,再让模型基于检索结果做判断。

我搭过一套这样的流程:用AST解析器把项目拆成函数级别的块,每个块生成摘要和向量,存进向量数据库。审计时,针对每个敏感函数(比如执行SQL、调用系统命令、反序列化),检索出它的调用链上下游,拼成上下文喂给模型。

这套东西比直接贴代码强不少,因为它给了模型局部全局视图。实测下来,对于注入类和路径穿越类漏洞,召回率能到70%左右,但误报依然很多。最大的问题是检索质量不稳定——有时候检索出来的上下游代码根本不是同一条数据流,模型就被带偏了。

2.3 微调专用模型:成本高,但效果确实好

有些团队走的是微调路线。拿开源基座模型,用大量标注好的漏洞代码和修复代码做微调,让模型学会“什么样的代码模式对应什么样的漏洞”。

我参与过一个内部项目,用了几万条CVE对应的修复前后代码对,微调了一个7B参数的模型。效果确实比通用模型好,尤其是在识别“修复模式”上——给它一段代码,它能判断出“这段代码看起来像是修复过某个漏洞的,但修复不完整”。这个能力在审计老项目时特别有用,因为很多漏洞都是修复了一半,攻击者换个绕过方式又能打。

但微调的成本不低:数据标注要钱,训练要卡,迭代要时间。而且模型会过拟合到训练集里的漏洞类型,遇到新型漏洞就抓瞎。所以这条路适合有持续安全需求的大团队,小团队玩不起。

2.4 多智能体协作:让AI自己吵架

最近比较火的一个方向是多智能体。简单说就是搞几个不同角色的AI:一个负责找漏洞,一个负责反驳,一个负责验证。找漏洞的说“这里有问题”,反驳的说“你凭什么说有问题,数据流追踪给我看”,验证的再去跑一遍。

我试过一个开源的多智能体审计框架,配置了三个角色:审计员、评审员、利用验证员。跑下来的感受是:确实能过滤掉一部分误报,因为评审员会挑战审计员的结论。但代价是token消耗翻了好几倍,而且有时候评审员和审计员会陷入无限循环,谁也说服不了谁。

这个方向有前途,但目前还处于玩具阶段。适合做研究或者对误报率极度敏感的场景,不适合日常生产使用。

3. 实操:搭一套AI辅助代码审计的最小可行流程

3.1 环境准备与工具选型

如果你想自己搭一套试试,不需要一上来就搞微调。我建议从最简单的组合开始:

  • 代码解析:用tree-sitter或者语言自带的AST解析库,把代码拆成函数和类。
  • 向量检索:ChromaDB或者Qdrant,本地跑就行,不用上云。
  • 模型:先用API调用通用大模型,等流程跑通了再考虑本地部署。
  • 编排:LangChain或者自己写Python脚本,别用太重的框架。

这套组合的好处是门槛低,一台开发机就能跑。坏处是对于超大项目,向量检索的延迟会比较明显,需要做分片和缓存。

3.2 第一步:把代码拆成“审计单元”

不要整个文件往模型里塞。我的做法是:以函数为基本单元,但每个函数要带上它的“上下文包”——包括它所在的类、它调用的敏感函数列表、调用它的上游函数签名。

具体操作上,我用tree-sitter解析出所有函数定义,然后对每个函数做三件事:

  1. 提取函数体内的敏感操作(SQL执行、文件操作、命令执行、反序列化等)。
  2. 向上追踪两层调用者,向下追踪两层被调用者,生成调用链摘要。
  3. 把函数体、调用链摘要、敏感操作列表拼成一个结构化的文本块。

这个文本块就是喂给模型的“审计单元”。实测下来,一个中等规模的Java项目(约5万行),能拆出3000到5000个审计单元。每个单元大概500到1500个token,完全在模型的处理范围内。

3.3 第二步:设计提示词,让模型说“人话”

提示词的设计直接决定输出质量。我踩过的坑是:一开始用“请找出这段代码中的安全漏洞”,结果模型把代码风格问题、性能问题、甚至命名不规范都给我列出来了。

后来改成结构化提示词,效果好了很多。核心是三点:

  • 限定漏洞类型:明确告诉模型只关注注入、路径穿越、反序列化、权限绕过这几类。
  • 要求给出证据:让模型指出具体行号、具体变量、具体数据流路径。
  • 要求给出置信度:让模型自己评估“高/中/低”,方便后续排序。

我用的提示词模板大致是这样的:

你是一名安全审计员。请分析以下代码单元,判断是否存在安全漏洞。 代码单元: {code_unit} 调用链上下文: {call_chain} 请只关注以下漏洞类型:SQL注入、命令注入、路径穿越、不安全反序列化、权限校验缺失。 对每个疑似漏洞,输出: 1. 漏洞类型 2. 具体行号和代码片段 3. 数据流路径(从输入到危险操作) 4. 置信度(高/中/低) 5. 如果置信度为中或低,说明还需要什么信息才能确认 如果不存在上述漏洞,输出“未发现明显漏洞”。

这个模板的关键在于最后一条:让模型主动说出“我还需要什么信息”。这能帮你判断哪些告警值得人工跟进,哪些可以直接忽略。

3.4 第三步:后处理与告警排序

模型输出的原始告警不能直接给人看,必须做后处理。我做了三件事:

  • 去重:同一个漏洞可能被多个审计单元报出来,按行号和漏洞类型去重。
  • 排序:按置信度、漏洞类型危险程度、代码是否在关键路径上综合排序。
  • 聚合:把同一个调用链上的告警聚合在一起,方便人工一次性看完。

排序的权重我是这么定的:置信度高的直接排前面;置信度中的,如果漏洞类型是反序列化或者命令注入,往前排;置信度低的,如果代码在鉴权模块或者对外接口上,也往前排。剩下的低置信度告警,统一放到“待观察”列表里,不占用主要精力。

3.5 第四步:人工复核与反馈闭环

AI审完之后,必须有人工复核环节。但人工复核不是从头读代码,而是带着AI给的线索去验证。我的做法是:对于高置信度告警,直接看模型指出的行号和数据流,确认是否可达;对于中置信度告警,重点看模型说的“还需要什么信息”,然后去补那个信息。

复核的结果要反馈回系统。如果模型报了一个误报,把这段代码和“误报”标签存起来;如果模型漏了一个漏洞,把这段代码和“漏报”标签存起来。攒够一定数量后,可以用来优化提示词,或者做少量微调。

这个闭环是整套流程里最容易被忽略、但最重要的一环。没有反馈,系统永远不会进步。

4. 实测数据:AI审计到底能发现多少真漏洞

4.1 测试集设计与基准对比

我拿三个模拟项目做了对比测试,分别是:一个Spring Boot的Web应用(约3万行)、一个Python的API服务(约1.5万行)、一个C语言的网络处理模块(约8000行)。每个项目里人工植入了10个漏洞,涵盖注入、路径穿越、反序列化、权限绕过等类型。

对比的基线是:纯人工审计、传统SAST工具、AI辅助流程(就是上面搭的那套)。评价指标是召回率(发现了多少真漏洞)和误报率(报了多少假漏洞)。

审计方式召回率误报率平均耗时
纯人工90%5%40小时
传统SAST65%85%2小时
AI辅助75%45%6小时

这个数据很说明问题。AI辅助的召回率比传统SAST高,误报率比传统SAST低,但依然远不如人工。耗时上,AI辅助是人工的七分之一左右,但比纯工具慢,因为多了模型推理和后处理的时间。

4.2 AI擅长什么,不擅长什么

从测试结果看,AI在以下几类漏洞上表现较好:

  • 硬编码凭证:几乎100%能发现,因为这是纯模式匹配。
  • 明显的SQL拼接:能发现大部分,尤其是没有参数化查询的直白写法。
  • 路径穿越:如果代码里有../相关的拼接,能发现。
  • 不安全的反序列化:如果用的是已知的危险库和危险方法,能发现。

AI表现较差的地方:

  • 逻辑漏洞:比如权限校验的顺序问题、条件竞争、业务逻辑绕过。这些需要理解业务意图,AI目前做不到。
  • 复杂的绕过场景:比如前面说的白名单startsWith问题,AI很难精确判断。
  • 跨文件数据流:如果数据流经过多个文件、多个中间件,AI的追踪能力会急剧下降。
  • 新型漏洞:训练数据里没有的漏洞模式,AI基本抓瞎。

4.3 一个让我印象深刻的漏报

测试中有一个漏洞,是在一个自定义的加密工具类里,密钥派生函数用了一个固定的盐值。这个漏洞人工审计一眼就能看出来,但AI没报。

我后来分析原因:模型看到了这个盐值,但它不认为这是“安全问题”,因为在训练数据里,固定盐值通常出现在测试代码或者示例代码里,模型学会了“忽略这种模式”。但实际上,这段代码在生产环境里被调用了。

这个案例说明,AI的“经验”来自训练数据,而训练数据里的偏见会直接传导到审计结果里。人工审计员知道“生产代码里的固定盐值就是问题”,但AI不知道。

5. 常见问题与避坑指南

5.1 为什么我的AI审计全是误报

这是最常见的问题。原因通常有三个:

第一,提示词太宽泛。你问“有没有安全问题”,模型就会把代码风格、性能隐患、甚至命名不规范都算进去。解决办法是严格限定漏洞类型,并且要求模型给出具体的数据流证据。

第二,代码单元切得太碎。如果你把一个函数切成好几段分别喂给模型,模型看不到完整的数据流,就会把正常的参数传递误判为注入。解决办法是以函数为最小单元,并且带上调用链上下文。

第三,没有做后处理。模型的原始输出必须经过去重、排序、聚合,否则几千条告警堆在一起,看起来就像全是误报。

5.2 AI审计能替代人工吗

不能。至少目前不能。AI能做的是把人工从“逐行读代码找可疑点”这种重复劳动里解放出来,让人工专注于“验证可疑点是否真的可利用”这种高价值判断。

我自己的做法是:AI先跑一遍,把告警按置信度排序,高置信度的我快速验证,中置信度的我带着问题去读代码,低置信度的我直接忽略。这样能把人工审计的效率提升两到三倍,但最终的决定权还在人手里。

5.3 怎么判断一个AI审计结果值不值得信

我总结了一个简单的判断标准:看它有没有给出具体的数据流路径。如果模型只说“第45行有SQL注入”,但不告诉你数据从哪来、怎么流到第45行的,那这条告警的可信度就很低。如果模型能画出“从HTTP参数到DAO层到SQL拼接”的完整路径,那即使置信度标的是“中”,也值得花时间验证。

另外,看模型有没有说“我不确定”。好的审计结果应该包含“需要进一步确认的信息”,而不是斩钉截铁地说“一定有漏洞”或者“一定没有漏洞”。安全审计的本质是概率判断,不是二值判断。

5.4 成本怎么控制

AI审计的成本主要在token消耗上。一个5万行的项目,如果每个审计单元都调一次模型,大概要消耗几百万token。按API价格算,一次全量审计可能要几十到上百块。

控制成本的办法有几个:第一,只对包含敏感操作的函数做AI审计,纯业务逻辑函数用传统规则过滤;第二,用缓存,同一个函数如果没改过,不用重复审计;第三,分级处理,先用小模型做初筛,只把可疑的送给大模型做精判。

我自己的做法是:CI/CD里只跑增量审计,每次只审改动的文件;全量审计一周跑一次,放在晚上低峰期。

5.5 模型选型有什么讲究

如果你用API,不同模型在代码审计上的表现差异很大。我的经验是:代码生成能力强的模型,不一定审计能力强。审计需要的是怀疑精神和逻辑推理,而不是代码补全能力。

选模型的时候,重点看它在“代码理解”和“逻辑推理”类基准上的表现,而不是“代码生成”类基准。另外,上下文窗口越大越好,因为审计需要看调用链。如果窗口太小,调用链被截断,审计质量会断崖式下降。

6. 我个人在实际操作中的几点体会

第一,AI审计最适合的场景是存量老项目的快速摸底。一个新项目从零开始写,安全编码规范落实到位,其实不太需要AI审计。但一个跑了五年的老项目,代码几十万行,人工审根本审不过来,这时候AI辅助能帮你快速定位高风险区域。

第二,不要追求“全自动”。我见过一些团队想把AI审计做成完全自动化的流水线,告警直接生成工单派给开发。结果开发被海量误报淹没,最后整个系统被弃用。正确的做法是AI出线索,人工做判断,判断结果反馈给AI做优化。

第三,审计结果的呈现方式很重要。同样的告警,如果按文件路径罗列,人工看起来会很累;如果按调用链聚合,并且标注出“这条链路上有三个可疑点”,人工就能一次性看完一条完整的数据流。我在实际项目里会把告警按“入口点”聚合——所有从同一个HTTP接口或同一个消息队列消费者触发的告警放在一起,这样人工验证的时候可以顺着业务逻辑走一遍。

第四,别忘了审计之外的事。代码审计只是安全左移的一环,真正要减少漏洞,还得靠安全编码培训、代码评审规范、依赖组件扫描这些基础工作。AI审计是锦上添花,不是雪中送炭。如果团队连基本的参数化查询都没落实,AI审出再多漏洞也修不过来。

最后分享一个我常用的技巧:在让AI审计之前,先让它做一遍“代码摘要”。让模型用自然语言描述每个模块的功能和数据流向,然后再基于摘要做审计。这样做的好处是,模型在摘要阶段已经建立了一个全局视图,审计阶段就不容易犯“只见树木不见森林”的错误。实测下来,这个预处理步骤能把误报率降低大概十到十五个百分点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询