文章总结: Java-Audit是基于JoernCPG静态分析与ClaudeAI语义分析的JavaWeb应用代码审计工具,通过Joern进行数据流追踪和Sink发现,AI负责鉴权逻辑和业务漏洞分析。项目优化了Joern结果过滤和路由分析存储,采用三阶段异步流水线架构,包含路由分析、参数追踪、鉴权检测等Agent模块,最终生成包含CVSS评分的Markdown审计报告。工具开源且支持CLI操作。
综合评分: 85
文章分类: 代码审计,安全工具,WEB安全,安全开发,AI安全
基于SAST+AI代码审计 架构与功能详解
原创
一寸灰
一寸灰
东方隐侠安全团队
2026年4月6日 12:46
江苏
Java-Audit 架构与功能详解
之前的代码审计项目由同事接手了,然后他改了很多东西。那理论上,我这水逼项目和产品也不搭边了,所以直接开了!
相对于之前优化的点
- joern扫描结果的过滤: joern扫描出来的结果大部分都是噪音如果我们AI去读取那些结果以做分析会很浪费上下文。所以写了个过滤器,只保留AI用于审计的部分。
- 路由分析结果外部存储 :将路由分析的结果写入到向量数据库中,其他Agent要分析的时候直接到向量库里边查找即可。(我感觉这个还能再优化,得空研究研究)
项目地址:https://github.com/sss12365/SAST2AI
项目细节下边就是AI生成的了
项目概述
01
Java-Audit 是一个基于 Joern CPG 静态分析 + Claude AI 语义分析 的 Java Web 应用代码审计工具。CLI 界面,Python 实现,通过 OpenAI 兼容 API 调用 Claude 模型。
核心理念 :Joern 负责精确的代码属性图分析(数据流追踪、Sink 发现),AI 负责 Joern 无法完成的语义理解(鉴权逻辑、业务漏洞、可控性推理)。两者结果通过 ChromaDB 向量数据库共享,Agent 按需检索,最大限度减少 token 消耗。
项目结构
java-audit/ # 3956 行代码
├── main.py # CLI 入口 (click)
├── config.yaml # 配置文件
├── requirements.txt # Python 依赖
│
├── core/ # 核心引擎层
│ ├── config.py # YAML 配置加载 + Pydantic 校验
│ ├── models.py # 数据模型定义
│ ├── orchestrator.py # 三阶段异步编排器
│ ├── joern_runner.py # Joern CLI 交互封装
│ ├── joern_parser.py # Joern 原始输出压缩器 (99.4% 压缩率)
│ ├── java_compressor.py # Java 源码压缩器 (60-85% 压缩率)
│ └── audit_store.py # ChromaDB 向量知识库
│
├── agents/ # AI Agent 层
│ ├── base.py # BaseAgent (OpenAI SDK 封装)
│ ├── route_analysis.py # 路由分析 Agent
│ ├── route_param.py # 参数追踪 Agent
│ ├── auth_analysis.py # 鉴权分析 Agent
│ ├── hardcoded_audit.py # 硬编码审计 Agent
│ └── vuln_verification.py # 漏洞验证 Agent
│
├── prompts/ # Agent System Prompt
│ ├── route_analysis.md # 路由提取指令
│ ├── route_param.md # 参数追踪 + 可控性分析指令
│ ├── auth_analysis.md # 鉴权绕过检测指令
│ ├── hardcoded_audit.md # 硬编码过滤指令
│ └── vuln_verification.md # 漏洞验证 + PoC 生成指令
│
├── joern_scripts/ # Joern Scala 查询脚本
│ ├── find_routes.sc # 路由发现 (Spring/Servlet/JAX-RS/Struts2)
│ ├── find_sinks.sc # Sink 发现 (SQL/CMD/File/HTTP/XML/...)
│ ├── dataflow_analysis.sc # Source→Sink 污点追踪
│ ├── hardcoded_secrets.sc # 硬编码密钥检测
│ └── find_auth.sc # 鉴权代码定位
│
└── report/ # 报告生成层
├── generator.py # Markdown 报告生成器
└── templates/
└── report_template.md # 报告模板
执行流水线
02
┌─────────────────────────────────────────────────────────────────┐
│ Stage 0: 预处理与索引 │
│ ┌──────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Java 源码压缩 │ │ Joern 原始输出压缩 │ │
│ │ 565 files → 426 docs │ │ 3.2MB → 18KB (99.4%) │ │
│ │ aggressive compress │ │ 去噪 + 去重 + 关键步骤提取 │ │
│ └──────────┬───────────┘ └──────────────┬──────────────────┘ │
│ └──────────┬──────────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ChromaDB 向量库 │ │
│ │ source_code 集合 │ │
│ │ joern 集合 │ │
│ └──────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Stage 1: 信息收集 (并行) │
│ ┌──────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Route Analysis Agent │ │ Joern Full Scan │ │
│ │ AI 提取路由+参数 │ │ CPG 构建 → 路由/Sink/数据流 │ │
│ │ 压缩源码 60K chars │ │ 或加载已有 joern-raw-output │ │
│ └──────────┬───────────┘ └──────────────┬──────────────────┘ │
│ └──────────┬──────────────────┘ │
│ ▼ 写入 routes / joern 集合 │
├─────────────────────────────────────────────────────────────────┤
│ Stage 2: 深度分析 (并行) │
│ ┌──────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Route Param Agent │ │ Auth Analysis Agent │ │
│ │ 从 store 按批加载路由 │ │ 从 store 检索 Filter/Config 代码 │ │
│ │ 检索相关 ServiceImpl │ │ 语义匹配鉴权相关 Joern 发现 │ │
│ │ 追踪 Source→Sink │ │ URI 绕过 + CVE + 架构分析 │ │
│ └──────────┬───────────┘ └──────────────┬──────────────────┘ │
│ └──────────┬──────────────────┘ │
│ ▼ 写入 dataflows / auth 集合 │
├─────────────────────────────────────────────────────────────────┤
│ Stage 3: 验证与报告 (并行) │
│ ┌──────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Hardcoded Audit Agent│ │ Vuln Verification Agent │ │
│ │ Joern 结果语义过滤 │ │ 从 store 只加载 HIGH+ 发现 │ │
│ │ 排除误报 │ │ 检索可控 dataflow │ │
│ └──────────┬───────────┘ │ 代码证据验证 │ │
│ │ │ CVSS 三维评分 │ │
│ │ │ 生成 PoC + 伪代码 │ │
│ │ └──────────────┬──────────────────┘ │
│ └──────────┬──────────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Report Generator │ │
│ │ Markdown 审计报告 │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
核心模块详解
03
- CLI 入口 (
main.py, 144 行)
命令:
scan <project_path> 完整审计
--config, -c 配置文件路径 (默认 config.yaml)
--output, -o 输出目录
--joern-output, -j 已有 Joern 输出文件 (跳过 Joern 扫描)
check 检查配置和依赖
parse-joern <file> 独立压缩 Joern 输出
-output, -o 输出文件
伪代码:
def scan(project_path, config, output, joern_output):
cfg = load_config(config) # 加载 YAML
orchestrator = AuditOrchestrator(cfg) # 初始化编排器
report = asyncio.run( # 异步执行
orchestrator.run(project_path, output, joern_output)
)
print(f"Report: {report}")
- 配置系统 (
core/config.py, 52 行)
# config.yaml
model:
base_url: "https://your-api.com" # OpenAI 兼容 API 地址
api_key: "sk-xxx" # API 密钥 (或 ANTHROPIC_API_KEY 环境变量)
model_id: "claude-opus-4-6" # 模型 ID
max_tokens: 16000 # 最大输出 token
joern:
home: "/path/to/joern-cli" # Joern 安装目录
output:
dir: "./audit_output" # 输出目录 (仅 --output 指定时使用,默认输出到项目 report/)
Pydantic 校验:
- api_key: 为空时自动读取ANTHROPIC_API_KEY环境变量
- joern.home: 校验目录存在
- 数据模型 (
core/models.py, 141 行)
# 核心数据结构
RouteInfo # 路由: path, method, handler_class, handler_method, params, burp_template
RouteParam # 参数: name, java_type, http_location, required
CallChainNode # 调用链节点: level, class_name, method_name, code_snippet, param_mapping
DataFlowChain # 数据流: source_param → sink_type + chain[] + controllable + pseudocode
Vulnerability # 漏洞: vuln_id, severity_score, poc_request, dataflow_chain, remediation
AuthInfo # 鉴权: framework, version, route_auth_mapping, bypass_findings
HardcodedSecret # 硬编码: secret_type, value_preview(脱敏), file_path
JoernResult # Joern 结果: routes, sinks, dataflows, hardcoded_secrets
AuditReport # 最终报告: 所有结果汇总
# CVSS 三维评分
SeverityScore:
score = R × 0.40 + I × 0.35 + C × 0.25 # R=可达性, I=影响, C=复杂度 (0-3)
cvss = score / 3.0 × 10.0
severity:
C (Critical): score >= 2.70, CVSS 9.0-10.0
H (High): score >= 2.10, CVSS 7.0-8.9
M (Medium): score >= 1.20, CVSS 4.0-6.9
L (Low): score >= 0.10, CVSS 0.1-3.9
# Sink 类型枚举
SinkType: SQL | COMMAND | HTTP | FILE | XML | LDAP | EXPRESSION | DESERIALIZE | RESPONSE | PATH
- 编排器 (
core/orchestrator.py, 220 行)
async def run(project_path, output_dir, joern_output_file):
# 默认输出到 <project_path>/report/,每次清空重建
output = output_dir or Path(project_path) / "report"
if output.exists():
shutil.rmtree(output)
output.mkdir(parents=True)
store = AuditStore(output / ".audit_store")
store.reset()
# Stage 0: 索引源码到向量库
index_source_code(project_path)
# → 读取所有 .java → aggressive 压缩 → 存入 source_code 集合
# Stage 1: 并行
if joern_output_file:
# 加载已有 Joern 输出 → 压缩 → 存入 joern 集合
parsed = parse_joern_output(raw_text)
compact = to_compact_text(parsed)
store.store_joern_findings(compact, parsed.findings)
route_result = await route_agent.analyze(project_path)
else:
route_result, joern_result = await gather(
route_agent.analyze(project_path),
joern.full_scan(project_path), # CPG 构建 + 5 个查询脚本
)
store.store_routes(route_result["routes"])
# Stage 2: 并行, Agent 从 store 按需检索
param_result, auth_result = await gather(
param_agent.analyze_with_store(store, joern_result),
auth_agent.analyze_with_store(store, joern_result),
)
store.store_dataflows(param_result)
store.store_auth(auth_result)
# Stage 3: 并行, VulnVerify 只加载 HIGH+ 发现
hardcoded_result, vuln_result = await gather(
hardcoded_agent.audit(joern_result),
vuln_agent.verify_with_store(store, joern_result),
)
# 生成报告
report_gen.generate(all_results, output_path)
- Joern 集成
5.1 Joern Runner ( core/joern\_runner.py , 121 行)
class JoernRunner:
async def build_cpg(project_path):
# javasrc2cpg <project> -o workspace/cpg.bin
# 超时 900 秒
async def full_scan(project_path):
cpg = await build_cpg(project_path)
# 并行执行 5 个查询脚本:
routes, sinks, dataflows, hardcoded, auth = await gather(
run_script("find_routes.sc", cpg),
run_script("find_sinks.sc", cpg),
run_script("dataflow_analysis.sc", cpg),
run_script("hardcoded_secrets.sc", cpg),
run_script("find_auth.sc", cpg),
)
5.2 Joern 查询脚本 ( joern_scripts/ , 550 行 Scala)
**find_routes.sc** (174 行) — 路由发现:
scala
// Spring MVC: @RequestMapping/@GetMapping/@PostMapping/…
cpg.annotation.name(“RequestMapping|GetMapping|PostMapping|…”).method → 提取 path, httpMethod, params
// Servlet: HttpServlet.doGet/doPost
cpg.typeDecl.filter(_.inheritsFromTypeFullName(“.HttpServlet.“)).method.name(“doGet|doPost”)
// JAX-RS: @Path + @GET/@POST
cpg.annotation.name(“GET|POST|PUT|DELETE”).method → 合并 class @Path + method @Path
// Struts2: ActionSupport.execute()
cpg.typeDecl.filter(_.inheritsFromTypeFullName(“.ActionSupport.“)).method.name(“execute”)
// 输出 JSON: RouteEntry(path, httpMethod, handlerClass, handlerMethod, filePath, lineNumber, framework, params[])
**find_sinks.sc** (80 行) — 危险 Sink 发现:
scala
// SQL: executeQuery, executeUpdate, prepareStatement, createQuery
// Command: Runtime.exec(), ProcessBuilder.start()
// File: FileInputStream, FileOutputStream, Files.read/write/copy
// HTTP: HttpClient.execute(), URL.openConnection()
// XML: DocumentBuilder.parse(), SAXParser
// Deserialize: ObjectInputStream.readObject(), JSON.parseObject()
// LDAP: DirContext.search()
// Expression: SpelExpression.getValue(), Ognl
// XSS: PrintWriter.write(), response.getWriter()
// 输出 JSON: SinkEntry(sinkType, callName, className, filePath, lineNumber, code)
**dataflow_analysis.sc** (87 行) — 污点追踪:
scala
// Sources: getParameter(), @RequestParam, @PathVariable, @RequestBody 等
// Sinks: 按类别定义 (SQL/COMMAND/FILE/HTTP/XML/DESERIALIZE/EXPRESSION)
// 使用 Joern reachableByFlows 进行 Source→Sink 可达性分析
sinks.reachableByFlows(sources) → 输出每条流的完整步骤
// 输出 JSON: DataFlowEntry(sourceParam, sinkType, steps[FlowStep(class, method, code, file, line)])
**hardcoded_secrets.sc** (106 行) — 硬编码检测:
scala
// 模式 1: 敏感变量名 + 字符串字面量赋值
// password, secret, apiKey, token, privateKey, encryptKey, jdbc 等
cpg.assignment.where(.target.name(“(?i).password.“)).where(.source.isLiteral)
// 模式 2: 敏感方法调用 + 字符串参数
// setPassword(), setSecret(), setApiKey() 等
cpg.call.name(“setPassword|setSecret|…”).where(_.argument(1).isLiteral)
// 模式 3: JDBC URL 含内嵌凭据
cpg.literal.code(“\”jdbc:.*\””).filter(_.contains(“password=”))
// 值预览: 前4后2位 + ****
// 输出 JSON: HardcodedEntry(secretType, variableName, valuePreview, className, filePath, lineNumber)
**find_auth.sc** (103 行) — 鉴权代码定位:
scala
// Shiro: 继承 org.apache.shiro.*, @RequiresAuthentication/Permissions/Roles
// Spring Security: @EnableWebSecurity, @PreAuthorize, SecurityFilterChain
// JWT: jwt 相关的 parse/verify/decode/sign 调用
// Filter: implements javax.servlet.Filter → doFilter()
// Interceptor: implements HandlerInterceptor → preHandle()
// URI 解析: getRequestURI/getServletPath/getPathInfo 调用位置 (潜在绕过)
// 输出 JSON: AuthEntry(authType, className, methodName, filePath, code, details)
```
5.3 Joern 输出压缩器 ( `core/joern\_parser.py` , 415 行)
问题 : Joern 原始输出 3.2MB (20772 行), 其中 92% 是构建日志噪音。
三层压缩 :
Layer 1: 噪音过滤 (去除 92% 构建日志)
– [INFO] Pass io.joern.x2cpg… completed (25 行 pass 日志)
– [INFO] Calculating reaching definitions (8852 行)
– [INFO] Number of definitions (8852 行)
– [INFO] Could not create edge (1405 行)
Layer 2: Taint Flow 关键步骤提取 (每条 flow 30→5 步)
– 去除: $obj3,
– 保留: Source 入口, 文件边界跳转, 危险操作 (executeQuery/readObject/transferTo), Sink 终点
示例:
原始 (30 步): FileController:82 → FileServiceImpl:43 → L47 → L90 → L99 →
getOriginalFilename() → StringUtils.isBlank → concatPath → FileUtils:57 →
L59 → L62 →
StringUtils.appendIfMissing → L40 → StringUtils.removeStart → … → transferTo
压缩 (5 步): FileController:82:@RequestParam MultipartFile →
FileServiceImpl:43:MultipartFile → FileUtils:57:String path →
FileServiceImpl:100:withBasePath → L102:transferTo(new File(fullPath))
Layer 3: 相同 Sink 去重合并
– FILE_UPLOAD: 31 findings → 19 unique + 12 duplicates merged
– 合并后只保留一条代表性 flow, 列出所有入口点:
entries(7): FileController.java:47, :55, :69, :82, FileServiceImpl:43, :90, :99
压缩效果: 3,212,959 bytes → 18,308 chars (99.4% reduction)
独立使用:
python3 main.py parse-joern joern-raw-output.txt -o compressed.txt
6. Java 源码压缩器 ( `core/java\_compressor.py` , 342 行)
两种模式 :
Normal 模式 (~25% 压缩):
去除: import, 单行注释, javadoc, 空行, 日志语句, Lombok 注解
保留: 大部分代码逻辑
Aggressive 模式 (~60-85% 压缩):
只保留:
✅ 路由注解 (@RequestMapping, @GetMapping, @PostMapping, @Path, @WebServlet...)
✅ 鉴权注解 (@PreAuthorize, @RequiresAuthentication, @Secured...)
✅ 方法签名 (public/protected/private 方法声明)
✅ 危险 Sink 调用 (executeQuery, readObject, getConnection, transferTo, Runtime.exec...)
✅ 输入获取 (getParameter, @RequestParam, @RequestBody, getInputStream...)
✅ SQL 字符串拼接 ("SELECT" + variable)
✅ 安全相关字段 (password, secret, token, filter, datasource...)
✅ 类/接口声明
去除:
❌ import 语句
❌ 注释 (单行/多行/javadoc)
❌ 空行
❌ 日志 (logger.debug/info/trace/warn)
❌ getter/setter 方法体
❌ Lombok 注解 (@Data, @Getter, @Setter...)
❌ 纯业务逻辑 (与安全无关的计算/转换)
实测压缩率 :
VizServiceImpl.java: 24,944 → 3,668 chars (85%)
DataProviderServiceImpl.java: 19,830 → 2,588 chars (87%)
ShiroSecurityManager.java: 13,238 → 2,026 chars (85%)
DataProviderController.java: 4,981 → 2,636 chars (47%) # Controller 本身就紧凑
7. 向量知识库 ( `core/audit\_store.py` , 399 行)
ChromaDB 持久化存储 , 6 个集合:
class AuditStore:
# 6 个集合
collections = {
“sourcecode”: # 每个 Java 文件一条文档 (aggressive 压缩后)
metadata: {path, filename, filetype, size, fullcontent}
# filetype: controller/service/filter/interceptor/config/dao/entity/security
“routes”: # 每个 HTTP 路由一条文档
metadata: {path, method, handlerclass, handlermethod, framework, paramcount, paramnames, hasbody, fulljson}
“joern”: # 每个 Joern finding 一条文档
metadata: {severity, category, location, full_json}
“dataflows”: # 每条 Source→Sink 链一条文档
metadata: {route, sourceparam, sinktype, controllable, severity, full_json}
“auth”: # 鉴权分析结果
metadata: {type: “auth_analysis”}
“vulns”: # 验证后漏洞
}
# Agent 专用查询方法
getroutesforparamanalysis(batchidx, batchsize) # 按批加载路由
getauthrelatedcode() # 语义检索鉴权代码
→ query(“sourcecode”, “authentication authorization filter…”,
where={“filetype”: {“$in”: [“filter”,”interceptor”,”config”,”security”]}})
getfindingsbyseverity(“HIGH”) # 只加载 HIGH+
→ query(“joern”, “vulnerability injection…”,
where={“severity”: {“$in”: [“CRITICAL”,”HIGH”]}})
getcontrollabledataflows() # 只加载可控数据流
→ query(“dataflows”, “controllable SQL…”,
where={“controllable”: True})
getdangerousroutes(n=20) # 高危路由
getcontrollercode() # Controller 源码
渐进式加载示意 :
Route Param Agent batch 3:
1. store.getroutesforparamanalysis(2, 10) → 10 条路由 (~3K chars)
2. 提取 handlerclass 名 → “UserController”
3. store.query(“sourcecode”, “UserController”) → 相关 ServiceImpl (~5K chars)
4. store.getfindingsbyseverity(“HIGH”) → Joern 高危发现 (~10K chars)
5. 组装 usermessage: ~18K chars (vs 之前每 batch 80K)
8. AI Agent 层
8.1 BaseAgent ( agents/base.py , 102 行)
class BaseAgent:
def init(config, agentname):
self.client = OpenAI(apikey, baseurl + “/v1”, timeout=300s)
self.systemprompt = loadprompt(f”prompts/{agent_name}.md”)
def callmodel(usermessage, systemprompt=None):
messages = [
{“role”: “system”, “content”: systemprompt},
{“role”: “user”, “content”: usermessage},
]
response = client.chat.completions.create(model, max_tokens, messages)
return response.choices[0].message.content
async def callmodelasync(usermessage):
return await asyncio.tothread(callmodel, usermessage)
def readsourcefiles(projectpath, extensions=[“.java”,”.xml”,”.properties”,…])
def readspecificfiles(projectpath, patterns=[“*Controller.java”,…])
8.2 Route Analysis Agent ( agents/route\_analysis.py, 95 行)
输入: 项目源码路径
处理:
1. compressjavaproject(projectpath, maxtotalchars=60000) # 压缩源码
2. 调用 AI 分析所有路由
输出: {routes: [{path, method, handlerclass, handlermethod, params[], burptemplate}], framework_info}
支持框架: Spring MVC, Servlet, JAX-RS, Struts 2, CXF Web Services
参数解析: @RequestParam(Query), @PathVariable(Path), @RequestBody(Body), @RequestHeader(Header), @CookieValue(Cookie)
8.3 Route Param Agent (agents/route\_param.py, 209 行)
两种模式:
analyzewithstore(store, joernresult) # 向量库模式 (推荐)
analyze(routeresult, joern_result) # 直接传参模式 (fallback)
向量库模式:
1. joerncontext = store.getfindingsbyseverity(“HIGH”) + joern dataflows # 一次性加载
2. for batch in batches(10):
routes = store.getroutesforparamanalysis(batchidx)
code = store.query(“sourcecode”, handlerclasses)
result = AI(routes + code + joerncontext) # 每 batch 只加载相关代码
输出 (每个有危险 Sink 的路由):
{routepath, dataflowchains: [{
sourceparam, sinktype, sinklocation,
chain: [{level, classname, methodname, codesnippet, parammapping}],
controllable, controllabilitynote, pseudocode, poc_request
}]}
可控性判定:
参数传到 Sink? → 硬编码覆盖? → 安全检查? → 可绕过?
结论: ✅ 完全可控 / ⚠️ 条件可控 / ❌ 不可控
8.4 Auth Analysis Agent ( `agents/auth\_analysis.py` , 167 行)
两种模式:
analyzewithstore(store, joernresult) # 向量库模式
analyze(routeresult, joern_result) # 直接传参模式
向量库模式:
1. authcode = store.getauthrelatedcode() # 语义检索 Filter/Interceptor/Config
2. routes = store.query(“routes”, “all”, n=100)
3. joernauth = store.query(“joern”, “authentication filter interceptor…”)
4. AI(authcode + routes + joern_auth)
检测范围:
– 鉴权框架识别: Shiro, Spring Security, JWT, Filter, Interceptor
– 组件版本 CVE: Shiro < 1.11.0, Spring Security < 5.7.5 等
– URI 解析绕过: getRequestURI() vs getServletPath(), 分号注入, 双斜杠, URL编码
– 鉴权架构分析: Filter → Interceptor → Action 各层
– 路由鉴权映射: 每个路由的鉴权状态
输出:
{authframework: {name, version, configfiles, knowncves},
autharchitecture: {layers[]},
routeauthmapping: {“/path”: “状态”},
bypassfindings: [{vulnid, title, severity, poc_request, remediation}]}
8.5 Hardcoded Audit Agent ( `agents/hardcoded\_audit.py` , 69 行)
输入: Joern 硬编码扫描结果
处理: AI 语义过滤, 区分真实密钥 vs 误报
误报排除:
– 占位符: ${password}, %s, {0}
– 空值/null
– 测试代码 (test/ 目录)
– 示例值 (“your-api-key-here”)
– 已哈希密码 ($2a$10$…)
– 配置引用
输出:
{hardcodedsecrets: [{secrettype, risklevel, variablename, valuepreview(脱敏), filepath, linenumber, remediation}],
filteredcount: 12, filterreasons: {placeholder: 5, testcode: 3, …}}
8.6 Vuln Verification Agent ( `agents/vuln\_verification.py` , 174 行)
两种模式:
verifywithstore(store, joernresult) # 向量库模式
verify(paramresult, auth_result, …) # 直接传参模式
向量库模式 — 只加载真正需要验证的内容:
1. controllableflows = store.getcontrollabledataflows() # 可控数据流
2. highfindings = store.getfindingsbyseverity(“HIGH”) # 高危 Joern 发现
3. auth = store.query(“auth”, “bypass vulnerability”) # 鉴权绕过
4. code = store.query(“sourcecode”, “危险 Sink 关键词”) # 相关源码
5. AI(controllableflows + highfindings + auth + code)
核心原则: 只报告有代码证据的真实漏洞
✅ 代码中确实存在 SQL 拼接 / readObject() / 未过滤输入到 Sink
❌ 推测性 (“如果服务端没有校验…”)
❌ 框架已自动防护 (MyBatis #{}, PreparedStatement)
❌ 证据不足的 IDOR
验证流程:
1. 可达性验证 (外部可访问? 需要什么权限?)
2. 数据流验证 (Source→Sink 真实存在? 中间有过滤?)
3. 可控性验证 (用户输入能控制到 Sink? 硬编码覆盖?)
4. 利用可行性 (需要特殊条件? WAF 拦截?)
5. 影响评估 (实际影响?)
6. 代码证据 (具体文件名、行号、代码片段)
输出:
{verifiedvulnerabilities: [{
vulnid, title, severityscore: {R, I, C}, cvss, severity,
exploitability: confirmed/pending/notexploitable,
location, description,
dataflowchain: {source, sink, chainsummary, pseudocode},
pocrequest, remediation
}],
dismissedfindings: [{originalfinding, dismissreason, location}]}
9. Prompt 设计 ( prompts/ , 470 行)
每个 Agent 的 System Prompt 包含:
* 角色定义 (专业 Java Web 安全审计专家)
* 检测范围和边界
* 输出格式 (严格 JSON schema)
* 关键规则 (零遗漏 / 只报真实漏洞 / 代码证据)
特色设计:
* `vuln\_verification.md` : 6 条"不报告"规则, 避免推测性漏洞
* `auth\_analysis.md` : 完整的 URI 绕过模式表 + CVE 版本对照表
* `route\_param.md` : 可控性判定决策树 + MyBatis 安全/危险模式区分
10. 报告生成器 ( `report/generator.py` , 286 行)
Markdown 报告结构 :
{project} – Java 代码审计报告
1. 审计概述 (框架识别、路由总数、分析方法)
2. 风险统计 (C/H/M/L 四级表)
3. 漏洞详情
### [{vuln_id}] {title}
| 严重等级 | CVSS | 可达性 | 影响 | 复杂度 | 可利用性 | 位置 |
描述
执行链 (Source → Sink) — 伪代码
利用数据包 (PoC) — Burp Suite HTTP 请求
修复建议
4. 数据流追踪 (每个参数的 Source→Sink 链)
5. 鉴权分析 (框架 + CVE + 路由鉴权映射 + 绕过)
6. 硬编码审计 (类型/变量/值预览/文件/风险)
7. 审计结论 (统计汇总)
“`
Token 优化效果总结
04
| 优化层 | 技术 | 压缩率 |
| — | — | — |
| Joern 输出 | 噪音过滤 + Taint Flow 关键步骤 + Sink 去重 | 99.4% (3.2MB → 18KB) |
| Java 源码 | Aggressive 模式: 只保留安全骨架 | 60-85% |
| 上下文传递 | ChromaDB 向量库按需检索替代全量传输 | ~70% |
| Batch 优化 | Joern context 一次加载 + batch_size 5→10 | ~50% API 调用 |
总计 : 单次审计 token 消耗从 ~340K 降至 ~95K ( ~72% reduction )
支持的漏洞类型
05
| 类型 | Joern 检测 | AI 检测 | 示例 |
| — | — | — | — |
| SQL 注入 | ✅ Source→Sink 数据流 | ✅ 可控性/绕过分析 | String 拼接 SQL, MyBatis ${} |
| 命令注入 | ✅ Runtime.exec() 追踪 | ✅ 参数过滤分析 | ProcessBuilder, Runtime.exec |
| 反序列化 | ✅ readObject() 定位 | ✅ Gadget Chain 可用性 | ObjectInputStream, JSON.parseObject |
| 文件上传 | ✅ transferTo() 追踪 | ✅ 路径穿越/类型校验 | MultipartFile, 无路径保护 |
| 文件读取 | ✅ FileInputStream 追踪 | ✅ 路径遍历分析 | path traversal (../) |
| SSRF | ✅ HttpClient/URL 追踪 | ✅ 内网探测分析 | HttpClient.execute, URL.openConnection |
| XXE | ✅ XML 解析器定位 | ✅ 外部实体配置 | DocumentBuilder, SAXParser |
| JDBC 注入 | ✅ getConnection() 追踪 | ✅ H2/MySQL 反序列化 | JDBC URL INIT=RUNSCRIPT |
| 鉴权绕过 | ✅ URI 方法定位 | ✅ 路径匹配差异分析 | getRequestURI vs getServletPath |
| 越权访问 | ❌ | ✅ 权限校验缺失 | IDOR, 参数可控资源 ID |
| 硬编码密钥 | ✅ 模式匹配 | ✅ 语义过滤误报 | 数据库密码, API Key, JWT Secret |
| 表达式注入 | ✅ SpEL/OGNL 追踪 | ✅ 输入可控性 | SpelExpression.getValue() |
| LDAP 注入 | ✅ DirContext.search() 追踪 | ✅ 过滤器拼接分析 | LDAP filter injection |
| XSS | ✅ response.write() 追踪 | ✅ 输出编码分析 | 反射型/存储型 |
支持的 Java 框架
06
| 框架 | 路由发现 | 参数解析 | 鉴权分析 |
| — | — | — | — |
| Spring MVC | @RequestMapping, @GetMapping, @PostMapping… | @RequestParam, @PathVariable, @RequestBody | Spring Security |
| Servlet | HttpServlet, @WebServlet, web.xml | HttpServletRequest | Filter Chain |
| JAX-RS | @Path, @GET, @POST | @QueryParam, @PathParam, @FormParam | – |
| Struts 2 | struts.xml, ActionSupport | Action 属性 | Interceptor |
| Apache Shiro | – | – | shiro.ini, @RequiresAuthentication |
| JWT | – | Bearer Token | JWT 签名验证 |
| MyBatis | – | #{} (安全) / ${} (危险) | – |
最后想说的
07
这一段话改了好多好多次,改了又删,删了又改。
简单点说就是,过我手的东西,我会尽责任去做好去完善。但是每当我对公司产品提出问题的时候,现在身边的同事都在跟我说,客户不关心这个,只关心结果。就算以结果导向,也没看见很好的结果。只有光鲜亮丽的前端,格式规整封面华丽的报告。说实话不知道我对技术、产品的精益求精理念是对还是错。
就这样吧,祝师傅们清明安康。
结语:
一寸灰兄弟的钻研与创新精神值得我们持续学习,这个世界有它的问题,我们一定不要怀疑自己的赤子之心。
如果十年后我们还能保持今日的热血,我想那是最值得感恩的事情。
——千里
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:东方隐侠安全团队 一寸灰
一寸灰《基于SAST+AI代码审计 架构与功能详解》