一句话: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 代码审计的正确用法是流程化:
- 先建认知,再枚举入口
- 单点深挖,禁止一次问全项目
- 必须做自我批判
- 最终人工验证
它能帮你把 10 万行代码压缩成 50 个候选点,这已经是巨大的效率提升。但"这 50 个里哪个是真漏洞",仍然需要你的判断。
下一篇:AI 辅助 SQL 注入检测。
系列文章:AI 渗透测试与漏洞挖掘实战