一句话: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 渗透测试与漏洞挖掘实战