AgentSkillsCN

java-route-tracer

Java Web 源码路由多层级调用链追踪工具。根据用户指定的路由路径,追踪从 Controller/Action 到 DAO 层的完整调用链,输出每一层的文件位置、方法签名和可传入参数。适用于多种漏洞类型的参数流向分析:(1) SQL注入 - 追踪参数到SQL拼接点,(2) 命令注入 - 追踪参数到Runtime.exec(),(3) SSRF - 追踪参数到HTTP请求,(4) XSS - 追踪参数到响应输出,(5) 文件操作 - 追踪参数到File操作,(6) XXE/反序列化/LDAP注入/表达式注入等。支持 Spring MVC、Struts 2、Servlet、JAX-RS 等框架。**支持反编译 .class/.jar 文件提取调用链**。结合 java-route-mapper 使用可实现完整的路由+调用链审计。

中文原作
SKILL.md
--- frontmatter
name: java-route-tracer
description: Java Web 源码路由多层级调用链追踪工具。根据用户指定的路由路径,追踪从 Controller/Action 到 DAO 层的完整调用链,输出每一层的文件位置、方法签名和可传入参数。适用于多种漏洞类型的参数流向分析:(1) SQL注入 - 追踪参数到SQL拼接点,(2) 命令注入 - 追踪参数到Runtime.exec(),(3) SSRF - 追踪参数到HTTP请求,(4) XSS - 追踪参数到响应输出,(5) 文件操作 - 追踪参数到File操作,(6) XXE/反序列化/LDAP注入/表达式注入等。支持 Spring MVC、Struts 2、Servlet、JAX-RS 等框架。**支持反编译 .class/.jar 文件提取调用链**。结合 java-route-mapper 使用可实现完整的路由+调用链审计。

Java Route Tracer - 路由调用链追踪

根据用户指定的路由,追踪从入口到最终使用点的完整调用链,输出层级调用信息。适用于多种漏洞类型的参数流向分析。

核心功能

只输出调用链信息,不进行漏洞分析或安全建议。

输出内容包括:

  • 完整 HTTP 数据包 - Burp Suite 可直接使用的请求模板
  • 详细参数定义 - 每个参数的名称、类型、嵌套结构
  • 层级调用关系 - Controller → Service → DAO → 父类
  • 参数流向追踪 - 从 HTTP 入口到最终使用点的完整路径
  • 参数变量名追踪 - 同一参数在不同类中的变量名变化
  • 最终使用点标注 - 参数在最终层如何被使用

适用漏洞类型

漏洞类型追踪最终使用点
SQL 注入sql + paramStatement.execute()、MyBatis ${}、Hibernate HQL 拼接
命令注入Runtime.exec(cmd)ProcessBuilder(cmd)
SSRFHttpClient.execute(url)URL.openConnection()RestTemplate
XSSresponse.getWriter().write()、模板引擎输出、@ResponseBody
文件操作new File(path)FileInputStream()Files.read()
XXEDocumentBuilder.parse()SAXParserXMLReader
反序列化ObjectInputStream.readObject()JSON.parseObject()
LDAP 注入DirContext.search(filter)LdapTemplate
表达式注入SpelExpression.getValue()、OGNL、MVEL、Freemarker
路径遍历new File(basePath + userInput)Paths.get()

工作流程

1. 接收用户输入

用户提供:

  • 路由路径:如 /admin/user_login.action/api/faceCapture
  • 项目路径:源码或反编译文件所在目录

2. 定位入口点与方法识别

根据框架类型定位路由入口类:

框架入口定位方式
Spring MVC@RequestMapping 注解匹配
Struts 2struts.xml 中 action 配置
Servletweb.xml 或 @WebServlet
JAX-RS@Path 注解匹配

重要说明:当入口类包含多个业务方法时(如 Web Service 有多个接口方法、Controller 有多个端点方法),技能会自动识别并追踪所有方法。详细实现策略请参考:multi-method-tracing.md

2.1 多方法追踪执行流程(强制要求)

必须严格按照以下步骤执行,使用 TodoWrite 工具管理任务:

步骤 1: 统计所有方法

首先扫描入口类,识别所有需要追踪的方法,输出方法清单:

code
正在扫描入口类: VehicleQueryServiceImpl

发现以下 10 个方法需要追踪:
1. getBasicQuery(searchJson, pageJson, extend)
2. getAdvancedQuery(searchJson, pageJson, extend)
3. getDetailQuery(searchJson, pageJson, extend)
4. getStatisticsQuery(searchJson, pageJson, extend)
5. getExportQuery(searchJson, pageJson, extend)
... (列出所有方法)
10. getReportQuery(searchJson, pageJson, extend)

共计: 10 个方法

步骤 2: 创建 TodoList 任务

必须使用 TodoWrite 工具,将每个方法添加为独立任务:

python
TodoWrite([
    {"content": "追踪方法 1/10: getBasicQuery", "status": "pending", "activeForm": "追踪 getBasicQuery"},
    {"content": "追踪方法 2/10: getAdvancedQuery", "status": "pending", "activeForm": "追踪 getAdvancedQuery"},
    {"content": "追踪方法 3/10: getDetailQuery", "status": "pending", "activeForm": "追踪 getDetailQuery"},
    {"content": "追踪方法 4/10: getStatisticsQuery", "status": "pending", "activeForm": "追踪 getStatisticsQuery"},
    # ... 所有 10 个方法
    {"content": "追踪方法 10/10: getReportQuery", "status": "pending", "activeForm": "追踪 getReportQuery"},
    {"content": "生成总索引文件", "status": "pending", "activeForm": "生成总索引"}
])

步骤 3: 逐个执行任务(优化策略)

按照 TodoList 顺序执行每个任务,根据接口数量采用不同的追踪策略:

接口序号追踪策略报告内容
第 1 个接口完整追踪链包含所有层级、分支、变量追踪、可控性分析
第 2 个及之后的接口简化追踪链必须包含:请求模板 + 该方法参数定义 + 调用链 + Sink识别 + 可控性分析

执行流程:

  1. 将当前任务标记为 in_progress
  2. 根据接口序号选择追踪策略
  3. 生成相应类型的报告文件
  4. 将任务标记为 completed
  5. 继续下一个任务
code
--- 方法 1/10: getBasicQuery ---
状态: in_progress
[完整追踪链...]
[生成报告: myproject_trace_getBasicQuery_20260205.md]
状态: completed ✅

--- 方法 2/10: getAdvancedQuery ---
状态: in_progress
[简化追踪链...]
[生成报告: myproject_trace_getAdvancedQuery_20260205.md (简化版)]
状态: completed ✅

--- 方法 3/10: getDetailQuery ---
状态: in_progress
[简化追踪链...]
[生成报告: myproject_trace_getDetailQuery_20260205.md (简化版)]
状态: completed ✅

... 继续直到所有任务完成 ...

步骤 4: 完成验证

所有方法追踪完成后:

  1. 生成总索引文件
  2. 验证生成的文件数量
  3. 输出完成报告
bash
# 验证命令
ls route_tracer/{route_name}/*.md | wc -l
# 期望结果: 11 (10个方法报告 + 1个总索引)

2.2 中断恢复机制

如果执行中断,下次继续时必须:

  1. 检查已生成的报告文件
  2. 对比方法清单,识别未完成的方法
  3. 只为未完成的方法创建 TodoList 任务
  4. 继续执行
python
# 检查已完成的方法
existing_files = ls("/path/to/route_tracer/{route_name}/*.md")
completed_methods = [extract_method_name(f) for f in existing_files]

# 创建剩余任务
remaining_tasks = []
for method in all_methods:
    if method not in completed_methods:
        remaining_tasks.append({
            "content": f"追踪方法: {method}",
            "status": "pending",
            "activeForm": f"追踪 {method}"
        })

TodoWrite(remaining_tasks)

2.3 强制规则

规则说明
必须统计开始前必须统计并输出所有方法数量
必须用 TodoList必须使用 TodoWrite 将每个方法添加为任务
接口优化策略当接口数量 > 3 时,采用以下优化方案:<br>- 第 1 个接口:生成完整调用链追踪(包含所有层级、分支、变量追踪)<br>- 第 2 个及之后的接口:只生成完整 Burp Suite 数据包 + 简单追踪链(不包含详细代码和分支分析)
必须标记状态每个任务完成后立即标记为 completed
禁止提前结束TodoList 中有 pending 任务时禁止结束
必须验证数量结束前必须验证生成的文件数量

2.4 文件生成规则(强制)

所有接口数量统一使用以下策略,无需分批处理:

接口序号报告类型内容
第 1 个完整版所有层级、分支、变量追踪、可控性分析(约 20000-30000 字符)
第 2 个及之后简化版请求模板 + 该方法参数定义 + 调用链 + Sink识别 + 可控性分析(约 2000-4000 字符)

文件命名规范:

规则格式
✅ 正确{项目名}_trace_{方法名}_{时间戳}.md
❌ 错误{方法名}_{时间戳}.md (缺少项目前缀)

变量替换检查(强制):

生成每个简化版报告时,必须确保以下内容被正确替换(禁止保留模板变量):

检查项错误示例正确示例
方法名${method}getRegionWanderSearchCount
路由路径/api/${method}/itc/ws/carQuerygetRegionWanderSearchCount
服务实现类${ServiceImpl}CarQueryServiceImpl
SOAP 方法标签<web:${method}><web:getRegionWanderSearchCount>
调用链方法${ServiceImpl}.${method}()CarQueryServiceImpl.getRegionWanderSearchCount()

文件内容验证:

生成每个文件后,必须检查:

  1. 文件名包含项目前缀(如 myproject_trace_
  2. 文件内容中不包含任何 ${...} 形式的未替换变量
  3. 方法名在报告标题、数据包、调用链中都正确显示

2.5 简化版必须包含内容(强制)

必须包含说明不可省略
HTTP/SOAP 请求模板占位符格式 {{param}},用于说明结构
测试用数据包示例包含实际测试值的完整数据包,可直接用于Burp测试
该方法特有的参数定义表从该方法代码签名读取,不可假设
调用链层级图[L1] → [L2] → [Sink] 格式
Sink 识别识别该方法的最终使用点类型
可控性分析结论该方法参数的可控性判定

2.5.1 测试数据包生成规则(强制)

测试数据包必须包含实际可测试的值,禁止使用占位符:

Java类型占位符(禁止)实际值生成规则
String (ID类){{userId}}数字字符串如 1001
String (名称类){{name}}有意义的测试值如 testUser
String (日期类){{date}}标准格式如 2026-01-01
String (路径类){{path}}合法路径如 /tmp/test.txt
String (URL类){{url}}完整URL如 http://example.com/api
int/Integer{{count}}数字如 10
boolean/Boolean{{flag}}truefalse
JSON字符串{{xxxJson}}完整JSON如 {"id":"1","name":"test"}

生成测试值的方法:

  1. 从代码中提取 - 查找代码注释、单元测试、示例数据中的值
  2. 从字段语义推断 - 根据字段名含义生成合理值(如 emailtest@example.com
  3. 从类型推断 - String用描述性文本,数字用合理范围值
  4. 使用通用安全测试值 - 避免特殊字符,确保请求可正常发送

2.6 方法参数独立分析规则(强制)

在追踪每个方法时,必须执行以下步骤,禁止跳过:

步骤1:读取该方法的实际代码签名

必须读取实际代码获取真实参数,禁止假设:

code
方法A: getDictionaryAll(String codeTypes)           → 1个参数
方法B: getCommonQuery(String searchJson, ...)       → 3个参数
方法C: executeCommand(String cmd, String args)      → 2个参数

步骤2:独立追踪该方法的调用链到 Sink

不同方法可能有不同的 Sink 类型:

方法示例调用链Sink 类型
getCommonQuery→ DAO → SQL执行SQL
executeTask→ Runtime.exec()COMMAND
fetchUrl→ HttpClient.get()HTTP
parseXml→ DocumentBuilder.parse()XML
getDictionaryAll→ Map.get()无敏感Sink

步骤3:分析该方法参数的可控性

根据该方法实际追踪到的 Sink 类型进行分析:

Sink 类型分析要点
SQL参数是否拼接到 SQL
COMMAND参数是否传递到命令执行
HTTP参数是否用于构造 URL
FILE参数是否用于文件路径
XML参数是否传递到 XML 解析器
无敏感Sink标注"无敏感操作"

步骤4:输出该方法的独立分析结果

禁止行为:

  • ❌ 禁止写"参考第1个接口的参数定义"
  • ❌ 禁止复用其他方法的可控性结论
  • ❌ 禁止假设所有方法都有相同的参数或 Sink 类型

2.7 简化版生成前检查清单(每个方法必须执行)

#检查项必须执行
1读取该方法的代码签名,获取实际参数列表
2追踪该方法的调用链到最终使用点
3识别该方法的 Sink 类型(SQL/COMMAND/HTTP/FILE/XML/无)
4分析该方法参数的可控性
5输出该方法的可控性分析表

3. 追踪调用链

从入口方法开始,逐层追踪:

code
Controller/Action 层
    ↓ 调用
Service/Manager 层
    ↓ 调用
DAO/Repository 层
    ↓ 执行
SQL/数据库操作

追踪规则:

  1. 识别方法调用 - 分析方法体中的 this.xxx() 或注入对象的方法调用
  2. 跟踪依赖注入 - 识别 @Autowired@Resource、构造器注入的对象
  3. 分析父类方法 - 如果调用 super.xxx() 或继承方法,追踪到父类
  4. 记录参数传递 - 记录参数如何从上层传递到下层

4. 反编译支持

当源码不可用时,使用 java-decompile-mcp 反编译:

python
# 反编译单个文件
mcp__java-decompile-mcp__decompile_file(
    file_path="/path/to/SomeClass.class",
    output_dir="/path/to/output",
    save_to_file=True
)

# 反编译目录
mcp__java-decompile-mcp__decompile_directory(
    directory_path="/path/to/classes",
    output_dir="/path/to/output",
    recursive=True,
    save_to_file=True,
    max_workers=4
)

# 批量反编译
mcp__java-decompile-mcp__decompile_files(
    file_paths=["/path/to/A.class", "/path/to/B.class"],
    output_dir="/path/to/output",
    save_to_file=True
)

输出格式

单方法路由

文件命名: {项目名}_audit/route_tracer/{路由名}/{项目名}_trace_{路由标识}_{时间戳}.md

目录结构:

code
{project_name}_audit/
└── route_tracer/
    └── {route_name}/
        └── {project_name}_trace_{route_id}_20260204.md

多接口方法路由(重要)

当入口类包含多个业务方法时(如 Web Service、Controller 有多个端点),必须为每个方法生成独立的追踪报告文件。

文件命名规则:

  • 单个方法报告:{项目名}_trace_{方法名}_{时间戳}.md
  • 总索引报告:{项目名}_trace_all_methods_{时间戳}.md

目录结构:

code
{project_name}_audit/
└── route_tracer/
    └── {route_name}/
        ├── {project_name}_trace_getBasicQuery_20260204.md        # 方法1
        ├── {project_name}_trace_getAdvancedQuery_20260204.md     # 方法2
        ├── {project_name}_trace_getDetailQuery_20260204.md       # 方法3
        ├── {project_name}_trace_getImageQuery_20260204.md        # 方法N
        ├── ... (每个方法一个独立文件)
        └── {project_name}_trace_all_methods_20260204.md           # 总索引

路由名说明:

  • 路由名从路由路径提取,去掉前缀斜杠和特殊字符
  • 例如:/itc/ws/carQueryitc_ws_carQuery
  • 例如:/api/user/login.actionapi_user_login

执行流程:

code
正在分析入口类: VehicleQueryServiceImpl

✅ 发现 10 个入口方法

--- 方法 1/10: getBasicQuery ---
[生成独立报告: myproject_trace_getBasicQuery_20260204.md]

--- 方法 2/10: getAdvancedQuery ---
[生成独立报告: myproject_trace_getAdvancedQuery_20260204.md]

...

✅ 所有 10 个方法追踪完成
✅ 生成总索引: myproject_trace_all_methods_20260204.md

总索引报告内容:

markdown
# /api/ws/vehicleQuery Web Service 所有方法追踪索引

生成时间: 2026-02-04
入口类: VehicleQueryServiceImpl
方法总数: 10

## 方法清单

| # | 方法名 | 参数列表 | 详细报告 |
|:--|:-------|:---------|:---------|
| 1 | getBasicQuery | searchJson, pageJson, extend | [查看](myproject_trace_getBasicQuery_20260204.md) |
| 2 | getAdvancedQuery | searchJson, pageJson, extend | [查看](myproject_trace_getAdvancedQuery_20260204.md) |
| 3 | getDetailQuery | searchJson, pageJson, extend | [查看](myproject_trace_getDetailQuery_20260204.md) |
| ... | ... | ... | ... |

详细实现策略请参考:multi-method-tracing.md

输出模板

完整追踪链(适用于第 1 个接口)

完整的调用链追踪报告,包含所有层级、分支、变量追踪等内容。

markdown
# 路由调用链追踪报告

**追踪路由**: `/admin/image/getImageCapture.action`
**生成时间**: 2026-02-04
**项目路径**: /path/to/project

---

## 1. HTTP 请求数据包

### 1.1 完整请求模板

```http
POST /admin/image/getImageCapture.action HTTP/1.1
Host: {{host}}
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID={{session}}

searchJson={{searchJson}}&pageJson={{pageJson}}&extend={{extend}}
```

### 1.2 参数详细定义

#### 顶层参数

| 参数名 | Java类型 | HTTP位置 | 必填 | 说明 |
|:-------|:---------|:---------|:-----|:-----|
| searchJson | String | Body | 是 | JSON格式,反序列化为 ImageCaptureBean |
| pageJson | String | Body | 是 | JSON格式,反序列化为 Page<ImageCapture> |
| extend | String | Body | 否 | 扩展参数 |

#### pageJson 内部结构 (Page<ImageCapture>)

| 字段名 | Java类型 | 说明 | 最终使用位置 |
|:-------|:---------|:-----|:-------------|
| orderBy | String | 排序字段 | **AbstractDao.findSql():234 - SQL拼接** |
| order | String | 排序方向 (asc/desc) | **AbstractDao.findSql():234 - SQL拼接** |
| pageSize | int | 每页条数 | 分页查询 |
| currentPage | int | 当前页码 | 分页查询 |

#### searchJson 内部结构 (ImageCaptureBean)

| 字段名 | Java类型 | 说明 |
|:-------|:---------|:-----|
| userBean | UserBean | 用户信息对象 |
| startTime | String | 开始时间 |
| endTime | String | 结束时间 |
| deviceIds | List<String> | 设备ID列表 |

### 1.3 测试用数据包示例

```http
POST /admin/image/getImageCapture.action HTTP/1.1
Host: 192.168.1.100:8080
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=ABC123

searchJson={"userBean":{"loginName":"admin"},"startTime":"2026-01-01","endTime":"2026-02-01"}&pageJson={"orderBy":"id","order":"desc","pageSize":10,"currentPage":1}&extend=
```

---

## 2. 调用链层级追踪

### [Level 1] Action 入口层

**文件**: `com/example/web/action/ImageCaptureAction.java:125`
**类名**: `ImageCaptureAction`
**方法签名**:
```java
public String getImageCapture(String searchJson, String pageJson, String extend)
```

**完整代码:**
```java
public String getImageCapture(String searchJson, String pageJson, String extend) {
    // pageJson 反序列化为 Page 对象
    Page<ImageCapture> page = (Page)JsonUtils.fromJson(pageJson, (new TypeToken<Page<ImageCapture>>() {
    }).getType(), (String)null);

    this.defaultImageCaptureSearchOrder(page);

    // searchJson 反序列化为 ImageCaptureBean 对象
    ImageCaptureBean searchBean = (ImageCaptureBean)JsonUtils.fromJson(searchJson, ImageCaptureBean.class, (String)null);

    if (searchBean.getUserBean() != null && StringUtil.isEmpty(searchBean.getUserBean().getHostLoginIp())) {
        String addressIp = WebServiceUtil.getWebServiceIp();
        if (StringUtil.isEmpty(addressIp)) {
            addressIp = searchBean.getUserBean().getLoginServerName();
        }
        searchBean.getUserBean().setHostLoginIp(addressIp);
    }

    searchBean.setSecordExcute(false);
    String json = WebServiceUtil.valiatePam((new TypeToken<ReturnMsgBean<ImageCapture>>() {
    }).getType(), new Object[]{page, searchBean});

    if (json == null) {
        // 关键调用: 传递 searchBean, page, extend 到 Manager 层
        json = this.imageCaptureManager.getImageCaptureJson(searchBean, page, extend);
    }

    return json;
}
```

**参数转换:**

| HTTP参数 | 转换后类型 | 转换后变量名 | 传递到下一层 |
|:---------|:-----------|:-------------|:-------------|
| pageJson (String) | Page<ImageCapture> | page | ✅ |
| searchJson (String) | ImageCaptureBean | searchBean | ✅ |
| extend (String) | String | extend | ✅ |

**Page对象关键字段:**
- `page.orderBy` ← pageJson.orderBy (String类型)
- `page.order` ← pageJson.order (String类型)

**下一层调用**: `this.imageCaptureManager.getImageCaptureJson(searchBean, page, extend)`

---

### [Level 2] Manager 服务层

**文件**: `com/example/service/ImageCaptureManager.java:89`
**类名**: `ImageCaptureManager`
**方法签名**:
```java
public String getImageCaptureJson(ImageCaptureBean searchBean, Page<ImageCapture> page, String extend)
```

**完整代码:**
```java
public String getImageCaptureJson(ImageCaptureBean searchBean, Page<ImageCapture> page, String extend) {
    // 关键调用: 将 page 传递给 DAO 层
    Page<ImageCapture> result = this.imageCaptureDao.getImageCapturePage(searchBean, page);
    return JsonUtils.toJson(result);
}
```

**参数传递:**

| 接收参数 | 类型 | 传递到下一层 |
|:---------|:-----|:-------------|
| searchBean | ImageCaptureBean | ✅ |
| page | Page<ImageCapture> | ✅ (含 orderBy, order) |
| extend | String | ❌ (未传递) |

**下一层调用**: `this.imageCaptureDao.getImageCapturePage(searchBean, page)`

---

### [Level 3] DAO 数据访问层

**文件**: `com/example/dao/ImageCaptureDao.java:56`
**类名**: `ImageCaptureDao`
**父类**: `AbstractDao<ImageCapture>`
**方法签名**:
```java
public Page<ImageCapture> getImageCapturePage(ImageCaptureBean searchBean, Page<ImageCapture> page)
```

**完整代码:**
```java
public Page<ImageCapture> getImageCapturePage(ImageCaptureBean searchBean, Page<ImageCapture> page) {
    String sql = buildQuerySql(searchBean);
    // 关键调用: 调用父类 AbstractDao.findSql(),传入 page 对象
    return super.findSql(sql, page);
}
```

**参数传递:**

| 接收参数 | 类型 | 传递到下一层 |
|:---------|:-----|:-------------|
| searchBean | ImageCaptureBean | ❌ (用于构建SQL) |
| page | Page<ImageCapture> | ✅ (传递给父类) |

**下一层调用**: `super.findSql(sql, page)` → 父类 `AbstractDao.findSql()`

---

### [Level 4] 父类基础层 (最终使用点)

**文件**: `com/example/dao/base/AbstractDao.java:234`
**类名**: `AbstractDao<T>`
**方法签名**:
```java
protected Page<T> findSql(String sql, Page<T> page)
```

**完整代码:**
```java
protected Page<T> findSql(String sql, Page<T> page) {
    if (page.getOrderBy() != null) {
        // ⚠️ 最终使用点: orderBy 和 order 直接拼接到 SQL 语句
        sql = sql + " ORDER BY " + page.getOrderBy() + " " + page.getOrder();
    }
    // 执行 SQL 查询
    return executeQuery(sql, page);
}
```

**参数最终使用:**

| 参数 | 类型 | 使用方式 | 代码位置 |
|:-----|:-----|:---------|:---------|
| page.getOrderBy() | String | **直接拼接到 SQL** | AbstractDao.java:235 |
| page.getOrder() | String | **直接拼接到 SQL** | AbstractDao.java:235 |

---

## 3. 调用链总结图

```
HTTP Request
│
├─ searchJson (String) ──→ ImageCaptureBean searchBean
│                              └─ userBean: UserBean
│                              └─ startTime: String
│                              └─ endTime: String
│
├─ pageJson (String) ────→ Page<ImageCapture> page
│                              ├─ orderBy: String ─────────────────────┐
│                              ├─ order: String ───────────────────────┤
│                              ├─ pageSize: int                        │
│                              └─ currentPage: int                     │
│                                                                      │
└─ extend (String) ──────→ (未向下传递)                                │
                                                                       │
┌──────────────────────────────────────────────────────────────────────┘
│
▼ 参数流向追踪

[L1] ImageCaptureAction.getImageCapture()
      │
      │  page (含 orderBy, order), searchBean
      ▼
[L2] ImageCaptureManager.getImageCaptureJson()
      │
      │  page (含 orderBy, order), searchBean
      ▼
[L3] ImageCaptureDao.getImageCapturePage()
      │
      │  page (含 orderBy, order)
      ▼
[L4] AbstractDao.findSql()
      │
      └──→ sql = sql + " ORDER BY " + page.getOrderBy() + " " + page.getOrder()
           ▲                              ▲
           │                              │
           └── orderBy 直接拼接 ──────────┴── order 直接拼接
```

---

## 4. 参数变量名追踪表(核心)

**追踪同一参数在不同类中的变量名变化:**

### 4.1 pageJson.orderBy 参数追踪

| 层级 | 类名 | 变量名 | 类型 | 代码位置 | 说明 |
|:-----|:-----|:-------|:-----|:---------|:-----|
| HTTP | - | `pageJson` | String | 请求Body | 原始JSON字符串 |
| L1 | ImageCaptureAction | `pageJson` → `page.orderBy` | Page.orderBy: String | :125 | JsonUtils.fromJson()反序列化 |
| L2 | ImageCaptureManager | `page.orderBy` | Page.orderBy: String | :89 | 参数名不变,直接传递 |
| L3 | ImageCaptureDao | `page.orderBy` | Page.orderBy: String | :56 | 参数名不变,传递给父类 |
| L4 | AbstractDao | `page.getOrderBy()` | String | :235 | **最终使用: SQL拼接** |

### 4.2 pageJson.order 参数追踪

| 层级 | 类名 | 变量名 | 类型 | 代码位置 | 说明 |
|:-----|:-----|:-------|:-----|:---------|:-----|
| HTTP | - | `pageJson` | String | 请求Body | 原始JSON字符串 |
| L1 | ImageCaptureAction | `pageJson` → `page.order` | Page.order: String | :125 | JsonUtils.fromJson()反序列化 |
| L2 | ImageCaptureManager | `page.order` | Page.order: String | :89 | 参数名不变 |
| L3 | ImageCaptureDao | `page.order` | Page.order: String | :56 | 参数名不变 |
| L4 | AbstractDao | `page.getOrder()` | String | :235 | **最终使用: SQL拼接** |

### 4.3 searchJson 参数追踪

| 层级 | 类名 | 变量名 | 类型 | 代码位置 | 说明 |
|:-----|:-----|:-------|:-----|:---------|:-----|
| HTTP | - | `searchJson` | String | 请求Body | 原始JSON字符串 |
| L1 | ImageCaptureAction | `searchJson` → `searchBean` | ImageCaptureBean | :128 | JsonUtils.fromJson()反序列化 |
| L2 | ImageCaptureManager | `searchBean` | ImageCaptureBean | :89 | 参数名不变 |
| L3 | ImageCaptureDao | `searchBean` | ImageCaptureBean | :56 | **最终使用: buildQuerySql()** |

### 4.4 变量名变化示例(当参数名在不同层改变时)

```
HTTP: pageJson (String)
       ↓ JsonUtils.fromJson()
L1 ImageCaptureAction: page (Page<ImageCapture>)
       ↓ 方法参数传递
L2 ImageCaptureManager: page (Page<ImageCapture>)  ← 同名
       ↓ 方法参数传递
L3 ImageCaptureDao: page (Page<ImageCapture>)  ← 同名
       ↓ super.findSql(sql, page)
L4 AbstractDao: page (Page<T>)  ← 泛型类型变化
       ↓ page.getOrderBy()
最终使用: sql = sql + " ORDER BY " + page.getOrderBy()
```

### 4.5 完整参数追踪汇总

| 原始HTTP参数 | L1变量名 | L2变量名 | L3变量名 | L4变量名 | 最终使用 |
|:-------------|:---------|:---------|:---------|:---------|:---------|
| pageJson | page | page | page | page | page.getOrderBy() → SQL拼接 |
| pageJson.orderBy | page.orderBy | page.orderBy | page.orderBy | page.getOrderBy() | SQL ORDER BY 子句 |
| pageJson.order | page.order | page.order | page.order | page.getOrder() | SQL ORDER BY 子句 |
| searchJson | searchBean | searchBean | searchBean | - | buildQuerySql() |
| extend | extend | - | - | - | 未向下传递 |

5. 参数实际使用分析(CRITICAL - 防止漏洞误判)

追踪每个参数从 HTTP 入口到最终使用点,判断参数是否真正参与敏感操作。

此分析对于准确判定漏洞至关重要,可避免将"参数传递但未使用"的情况误判为漏洞。

5.1 分析说明

状态含义对漏洞判定的影响
✅ 被使用参数值直接或间接参与敏感操作需要进一步检测是否存在漏洞
❌ 未使用参数被传递但未参与敏感操作(被硬编码覆盖/被忽略)排除漏洞可能
⚠️ 部分使用参数的部分字段被使用,部分未使用仅检测被使用的字段

5.2 各漏洞类型的"硬编码覆盖"场景

漏洞类型参数硬编码覆盖场景结论
SQL 注入page.orderBySQL 已硬编码 order by createTime desc参数未使用
命令注入cmd代码硬编码 Runtime.exec("ls -la")参数未使用
SSRFurl代码硬编码 httpClient.get("http://internal/api")参数未使用
文件操作path代码硬编码 new File("/tmp/fixed.txt")参数未使用
XXExml代码使用固定 XML 模板参数未使用
表达式注入expr代码硬编码表达式 SpEL.parse("#{fixed}")参数未使用

5.3 参数使用分析表(输出模板)

markdown
## 参数实际使用分析

| 参数 | Sink类型 | 覆盖类型 | 覆盖条件 | 可控性结论 | 可控场景 |
|:-----|:---------|:---------|:---------|:-----------|:---------|
| page.orderBy | SQL ORDER BY | 条件覆盖 | `isEmpty(orderBy)` | ⚠️ 条件可控 | 非空时可控 |
| page.order | SQL ORDER BY | 无覆盖 | - | ✅ 完全可控 | 任意值 |
| cmd | Runtime.exec | 无覆盖 | - | ✅ 完全可控 | 任意值 |
| url | HTTP请求 | 安全检查覆盖 | `!isInternalUrl(url)` | ⚠️ 条件可控 | 外网URL时可控 |
| path | File操作 | 白名单覆盖 | `!allowedPaths.contains()` | ⚠️ 白名单内可控 | /tmp, /data 等 |
| searchBean.keyword | SQL WHERE | 无条件覆盖 | 总是覆盖 | ❌ 不可控 | 无 |

可控性结论说明:

结论含义审计建议
✅ 完全可控参数无任何覆盖,用户输入直接到达 Sink必须审计 Sink 点安全性
⚠️ 条件可控参数在特定条件下可控需验证绕过条件后审计
❌ 不可控参数被无条件覆盖或安全处理可排除该参数的漏洞可能

5.4 通用覆盖条件识别规则(适用于所有漏洞类型)

在追踪到参数赋值操作后,必须分析覆盖条件,此规则适用于任何类型的参数:

覆盖类型代码特征可控性判定适用场景
无条件覆盖x = "hardcoded"; (不在 if 内)❌ 不可控所有漏洞类型
空值保护覆盖if (isEmpty(x)) { x = default; }⚠️ 非空时可控所有漏洞类型
null保护覆盖if (x == null) { x = default; }⚠️ 非null时可控所有漏洞类型
白名单覆盖if (!list.contains(x)) { x = default; }⚠️ 白名单内可控所有漏洞类型
安全检查覆盖if (!isAllowed(x)) { x = safe; }⚠️ 绕过检查时可控SSRF/文件/命令
格式校验覆盖if (!isValid(x)) { x = default; }⚠️ 符合格式时可控所有漏洞类型
条件分支覆盖if (cond) { x = default; }⚠️ 条件不满足时可控所有漏洞类型

常见覆盖条件识别(通用):

条件类型识别模式可控场景
空值检查isEmpty(), isBlank(), == null, length() == 0非空时可控
白名单检查contains(), Arrays.asList(), allowedList.contains()白名单内可控
黑名单检查!contains(), blacklist.contains() → 拒绝不在黑名单时可控
格式校验matches(), Pattern.compile(), isValid()符合格式时可控
安全检查isAllowed(), isSafe(), checkSecurity()绕过检查时可控
范围检查x > min && x < max, inRange()范围内可控

原有硬编码覆盖检测规则(仍然适用):

敏感操作类型硬编码覆盖判断方法示例
SQL ORDER BY检查 SQL 字符串是否已包含 order bysql + " order by id desc"
SQL WHERE检查条件是否使用参数化 #{} 或硬编码值WHERE status = 1
命令执行检查命令字符串是否使用硬编码命令exec("ls -la")
HTTP 请求检查 URL 是否使用硬编码地址get("http://fixed.com")
文件操作检查路径是否使用硬编码路径new File("/tmp/log.txt")
XML 解析检查 XML 是否来自固定模板parse(FIXED_XML)

5.5 硬编码覆盖详情输出格式

当发现参数被硬编码覆盖时,必须输出详情:

code
### 硬编码覆盖详情

#### {参数名} - 被硬编码覆盖

**代码位置**: `{ClassName}.java:{line}`

**覆盖代码**: {展示硬编码覆盖的代码片段}

**分析**:
- {参数}被传递到{方法}
- 但{敏感操作}已经包含硬编码值
- **结论**: ❌ 参数未使用,不存在漏洞风险

5.6 参数实际使用分析检查清单

在输出追踪报告前,必须完成以下检查:

#检查项状态
1识别所有传递到最终层的参数
2确定每个参数的敏感操作类型
3检查是否存在硬编码覆盖
4标注每个参数的实际使用状态
5对"被硬编码覆盖"的参数输出详情
6分析覆盖条件语义,输出可控性结论

6. 参数可控性分析(CRITICAL - 通用漏洞判定依据)

适用于所有漏洞类型:SQL注入、命令注入、SSRF、文件操作、XXE、表达式注入等。

详细分析原则请参考:CONTROLLABILITY_ANALYSIS.md

6.1 核心分析流程

code
1. 追踪参数从 HTTP 入口到 Sink 的完整路径
2. 识别路径上所有对参数的赋值/覆盖操作
3. 分析覆盖操作的条件语义(理解代码意图,非模式匹配)
4. 输出可控性结论

6.2 覆盖类型与可控性

覆盖类型判定标准可控性结论
无覆盖参数直接传递到 Sink✅ 完全可控
无条件覆盖赋值不在任何条件内❌ 不可控
条件覆盖赋值在条件内,需分析语义⚠️ 条件可控

6.3 条件语义分析(关键)

根据代码语义理解,而非匹配固定方法名:

语义类型判断方法可控场景
空值保护条件判断参数是否为空,为空时设默认值非空时可控
白名单验证条件判断参数是否在允许列表中白名单内可控
安全检查条件判断参数是否安全绕过检查时可控

6.4 输出格式

必须输出可控性分析表:

markdown
| 参数 | Sink类型 | 覆盖类型 | 覆盖条件 | 可控性结论 | 可控场景 |
|:-----|:---------|:---------|:---------|:-----------|:---------|
| {name} | {sink} | {type} | {condition} | {✅/⚠️/❌} | {scenario} |

必须输出可控性汇总:

markdown
| 可控性类型 | 参数列表 | 审计建议 |
|:-----------|:---------|:---------|
| ✅ 完全可控 | {list} | 必须审计 Sink 安全性 |
| ⚠️ 条件可控 | {list} | 需验证绕过条件 |
| ❌ 不可控 | {list} | 可排除漏洞可能 |

6.5 Sink 类型

Sink说明
SQLSQL 拼接/执行
COMMAND系统命令执行
HTTPHTTP 请求发起
FILE文件读写操作
XMLXML 解析
LDAPLDAP 查询
EXPRESSION表达式解析
DESERIALIZE反序列化
RESPONSE响应输出
PATH路径拼接

7. 分支条件追踪(CRITICAL - 避免漏洞误判)

追踪调用链时,必须记录所有条件分支,确保识别代码是否真的会执行!

详细分析原则请参考:BRANCH_TRACING.md

7.1 核心分析流程

code
1. 识别代码中的条件分支(if/else/switch)
2. 分析每个分支的执行内容
3. 判断敏感操作在哪些分支中执行
4. 输出执行路径结论

7.2 必须识别的分支类型

根据代码语义理解,而非匹配固定方法名:

分支类型语义特征追踪要点
环境/平台分支根据运行环境选择不同路径标注各环境执行内容
安全检查分支对输入进行安全验证标注拦截条件
功能开关分支根据配置决定是否执行标注开关状态影响
空值/异常分支检查参数有效性标注提前退出条件
权限判断分支检查用户权限标注权限要求

7.3 提前退出点识别

退出类型语义必须标注
安全拦截退出安全检查失败,拒绝继续⚠️ 安全拦截
空值保护退出参数无效,提前返回⚠️ 提前退出
异常抛出退出条件不满足,抛出异常⚠️ 异常退出

7.4 输出格式

必须输出分支结构:

markdown
methodName(param)
    ├─ [if 条件A] → 分支A执行内容
    │      └─→ 敏感操作 ⚠️
    └─ [else] → 默认分支 / return / throw

必须输出执行路径结论:

markdown
| 路径 | 条件 | 敏感操作 | 可利用性 |
|:-----|:-----|:---------|:---------|
| 路径A | {条件} | ✅ 执行 | 可利用 |
| 路径B | {条件} | ❌ 不执行 | 不可利用 |

追踪策略

策略 1: 依赖注入追踪

识别 Spring/Struts 的依赖注入:

java
// Spring
@Autowired
private ImageCaptureManager imageCaptureManager;

// Struts
private ImageCaptureService faceCaptureService;
public void setImageCaptureService(ImageCaptureService service) {
    this.faceCaptureService = service;
}

追踪 imageCaptureManager.xxx() 调用时,定位到 ImageCaptureManager 类。

策略 2: 继承链追踪

当调用 super.xxx() 或父类方法时:

java
public class ImageCaptureDao extends AbstractDao<ImageCapture> {
    public Page<ImageCapture> query(Page<ImageCapture> page) {
        return super.findSql(sql, page);  // 追踪到 AbstractDao
    }
}

追踪步骤:

  1. 识别 extends AbstractDao
  2. 在父类中查找 findSql 方法
  3. 继续追踪父类的父类(如有)

策略 3: 接口实现追踪

当调用接口方法时:

java
@Autowired
private UserService userService;  // 接口类型

userService.getUser(id);  // 需要找到实现类

追踪步骤:

  1. 识别 UserService 是接口
  2. 查找 implements UserService 的类
  3. 定位到实现类的方法

策略 4: MyBatis/Hibernate 追踪

对于 ORM 框架:

MyBatis:

java
@Mapper
public interface UserMapper {
    List<User> selectByPage(@Param("page") Page page);
}

追踪到对应的 XML 文件中的 SQL 定义。

Hibernate:

java
session.createQuery("FROM User WHERE id = " + id);

直接在代码中识别 HQL/SQL。


反编译工具使用

何时反编译

场景操作
.java 源码存在直接读取分析
只有 .class 文件使用 decompile_file
整个项目只有 .class使用 decompile_directory
父类在 JAR 包中先解压 JAR,再 decompile_file

反编译命令参考

python
# 检查 CFR 状态
mcp__java-decompile-mcp__check_cfr_status()

# 检查 Java 版本
mcp__java-decompile-mcp__get_java_version()

# 如果 CFR 不存在,下载
mcp__java-decompile-mcp__download_cfr_tool(target_dir="/path/to/tools")

# 反编译单个类
mcp__java-decompile-mcp__decompile_file(
    file_path="/path/to/AbstractDao.class",
    save_to_file=True
)

# 反编译整个 DAO 包
mcp__java-decompile-mcp__decompile_directory(
    directory_path="/path/to/com/example/dao",
    recursive=True,
    save_to_file=True
)

输出规范

必须包含的信息

每个层级必须包含:

  1. 文件位置 - 完整包路径 + 文件名 + 行号
  2. 类名 - 包括父类信息(如有)
  3. 方法签名 - 方法名 + 参数列表 + 返回类型
  4. 关键代码 - 展示调用下一层的代码片段
  5. 参数传递 - 说明参数如何传递到下一层

禁止的操作

  • ❌ 不进行漏洞分析或风险评估
  • ❌ 不提供修复建议
  • ❌ 不推断不存在的调用关系
  • ❌ 不省略任何层级

输出文件命名

code
{project_name}_audit/route_tracer/{route_name}/{project_name}_trace_{route_id}_{timestamp}.md

示例:
myproject_audit/route_tracer/itc_ws_carQuery/myproject_trace_getImageCapture_20260204.md

简单追踪链(适用于第 2 个及之后的接口)

必须包含以下内容:请求模板 + 该方法参数定义 + 调用链 + Sink识别 + 可控性分析

markdown
# 路由调用链追踪报告(简化版)

**追踪路由**: `{route}`
**方法**: `{method}`
**生成时间**: {date}

---

## 1. HTTP 请求数据包

### 1.1 完整请求模板
```http
{该方法的完整请求模板}

1.2 该方法参数定义

参数来源: 从 {ClassName}.{method}() 方法签名读取

顶层参数

参数名Java类型说明
{param}{type}{desc}

参数内部结构(如有)

字段名Java类型说明
{field}{type}{desc}

1.3 测试用数据包示例

http
{测试数据包}

2. 调用链追踪

调用链层级

code
[L1] {Class}.{method}()
    ↓
[L2] {Class}.{method}()
    ↓
[Sink] {Sink类型}: {具体位置}

Sink 识别

Sink 类型位置说明
{SQL/COMMAND/HTTP/FILE/XML/无敏感Sink}{Class.java:line}{具体操作}

参数传递关系

参数L1L2...最终使用
{param}{var}{var}...{usage}

3. 可控性分析

参数Sink类型覆盖类型覆盖条件可控性结论可控场景
{param}{sink}{type}{condition}{✅/⚠️/❌/-}{scenario}

可控性汇总:

类型参数审计建议
✅ 完全可控{list}需审计 Sink 安全性
⚠️ 条件可控{list}需验证绕过条件
❌ 不可控{list}可排除漏洞可能
- 无敏感Sink{list}低风险
code

**简化版生成注意事项:**
- 必须从该方法实际代码读取参数,禁止假设
- 必须独立追踪该方法的调用链到 Sink
- 必须识别该方法的 Sink 类型(可能与第1个接口不同)
- 必须输出该方法的可控性分析(禁止复用其他方法结论)
- 如果该方法无敏感 Sink,在可控性分析中标注"无敏感Sink,低风险"

---

## 限制与边界

**仅执行以下操作:**
- 追踪指定路由的调用链
- 输出每层的文件位置和方法签名
- 记录参数传递关系
- 生成 HTTP 请求模板

**不执行以下操作:**
- 不进行安全漏洞分析
- 不提供修复建议
- 不评估风险等级
- 不分析业务逻辑正确性