一句话:AI 做代码审计的正确姿势是"分治 + 污点追踪 + 自我批判",不是把整个项目喂进去问一句"有漏洞吗"。

一、为什么代码审计是 AI 的高价值场景

代码审计的本质:在大量结构化文本里,沿着数据流找危险模式。

这正好是 LLM 的强项:

  • 能理解语义(知道 escapeHtml 是净化函数,rawQuery 不是)
  • 能跨文件追踪(人类容易漏的调用链)
  • 不疲劳(10 万行代码也不会看花眼)

弱项也很明确:上下文窗口有限,所以必须分治。

二、标准工作流

1. 项目认知     →  让 AI 理解项目结构和框架
2. 危险入口枚举 →  找出所有外部输入点
3. 污点追踪     →  对每个入口做 source→sink 分析
4. 净化验证     →  检查中间过滤是否有效
5. 自我批判     →  逐条质疑,剔除误报
6. 手工验证     →  对候选漏洞实际构造请求

三、第 1 步:让 AI 建立项目认知

# 输入
[项目目录树 + pom.xml / package.json + 配置文件]

# 任务
1. 判断这是什么类型的项目、用了哪些框架和安全组件
2. 列出所有可能产生漏洞的关键组件及版本
3. 输出"高危关注点"清单

# 输出
- 项目类型:
- 关键依赖(含版本):
- 高危关注点:

关键:输入里必须包含依赖清单。 老版本的 fastjson、log4j、shiro、struts2 是重灾区,AI 看到版本号就能联想到对应 CVE。

四、第 2 步:入口枚举

# 任务
从以下 Controller 代码中,列出所有接收外部输入的入口。

# 分类标注
- 用户可控参数(直接来自请求)
- 间接可控(来自 Header/Cookie/文件上传)
- 不可控(来自数据库/配置)

# 输出 Markdown 表格
| 方法 | 路径 | 参数 | 来源 | 类型 |

这一步产出的入口清单,就是后续逐个深挖的工作队列。

五、第 3 步:污点追踪(核心)

针对单个入口,做精细分析:

# 任务
分析 `queryOrder(HttpServletRequest req)` 方法是否存在安全漏洞。

# 分析要求
1. 从 req 中提取所有用户可控数据(source)
2. 追踪每个 source 的流向
3. 判断是否流入危险操作(sink):
   - SQL:Statement.execute / 字符串拼接 SQL
   - 命令:Runtime.exec / ProcessBuilder
   - 反序列化:ObjectInputStream.readObject / JSON.parseObject
   - 文件:new File / FileInputStream / Paths.get
   - SSRF:URL.openConnection / HttpClient.execute
   - 表达式:SpEL / OGNL / EL
4. 检查中间是否存在有效净化
5. 输出每条可疑路径

# 输出
[{"source":"","sink":"","taint_path":["file:line",...],
  "sanitizer":"none|xxx","exploitable":true|false,"confidence":0.0}]

# 代码
[java 代码]

六、实战:一个真实的误报治理过程

初轮 AI 报出 17 个"SQL 注入"。人工复核发现:

初报项 AI 判断 人工复核 结论
orderId 拼接进 SQL 高危 确实拼接,但上游有 Integer.parseInt 误报(类型强转即净化)
sortField 拼接 高危 无白名单,直接拼接 ORDER BY 真实漏洞
keyword like 拼接 高危 有 EscapeUtil.escapeLike 需看 escapeLike 实现
page 拼接 limit 中危 确实拼接,但值来自 Math.max(1,...) 误报

关键教训:AI 会漏掉"类型转换即隐式净化"这种情况。所以在 Prompt 里要显式提醒:

注意:`Integer.parseInt`、`Long.valueOf`、枚举白名单、
UUID.fromString 等操作,在参数被用于 SQL/命令拼接时
可视为有效净化(因为无法携带攻击载荷)。请考虑这一点。

加上这句后,误报直接砍掉一半。

七、跨文件污点追踪技巧

单文件分析容易断链。正确做法是给 AI 提供完整调用链:

入口: Controller.query() 
  → Service.buildQuery() [文件 A]
    → Dao.execute() [文件 B]
      → JDBC 拼接 [文件 B]

用 grep 先把相关文件都找出来,一起喂给 AI:

# 找调用关系
grep -rn "buildQuery\|executeQuery" --include="*.java" src/

然后把命中文件全量塞进上下文,让 AI 联合分析。

八、自我批判环节(必做)

# 任务
以下是上一轮识别的候选漏洞列表。现在你是安全评审负责人。

对每一条:
1. 重新审查证据链,指出任何逻辑跳跃
2. 检查是否遗漏了中间层的过滤
3. 评估"实际上能否被外部触发"
4. 给出裁决:CONFIRMED / REJECTED / NEEDS-MORE-INFO

# 规则
宁可漏报,不可误报。证据不足一律 REJECTED。

# 候选列表
[json]

这一轮能砍掉 40%~60% 的误报,是整个流程里性价比最高的一步。

九、不同语言的审计要点

语言 重点 sink 常见坑
Java 反序列化、SpEL/OGNL、JNDI、SQL 框架多,注意 Shiro/Struts 特有点
PHP eval/assert、文件包含、反序列化 preg_replace /e、变量覆盖
Python eval/exec、pickle、模板注入 Jinja2 SSTI、yaml.load
Node.js child_process、原型链污染、SSRF eval、模板注入、vm 逃逸
Go SQL 拼接、命令执行、路径穿越 相较安全,重点在第三方库

十、小结

LLM 代码审计的正确用法是流程化:

  1. 先建认知,再枚举入口
  2. 单点深挖,禁止一次问全项目
  3. 必须做自我批判
  4. 最终人工验证

它能帮你把 10 万行代码压缩成 50 个候选点,这已经是巨大的效率提升。但"这 50 个里哪个是真漏洞",仍然需要你的判断。

下一篇:AI 辅助 SQL 注入检测。


系列文章:AI 渗透测试与漏洞挖掘实战