为什么有的 AI 能稳定挖漏洞,有的却只会“胡说八道”?
这两年,安全圈里关于“LLM 能不能做代码审计”的争论一直没停过。
乐观派认为,大模型已经能读代码、理解业务逻辑、识别危险模式,拿它做漏洞挖掘只是时间问题;悲观派则认为,LLM 连完整调用链都经常追不明白,更别说在大型 Java 项目里稳定挖 0day 了。
这两种说法都没错,但都没有说到根上。
真正的问题,从来不是 LLM 会不会审计代码,而是它知不知道该如何系统地做代码审计。
它当然能读懂 Java,也能识别 SQL 注入、命令执行、反序列化这些典型危险模式,甚至在局部推理上比很多初级审计员还快。但问题在于,它没有审计员脑子里的那套“工作骨架”:拿到项目先看什么,攻击面怎么摸,哪些模块优先扫,发现可疑点后怎么追调用链,什么时候该深挖,什么时候该止损,最后又怎么把结果整理成一份可信、可复现、可交付的报告。
一个资深审计员做审计,不是“想到哪审到哪”,而是在执行一套多年实战沉淀下来的流程;而裸跑的大模型,往往只有能力,没有纪律。
所以,这篇文章不讨论空泛的 Prompt 技巧,也不讨论“AI 会不会取代审计员”这种老问题。我们真正要拆解的是:
- 如何给 LLM 装上一套“资深 Java 审计员的工作骨架”
- 如何让它从随机游走式审计,变成系统化、可控、可复现的审计引擎
本文聚焦上篇:流程设计与效率优化。重点讲清三件事:
- 裸跑 LLM 做 Java 审计到底差在哪
- Skill 驱动与裸跑的本质差异是什么
- 一套经过实战验证的 6 阶段审计流水线,是怎么设计出来的
下篇再专门展开质量保障:怎么压制幻觉、怎么做覆盖率门禁、怎么判断漏洞成立条件、怎么给结果排优先级。
一、LLM 做代码审计,到底行不行?
这个问题其实没那么复杂:
行,但不能裸跑。
说它行,是因为 LLM 确实具备代码审计所需的很多基础能力。它能快速阅读 Java 项目结构,识别 Controller、Service、DAO、Entity 的职责边界;能从代码里感知危险操作,比如 Runtime.exec()、动态 SQL 拼接、危险反序列化链、模板渲染、表达式求值;也能围绕数据流做初步推理,判断一个参数是不是外部输入,是否经过过滤,最终有没有进入危险 sink。
但说它不能裸跑,是因为真正的代码审计,从来不是“识别几个危险 API”那么简单。代码审计是一项高约束、高流程、高优先级管理的复杂工作。它要求你始终记住项目背景、组件依赖、认证体系、攻击面分布、关键入口、历史结论、可疑点优先级,以及当前到底覆盖到了哪里、还有哪些文件没看。
而裸跑 LLM 的问题,恰恰就在这里:
它不是不会看代码,而是不会像一个老审计员那样有章法地工作。
换句话说,LLM 缺的不是知识,而是工作方法。
这也是为什么同样一个模型,有人拿它能稳定挖到高质量漏洞,有人跑出来的却是一堆看起来很像回事、实际一验证全是幻觉的报告。差别不在模型本身,而在于你有没有给它一套可以执行的“审计协议”。
而这套协议,就是这里说的 Skill。
二、Skill 不是 Prompt,而是“审计协议”
很多人一提到 Skill,会下意识把它理解成“高级 Prompt 模板”。这其实低估了它的作用。
Prompt 解决的是“这一轮怎么问”。
Skill 解决的是“这整件事该怎么做”。
如果说裸跑 LLM 像一个聪明但缺乏训练的新手,那么 Skill 扮演的角色,更像是把多年经验固化下来的“审计作战手册 + 流程引擎 + 质量规范”。
它不负责教模型“什么是 SQL 注入”,因为模型本来就知道。它真正做的是把资深审计员脑子里的那些隐性经验,转化成明确、可执行、可检查的约束:
- 审计从哪一步开始
- 中间结果存到哪里
- 哪些模块优先
- 哪些文件只做轻量扫描
- 什么情况下升级为深挖
- 如何验证 Source 到 Sink 的可达性
- 哪些结论必须附带行号和证据
- 哪些结果没有验证就绝不能写进最终报告
所以更准确地说:
Skill = 工作流 + 资源调度 + 分层分析 + 质量约束 + 输出规范
它不是给模型“加知识”,而是给模型“装骨架”。
这个骨架一旦建立起来,LLM 的表现就会从“偶尔灵光一现”,变成“持续稳定输出”。
三、裸跑 LLM 做 Java 审计,究竟差在哪?
把一个 Java 项目直接丢给 Claude、GPT 或其他模型,说一句“帮我做安全审计”,大概率会出现下面这几类典型问题。
1. 随机游走,覆盖率极低
裸跑 LLM 最常见的行为就是:看几个它觉得“重要”的文件,发现一个可疑点就一路深挖下去,挖完再回来时,已经忘了自己看过哪些模块、没看哪些模块。
这种方式在几千行的小项目里可能还能凑合,因为代码量不大,哪怕乱走一圈,覆盖率也不至于太差。但一旦进入十万行以上的 Java 项目,这种审计方式就会迅速失控。
它可能在一个 Controller 上花很多时间,却根本没把相邻模块走完;也可能沿着一个命令执行路径追得很深,却把旁边整个认证链都漏掉。最后看起来“分析很深入”,实际上只是对局部进行了超配审计,对全局则接近失明。
2. 幻觉高,报告不可信
这几乎是裸跑 LLM 在代码审计场景里最致命的问题。
模型经常会“记得有这么个逻辑”,但一具体到文件名、方法名、行号、参数名,就开始混。于是你会看到这样的输出:
- 引用了不存在的方法
- 把 A 类的方法说成 B 类调用的
- 行号前后错位
- 描述的数据流链条和实际代码不一致
- 把相似代码块脑补成完整漏洞链
在通用问答里,这种幻觉或许只是体验差;但在安全审计里,幻觉直接意味着报告失真。一份不能复核、不能定位、不能复现的问题清单,本质上就不算合格的审计输出。
3. 深度失衡,资源浪费严重
裸跑模型还有一个问题:它没有优先级感。
它可能在一个 SQL 注入疑点上写出一大段极其详细的分析,连 payload 都替你脑补出来,却完全没去看隔壁那个更危险的认证绕过点;也可能在一堆低风险 Entity 类上停留过久,只因为这些类的字段命名碰巧触发了它的注意。
这不是模型不努力,而是它不知道:
- 什么是入口层代码
- 什么是核心攻击面
- 什么该逐文件深挖
- 什么只需快速扫一遍
- 什么一旦命中高危模式就必须立刻升级分析
没有优先级框架,算力和上下文就会被浪费在错误的地方。
4. 上下文易断,长任务后劲不足
大型 Java 项目审计本质上是一个长任务。
而长任务对 LLM 的考验,从来不是“能不能理解一段代码”,而是“能不能在数十轮分析后仍然保持一致的全局认知”。
裸跑时,模型在前半程建立起来的那些重要理解,比如权限模型、认证中间件、通用输入封装、DAO 层写法、统一异常处理机制,很容易在后半程逐渐遗失。于是你会看到前后判断标准不一致、漏洞命名不一致,甚至前面确认过的假设在后面又被推翻。
5. 结果不可复现,工程价值低
同样一个项目,同样一句“帮我做审计”,不同时间跑两次,结论可能差很多。这意味着它很难纳入工程体系。
裸跑 LLM 更像一次“概率性挖掘”。运气好,真能撞出几个洞;运气不好,输出就完全不稳定。
但真正要在企业里落地的审计体系,要求的不是“偶尔很强”,而是:
- 流程可重复
- 结果可回溯
- 结论可验证
- 输出可交付
这就是为什么裸跑 LLM 很难成为“生产工具”,最多只能算“灵感辅助器”。
四、裸跑和 Skill 驱动,差的不是能力,而是章法
很多人看到这里,会误以为 Skill 的作用是“提升模型能力”。其实不是。
Skill 并没有把一个普通模型直接变成“安全大神”。它做的事情,更像是把原本松散、随机、跳跃的能力,组织成一套可控的系统。
如果非要概括两者差别,可以这么说:
裸跑 LLM 像一个天赋很好、读书很多、反应很快的新人;
Skill 驱动的 LLM,则更像一个被流程、规范、方法论训练过的职业审计员。
它们在局部推理上可能差不多,但在整体执行效果上会出现明显质变:
- 覆盖率从“看运气”变成“门禁强制”
- 幻觉从“随时可能冒出来”变成“被规则压制”
- 优先级从“模型自己感觉”变成“体系明确规定”
- 上下文从“模型记不记得”变成“文件持久化状态”
- 报告从“风格漂移”变成“模板化标准输出”
所以后面要讲的,不是某个神奇 Prompt,而是一整套让 AI 像工程师一样工作的设计。
五、一套完整的 AI Java 审计流水线,应该长什么样?
真正可落地的审计 Skill,不是一段长 Prompt,而是一条严格分阶段的流水线。
这条流水线的核心思想很简单:
不要相信模型的记忆,要相信流程和状态。
也就是说,所有关键中间结果都必须被持久化,所有阶段都要有明确输入和输出,模型只负责在每一阶段执行受约束的任务,而不是靠“记住前面发生过什么”来维持整个审计过程。
一个成熟的设计,通常会分成 6 个阶段。
Phase 0:代码库度量
这一阶段不是让 LLM 找漏洞,而是做工程度量。
用脚本统计项目的 LOC、文件数、模块分布、Controller 数量、Service 数量、配置文件比例等指标,先建立一张基础代码库画像。
这一层看起来不性感,但极其重要。因为没有度量,就无法做后续的资源分配,也无法决定该投多少 Agent、优先扫哪一层。
Phase 1:项目侦察与资源规划
在这一阶段,不是直接开扫,而是先建立项目的审计地图。比如:
- 识别项目是 Spring Boot、Spring MVC,还是传统 Servlet 结构
- 摸清入口层在哪里
- 判断认证授权是注解式、过滤器式,还是自定义拦截链
- 识别配置文件、关键安全组件、第三方依赖调用模式
- 进行 T1 / T2 / T3 分层,为后续资源调度做准备
这是从“代码堆”走向“有结构的目标”的关键一步。
Phase 2:全量分层审计
这是整条流水线的核心阶段。
每个 Agent 按照分配范围对文件逐个审计,但不是所有文件一视同仁,而是根据层级使用不同深度的分析策略。
这一阶段通常同时结合:
- Layer 1 的规则预扫描结果
- Layer 2 的 LLM 双轨审计
- Layer 3 的语义验证能力
也就是说,真正高效的审计不是“LLM 单干”,而是规则、推理、验证三者协同。
Phase 3:覆盖率门禁与补扫
这是很多“AI 审计方案”最容易忽略的一层。
因为只要没有门禁,所谓“全量审计”就只是口号。Phase 3 的职责,就是回答下面这些问题:
- 所有目标文件是否都被读过
- 所有 T1 文件是否完成深度分析
- 所有高危候选点是否完成复核
- 是否仍有关键路径未覆盖
没过门禁,就不能进入最终报告阶段。
这一步,本质上是在把“覆盖率”从一个抽象目标,变成强制执行条件。
Phase 4:规则沉淀
对已经确认的漏洞模式做抽象,把它们转换为静态规则,例如 Semgrep 规则或内部规则引擎配置。
它的意义不只在当前项目,而在后续复用:
- 下次遇到类似项目可直接复扫
- 可集成进 CI/CD 做持续检测
- 可作为组织知识库积累
这一步让单次审计从“项目交付”,升级为“组织能力沉淀”。
Phase 5:标准化报告生成
最后一步不是让模型自由发挥写作文,而是基于预定义模板,从确认后的结果中生成完整报告。通常包括:
- Executive Summary
- 漏洞描述
- 影响范围
- 触发条件
- 调用链证据
- 可利用性判断
- 修复建议
- 优先级评分
这一阶段的重点不是“写得多漂亮”,而是“输出必须稳定、完整、可交付”。
六、EALOC:把算力花在真正值得审的代码上
做大型 Java 项目审计时,一个现实问题很快就会冒出来:
并不是所有代码都值得花同样的分析成本。
一个典型的 Java Web 项目里,真正直接暴露给攻击者的代码,通常只占整个代码库的一小部分。大量代码其实是:
- Entity / DTO / VO
- 简单数据映射类
- 纯配置载体
- 低风险工具封装
这些代码不能说完全没风险,但显然不值得和 Controller、Filter、Security 配置、核心 Service 一样逐行深挖。
如果仍然按原始 LOC 平均投入审计资源,就会出现一个很荒唐的现象:大量时间被消耗在低风险区域,而真正的攻击面没有得到足够深度的分析。
这就是 EALOC 要解决的问题。
什么是 EALOC?
它不是原始代码行数,而是“有效审计代码量”。
核心思想是:不同层级代码,按照不同权重计入审计工作量。
一种常见设计是把代码分成三层:
- T1:Controller、Filter、Interceptor、SecurityConfig、外部接口入口等
- T2:Service、DAO、Mapper、Util、配置文件等
- T3:Entity、VO、DTO、POJO 等数据模型层
然后定义一个加权公式:
EALOC = T1 × 1.0 + T2 × 0.5 + T3 × 0.1
这个公式背后的逻辑非常符合实际审计经验:
- T1 是外部攻击面,必须深挖
- T2 是业务逻辑中枢,需要重点看关键路径
- T3 大多是低风险承载类,通常只需轻量模式匹配
这样一来,一个原始 LOC 很大的项目,实际“有效审计工作量”会大幅下降,但覆盖质量并不会跟着下降。
EALOC 真正解决的,不只是算力问题
它不是简单“省算力”,而是在做一件更重要的事:
把资深审计员的经验判断,转化为可以量化执行的资源调度模型。
过去,一个老审计员凭经验就知道“这类文件不用逐行看”;现在,这件事被明确写进了系统规则里。
这一步非常关键。因为一旦经验无法被量化,它就无法稳定复制,也无法交给 AI 执行。
分层规则不能拍脑袋
代码分层也不能靠“看起来像什么就归什么”,而应该有一套优先级明确的规则体系。比如:
- 第三方库源码:直接跳过,不审源码本体,审调用方式
- Layer 1 命中高危候选:动态提升优先级
- 带
@Controller/@RestController/Filter等标记:归 T1 - 带
@Service/@Repository/@Mapper:归 T2 - 类名含
Util/Helper/Handler:归 T2 - 带
@Entity/@Table/@Data:归 T3 - 未命中任何规则:默认归 T2,而不是 T3
这里最重要的原则只有一个:
宁可保守,不可误降级。
因为误把高风险代码丢到低优先级层,损失的不是一点算力,而是整条漏洞链可能被漏掉。
七、三层审计架构:机器扫广度,AI 挖深度,语义做验证
真正稳定的 AI 代码审计,不可能只靠一个 LLM 从头看到尾。
原因很简单:规则引擎擅长广度覆盖,LLM 擅长语义理解和局部推理,语义分析工具擅长精确追踪调用关系。这三种能力各有边界,缺一不可。
所以合理的设计,一定是一个三层架构。
Layer 1:全量预扫描,解决“广度”问题
第一层尽量不用 LLM,而是用规则和脚本扫全量代码。
常见手段包括:
ripgrepSemgrep- 自定义 grep / AST 模式匹配
- XML / YAML / properties 配置扫描
这一层的目标不是直接下漏洞结论,而是先把“危险区域”粗筛出来,告诉后面的 Agent:
- 哪些文件命中了高风险模式
- 哪些地方可能存在危险 sink
- 哪些配置看起来不安全
- 哪些入口值得优先关注
比如在 Java 场景下,P0 级规则就不能只扫 Runtime.exec(),而应该覆盖一整套危险面:
- 原生命令执行
- 各类反序列化入口
- 模板渲染引擎
- 表达式注入
- JNDI 使用
- 动态类加载
- 反射执行
同时还要考虑 Java 生态的历史问题,比如 javax.* 和 jakarta.* 两套命名空间并存,否则规则覆盖天然不完整。
Layer 1 的输出是“参考坐标”,不是最终结论。它的作用是帮助后面提高效率,而不是替代后续分析。
Layer 2:LLM 双轨审计,解决“理解与挖掘”问题
真正体现大模型价值的,是第二层。
在这一层里,每个 Agent 对分配给它的文件执行两条并行审计轨道。
第一条轨道:Sink-driven
从危险操作往上追。
比如发现:
Runtime.exec(cmd)Statement.executeQuery(sql)ObjectInputStream.readObject()TemplateEngine.process()
接下来就要问:
- 参数从哪来
- 有没有经过过滤
- 过滤是否有效
- 有没有拼接、反射、隐式转换等绕过空间
- 最终是不是可被外部输入控制
这条轨道擅长找“有危险代码的地方”。
第二条轨道:Control-driven
从入口往下看“该有的安全控制是否存在”。
比如发现一个对外暴露的接口,就要判断:
- 是否有认证
- 是否有鉴权
- 是否有角色边界
- 是否有输入校验
- 是否存在跨租户或越权问题
- 是否依赖前端校验而后端缺失约束
这条轨道尤其重要,因为很多高质量漏洞并不是某一行代码写错了,而是系统缺少了本应存在的安全控制。
这也是裸跑模型最容易漏的一类问题。因为它天然更容易被“危险 API”吸引,而不是主动发现“少了什么”。
双轨并行,才更接近真实审计逻辑。一个成熟审计员做审计时,脑子里本来就同时在跑这两套逻辑:
- 一条是“哪里有危险行为”
- 一条是“哪里少了应有控制”
如果只有前者,没有后者,你会漏掉大量认证绕过、权限缺失、业务逻辑失控类问题;如果只有后者,没有前者,你又会漏掉真正可触发的技术漏洞。
Layer 3:语义验证,解决“到底成不成立”问题
Layer 2 找到的是候选点,但候选不等于成立。
真正进入最终报告前,必须有一层专门做语义级验证。最理想的能力是 LSP 或其他语义分析能力,比如:
- go to definition
- find references
- hover 类型信息
- 调用关系追踪
- 变量来源确认
验证流程通常是:
- 找到疑似 sink
- 定位该 sink 的真实实现
- 向上追调用者
- 确认中间变量类型与转换
- 判断是否能追到实际 source
- 判断中间是否存在有效 sanitization 或控制点
最终输出的,不该是“我觉得这里可能有问题”,而应该是:
- 哪个 source
- 通过哪些方法调用链
- 到达哪个 sink
- 每一跳在什么文件什么位置
- 中间有没有安全控制
- 为什么这些控制无效或可绕过
当 LSP 不可用时,当然也可以退化为 grep + 手动追踪,但精度和效率都会明显下降。
所以 Layer 3 的存在,本质上是在给 LLM 的推理“落地生根”,防止它停留在概率判断层面。
八、不同层级代码,为什么必须区别对待?
三层架构解决的是“能力分工”问题;但每个文件到底分析到什么深度,还要结合代码分层来决定。
这就是为什么 T1、T2、T3 不只是资源调度概念,它还直接决定了审计深度。
T1:入口层,必须做完整分析
T1 主要包括:
- Controller
- Filter
- Interceptor
- Security Config
- 对外 API 入口
- 负责请求接入和权限校验的关键组件
这些文件是攻击者最先接触到的代码,必须执行完整分析,通常至少包括:
- 数据流
- 安全控制
- 敏感操作
- 自定义危险封装
- 配置初始化
- 业务逻辑约束
- 端点枚举
- 安全控制绕过假设
在这里,审计不是简单扫“危险函数”,而是要站在攻击者视角,把每个入口当成潜在突破点。
T2:业务中间层,聚焦关键维度
T2 主要包括:
- Service
- DAO
- Mapper
- Util
- 配置文件
这一层不直接暴露给外部,但它决定了数据怎么流转、危险操作怎么被封装、安全控制有没有真正落到实处。
所以 T2 通常聚焦:
- 数据流
- 危险操作
- 安全控制
它不需要像 T1 那样逐项展开,但也绝不能只是“扫一下关键字”。
T3:模型层,快速匹配即可
T3 一般是:
- Entity
- DTO
- VO
- POJO
这层大部分都是低风险代码,所以不值得投入完整深度分析。但也不是完全跳过,因为这里依然可能藏着:
- Mass Assignment
- 敏感字段暴露
- 序列化泄露
- 被框架自动绑定导致的业务风险
所以 T3 更适合做快速模式匹配。
动态升级机制很关键
分层不是写死的。
一个 T2 文件如果命中了高危模式,比如危险命令执行封装、无校验动态 SQL 生成,就应该立刻提升为 T1 深度分析;一个 T3 文件如果出现了复杂业务方法,也不能再按“低风险模型类”对待。
这一步的意义在于:
分层是为了优化资源,不是为了降低警觉。
九、这套体系真正解决了什么问题?
把上面的设计拼起来,就能看清它真正解决的,不是“让 AI 更聪明”,而是让 AI 更像一个合格的审计工程师。
1. 它解决了覆盖率问题
裸跑 LLM 的覆盖率看运气;分层、分配、门禁、补扫机制,则把覆盖率从“主观希望”变成“硬性要求”。
2. 它解决了资源浪费问题
不是所有代码都值得逐行深挖。EALOC 让资源投向真正高风险区域,避免在低风险模型类上空耗上下文和算力。
3. 它解决了幻觉问题
通过分阶段执行、证据绑定、语义验证、状态持久化,模型输出会被持续约束。它不再能随意“脑补一段调用链”就写进报告。
4. 它解决了长任务崩坏问题
中间结果落盘,阶段状态持久化,意味着上下文不再完全依赖模型记忆。模型遗忘,不再等于项目失忆。
5. 它解决了结果不可复现的问题
固定的阶段、固定的规则、固定的输出格式,让审计结果开始具备工程稳定性。到了这一步,它才真正有资格进入团队协作、项目交付和产品化体系。
十、AI 不会替代审计员,但会淘汰“没有方法论的审计方式”
很多人最关心的问题,其实不是“这套体系怎么设计”,而是:
代码审计工程师在 AI 时代到底会不会被替代?
我的判断是:短期内不会,长期看也不是“被替代”这么简单。
因为大模型再强,也只是把“能力”摊开放在你面前。真正把这些能力组织成生产力的,仍然是方法论、流程设计、验证机制和优先级判断。
换句话说,未来更有价值的审计工程师,不一定是那个手工追每一条调用链追得最快的人,而是那个能定义下面这些事情的人:
- 审计流程怎么跑
- 资源如何调度
- 哪类问题必须优先验证
- 哪些结论没有证据不能落报告
- 如何把一次审计沉淀成团队规则和工具能力
AI 会大幅压缩“纯体力型审计”的空间,但会放大“体系设计型审计员”“方法论型审计员”“验证型审计员”的价值。
所以从这个角度看,Skill 体系的意义,不只是为了让 AI 更会审计,也是在重新定义审计工程师在新阶段的能力锚点。
结语:Skill 的本质,是把“经验”变成“系统”
回到最开始的问题:
为什么有的 AI 能稳定挖漏洞,有的却只会生成一堆看起来很像回事的幻觉报告?
答案不是因为前者用了更玄学的 Prompt,也不是因为它背后的模型“突然更懂安全”。
真正的原因是:
前者不是在让模型自由发挥,而是在让模型执行一套审计系统。
这套系统里,有流程,有层级,有资源分配,有状态持久化,有广度扫描,有深度推理,有语义验证,也有输出规范。它不是让 AI 更像一个聊天助手,而是让 AI 更像一个能被组织、能被约束、能被验证的审计引擎。
归根到底,这套 AI + Java 代码审计 Skill 体系做成的,是一件非常朴素、却又非常关键的事情:
把资深审计员多年隐性的工作经验,变成一套可执行、可复制、可落地的工程系统。
这,才是 AI 真正进入代码审计行业的开始。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)