dhakal-bibek/DedupeAI
GitHub: dhakal-bibek/DedupeAI
一款 Burp Suite 扩展,通过去重 HTTP 历史记录、按端口高亮攻击者/受害者流量并提供 IDOR/BOLA 测试工具与 AI 导出桥接,来优化移动端多账号安全测试工作流。
Stars: 3 | Forks: 1
# DedupeAI
Burp Suite 扩展(Montoya API),它将嘈杂的 HTTP 历史记录转化为**去重、AI 就绪**的攻击面。它将每个新的代理条目标记为 **UNIQUE** 或 **DUPE**,将唯一的条目流式传输到实时 feed 中,根据监听器端口对攻击者/受害者流量进行颜色编码——一个**基于端口的高亮器**(PwnFox 风格,但以代理监听器端口为键),专为 **Android/iOS** 多账号测试而构建——并通过文件桥将去重后的集合直接交给 **Claude Code / AI**,内置 **IDOR/BOLA** 工具。

## 功能
- **Dedupe(去重)** — 每个 HTTP 历史记录条目都会在 Notes 列中被标记为 `[DEDUPE] UNIQUE` / `[DEDUPE] DUPE xN`(签名可配置)。对 Notes 列进行排序 → 所有唯一的行聚集在一起 → 批量选择并提供给你的扫描器或其他扩展。
- **Dedupe Live 标签页** — 一个始终开启、自动刷新的 feed,*仅*显示唯一的请求,无需手动选择。
- **攻击者 / 受害者标记** — 根据监听器端口注入 `X-AI-Use: attacker|victim`,并按端口**和**判定结果(绿色 / 黄色 / 红色 / 灰色)为行着色,适用于多账号 IDOR/BOLA 测试。
- **IDOR/BOLA 工具** — **Magic Cookie**(使用另一个身份的 session 重放请求)和 **Match & Replace**(交换 object id 并观察意外的 `200` 响应),均可直接在唯一请求 feed 中使用。
- **AI 桥接** — **Save request(s) for AI** 和 **Live export → file** 将唯一的集合镜像到 `~/.burp-dedupe//live-unique.http`,以便 Claude Code 可以读取它(Burp 的 MCP 无法查看自定义扩展窗口,因此文件系统是共享通道)。
- **内联 Repeater** — 无需离开当前视图即可编辑和重新发送任何记录的唯一请求。
- **Send unique to Organizer** — 仅将唯一请求(过滤掉重复项)推送到 Burp Organizer,带有可选的 Header 覆盖。
- **Body Only (Pretty JSON)** — 一个只读的响应查看器标签页(Proxy / Repeater / 任何地方),*仅*显示 body,移除了 JSON XSSI 防护符(`)]}'`, `for(;;);`, `while(1);`),并通过无依赖的重新缩进器对 JSON 进行美化打印;当你选择 **Save request(s) for AI** 时,这种仅包含 body 的美化 JSON 是一个可选选项。
## 工作原理
- 注册一个 `ProxyResponseHandler`(Montoya)。落入 HTTP 历史记录的每个响应都会被分类。
- 根据请求/响应的可配置部分(method、host、path、param 名称等)计算**签名**。
- 签名是派生自 SHA-256 的 128 位密钥,存储在 `ConcurrentHashMap` 中 — 快速、线程安全、轻量内存。
- 判决结果作为 `[DEDUPE] UNIQUE` 或 `[DEDUPE] DUPE x3` 盖戳到 Notes 列中。该行也会被高亮显示(可选)。高亮颜色取决于去重判决结果**和**监听器端口 — 请参阅下方的矩阵。
## 端口高亮器(攻击者 / 受害者标记)
- 注册一个 `ProxyRequestHandler`,它根据流量到达的**监听器端口**将识别性 Header 注入到流量中 — 非常适合每个账号都通过自己的代理监听器浏览的多账号 (IDOR/BOLA) 测试。
- 行颜色在 `PortHighlightHandler.colorFor(...)` 中集中决定,并由去重处理器(实时)和历史记录重新标记器应用,因此两者保持一致。颜色是**按端口和判决结果**设定的。默认值(在 `PortHighlightHandler.java` 顶部编辑 `PORT_RULES` 并重新构建):
| 监听器端口 | Unique | Duplicate | 注入的 Header |
|---|---|---|---|
| **8082** (攻击者) | 绿色 | 黄色 | `X-AI-Use: attacker` |
| **8083** (受害者) | 红色 | 灰色 | `X-AI-Use: victim` |
| 任何其他端口 | 黄色 | 灰色 | — |
- 因为颜色具有判决感知能力,所以它是在分类之后应用的 — 因此必须打开 "Highlight rows" / "Stamp Notes" 开关才能显示颜色。
![Burp HTTP 历史记录中的 DEDUPE 判决结果和攻击者/受害者端口标签 — Notes 列中的 [DEDUPE] UNIQUE / DUPE xN,行颜色根据端口和判决结果着色(8082 上的攻击者为绿色/黄色,8083 上的受害者为红色/灰色)](assets/history-verdicts.png)

## 构建
需要 JDK 21+。
```
JAVA_HOME=/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home \
./gradlew build
```
输出:`build/libs/burp-dedupe-0.1.0.jar`
## 在 Burp 中安装
1. Burp → **Extensions** → **Installed** → **Add**
2. Extension type: **Java**
3. 选择 `build/libs/burp-dedupe-0.1.0.jar`
4. 顶部会出现一个新的 **Dedupe** 标签页。
## 使用方法
1. 打开 **Dedupe** 标签页。
2. 选择一个预设(例如 **Request smuggling**,如果你只关心路径唯一性)。
3. 点击 **Apply** — 这也会重置已见集合,以便判决结果保持一致。
4. 浏览 / 重放流量。新条目将被标记。
5. 在 **HTTP history** 中,点击 **Notes** 列标题进行排序。所有 `[DEDUPE] UNIQUE` 行将聚集在一起。多选 → 发送给你的扫描器 / 扩展。
## 右键操作(HTTP history / Site map)
选择行,右键点击 → **Dedupe**:
- **Show only unique requests** — 打开一个样式类似于 Burp HTTP 历史记录的独立窗口:包含 `# / Host / Method / URL / Status / Length / MIME / Notes` 列,行根据其 Burp **高亮颜色**进行着色(因此 attacker=绿色 / victim=红色 / other=黄色 会保留),**Notes** 列显示 `[DEDUPE] …` 判决结果 + `[attacker]/[victim] port N` 标签,下方有只读的请求/响应查看器。Burp 的 API 无法过滤其自身的历史记录表,因此这里显示去重后的集合。(范围外/静态 `SKIP` 行和已知重复项被排除。)
- 这是当前选择的**快照**(在选择行时也可以通过 **Ctrl+9** 访问)。要获取自动更新的视图,请使用 **Dedupe Live** 标签页或 **Live unique window**(见下文)。
- 该表格支持**多选**(Ctrl/Cmd- 或 Shift-点击)。工具栏操作应用于整个选择。
- **Send to Repeater** — 将每个选定的请求发送到新的 Repeater 标签页,**以 method + path 命名**(例如 `GET /test/lasd/something/234`, `POST /sdfsd/dff`),以便轻松区分标签页。(提示:将 [Autorize](https://github.com/Quitten/Autorize) 指向 Repeater 并点击 Send 以运行其 authz 检查。)
- **Save request(s) for AI** — 将所有选定的请求**及其响应保存到一个 `.http` 文件中**(每个请求位于其自己的 `####` 部分中,以其 [case manifest](#case-manifest-per-request) 为前缀),供 Claude Code / AI 读取;在保存对话框中选择目标位置。在对话框中勾选 **Responses: body only, pretty JSON**,将每个响应仅写为其 body(剥离 XSSI 防护符,美化打印 JSON)而不是完整的原始响应 — 此选择将被记住。
- **Magic Cookie** — 使用用户提供的 **auth set**(cookies / bearer token / 自定义 Header)重新发送选定的请求。它会剥离请求现有的 `Cookie` 和 `Authorization`(以及你列出的任何 Header),并**仅**使用你提供的凭据发送 — method、path、body 和所有其他 Header 保持不变。你在对话框中输入一次 auth set(每行一个 `Name: value`),它会在窗口和重启后被记住(Montoya preferences);结果会在各自的窗口中打开,以便你比较状态。非常适合相同请求 / 不同身份的 **IDOR/BOLA** 检查(例如,使用受害者的 session 重放攻击者的请求并观察是否返回 `200`)。
- **Match & Replace (IDOR)** — 对选定的请求应用查找/替换后重新发送,作用于 **path/query**、**body** 或 **两者**(文本匹配,或勾选 **regex**)。专为 IDOR/BOLA 构建:交换 object id(例如 `1001`→`1002`)并观察结果,看在另一个身份的值应被拒绝的情况下是否返回 `200`。**仅重新发送实际包含匹配项的请求** — 其余请求将被跳过,因此你只会命中携带该 id 的 endpoint,并且仅更改了 id。Method、Header 和未触及的部分原样发送(`Content-Length` 会自动刷新)。匹配/替换/范围设置会被记住(Montoya preferences)。
- **Clear** — 清空此窗口(收集的行)。在实时窗口中,新的 `[DEDUPE] UNIQUE` 请求之后会继续到达;已清除的行不会重新出现。
- **Filter box** — 根据 **request**(path、query、headers、body)、**response body** 或列(Host / Method / URL / Status / MIME / Notes)中的任何文本进行过滤;默认为纯子字符串匹配,或勾选 **regex** 进行不区分大小写的正则表达式匹配。从扩展中无法实现完整的 Bambda(Java 代码片段)过滤 — Montoya 仅向 Burp 公开 `bambda().importBambda(...)` 来*加载* Bambda,而不是*评估*它 — 因此这是一个快速的文本/正则过滤器。对于真正的 Bambda,请在这些相同的行上使用 Burp 原生的 HTTP-history 过滤栏。
- **Send unique to Organizer** — 仅将唯一请求(过滤掉重复项)发送到 Burp Organizer,可选择应用 Header 覆盖,并打上批次标签。
## 实时唯一视图 — **Dedupe Live** 标签页(或 Ctrl+9 窗口)
两种进入方式,相同的实时视图 — **无需选择**:
- **Dedupe Live** — 一个始终开启的 Burp suite 标签页(位于 **Dedupe** 旁边);扩展加载后它就在那里。
- 在 **HTTP history** / **Site map** 中按 **Ctrl+9** — 在**选中**行(例如 **Ctrl+A**)的情况下,它会在快照窗口中打开*那些*经过去重的请求;在**没有选择**任何内容的情况下,它会在窗口中打开实时视图。(**Dedupe** 标签页上的 **Live unique window ▶** 按钮始终打开实时视图。)
这是一个 HTTP history 风格的视图,它会**自动收集 Notes 中标记了 `[DEDUPE] UNIQUE` 的每个 Proxy HTTP 历史记录条目** — 并且仅收集这些条目。随着你的浏览,新的唯一请求会自行出现(它每秒刷新几次,**增量**扫描历史记录,因此即使历史记录很大也能保持流畅);Burp 已经折叠掉的重复项(`[DEDUPE] DUPE …`)永远不会显示,并且在你打开它的那一刻,历史记录中已有的唯一请求就会被收集。
工具栏对多选的行进行操作:**Send to Repeater**、**In-scope only**(仅保留 URL 在 Target 范围内的行)、**Save request(s) for AI**、**Magic Cookie**、**Match & Replace**、**Clear**、**Live export → file** 和一个 **filter** 框(子字符串或正则表达式)。
**内联 Repeater:** 选择一行以将其加载到表格下方的**可编辑**请求编辑器中,进行微调,然后 **Send ▶** — 或 **Ctrl+Space** / **Cmd+Enter**。响应显示在右侧,带有 status / length / timing 信息,因此你可以修改记录的请求并重新发送,而无需离开视图(它使用p 的 HTTP 客户端,因此它会落入 **Logger**,而不是 Proxy 历史记录)。在 macOS 上,Ctrl+Space 可能保留用于输入源切换 — 请在此处使用 **Cmd+Enter**。
- 它**会重新读取代理处理器已经写入的判决结果** — 它*不会*重新去重。因此请保持**启用判决结果标记**(这就是写入 `[DEDUPE]` note 的操作);如果关闭标记,则不会收集任何内容。
- 它还会提取 **Stamp existing history** 过程在视图已经打开后标记为 `UNIQUE` 的行。
- 为什么需要专用视图:Burp 自己的 HTTP 历史记录已经实时显示了这些 note,但 Montoya API 无法将该表*过滤*为仅显示 `UNIQUE` 行 — 因此此视图实现了这一点。(该标签页在会话期间存在;关闭 Ctrl+9 窗口会停止该窗口的轮询。)
## 导出到文件(用于 Claude Code / AI)
Burp 的 MCP server 无法查看自定义扩展窗口,因此为了将去重后的请求交给 AI,我们使用**文件系统**作为共享通道。打开 **Live export → file** 开关后(在实时窗口中默认开启),该窗口将以防抖(debounced)的方式写入到一个稳定的、按项目划分的文件夹:
```
~/.burp-dedupe//
live-unique.http ← every unique request it collects, as it arrives
selection.http ← just the rows you currently have selected
```
该文件夹以**当前 Burp 项目**(`api.project().name()`)命名,因此每次测试都有其专属文件夹。每个条目是请求**和**其响应,位于一个以 `####` 分隔的块中,**以一个 [case manifest](#case-manifest-per-request) 作为前缀**;该文件以单行协议开头,告诉 AI 在处理 payload 之前先阅读清单并解释风险。该开关在**实时窗口中默认开启**(在快照/结果窗口中默认关闭,因此它们不会覆盖文件 — 在那里将其打开以进行一次性操作)。
**工作流:** 打开 **Dedupe Live** 标签页(或 Ctrl+9 窗口)→ 它会填充 `[DEDUPE] UNIQUE` 请求并自动将它们镜像 → 在 **Claude Code** 中:*"读取 `~/.burp-dedupe//live-unique.http`"* 以获取完整的去重集合,或读取 `selection.http` 仅获取你已高亮显示的内容。文件夹路径会在打开时记录到扩展的 **Output** 中,并在每次写入后显示在**状态栏**中。
### Case manifest(每个请求)
为了让 AI 获得**案例文件,而不是一堆 HTTP 噪声**,每个导出的请求都以 `#` 注释的**清单**作为前缀 — 这是不可跳过的,它位于每个块的前面:
1. **源请求** — method + URL。
2. **身份角色** — `attacker` / `victim`,来自 `X-AI-Use` Header 或 `[attacker]/[victim] port N` 标签。
3. **唯一的原因** — 去重判决结果以及签名基于的内容。
4. **重放命令** — 现成的 `curl` 命令(包含 auth + body)。
5. **预期的安全失败** — IDOR/BOLA 预言机:在*不同*身份下重放应该被拒绝(`401/403/404`);返回另一个身份数据的 `200` 就是漏洞发现。
```
# --- CASE MANIFEST (read before touching payloads) ------------------------
# 1. Source request : POST https://api-m.paypal.com/v1/tracking/batch/events
# 2. Identity role : victim (X-AI-Use: victim, proxy listener port 8083)
# 3. Why unique : [DEDUPE] UNIQUE — first request with this signature (method + host + path +
# sorted param names + status, per the active preset); its duplicates were folded out.
# 4. Replay command : curl -isSk -X POST 'https://api-m.paypal.com/v1/tracking/batch/events' -H 'Cookie: ...' --data-raw '...'
# 5. Expected safe : original response 200; replayed under a DIFFERENT identity this should be DENIED —
# expect 401/403/404. A 200 returning the other identity's data is the finding.
# --------------------------------------------------------------------------
===== REQUEST =====
POST /v1/tracking/batch/events HTTP/2
...
```
*清单格式由 [Timur Yessenov (@Timur_Yessenov)](https://x.com/Timur_Yessenov) 建议 — 感谢!*

## 预设
| 预设 | 视为唯一的判定条件 |
|---|---|
| Default | method + host + path + 排序后的 param names + status |
| Request smuggling | 仅 method + host + path(忽略 params) |
| IDOR / Auth | method + host + path(数字 ID 归一化)+ 排序后的 param names |
| XSS | method + host + path + 排序后的 query+body param names |
| SQLi | method + host + path + 排序后的 param names |
| SSRF | method + host + path + query param names |
| Open redirect | host + path + query param names(不区分 method) |
| SSTI | method + host + path + param names |
| Path traversal | method + host + 归一化后的 path + query param names |
| Strict | 完整 URL + 所有 params + values + status + content-type |
| Custom | 你勾选的任何内容 |

## 发送唯一请求到 Organizer(右键点击)
主要操作:在 **HTTP history** 中,选择任意数量的行(可以是重复项),右键点击 → **Dedupe → Send unique to Organizer**。扩展程序将会:

1. 使用当前的签名配置重新计算选择的签名(Notes 列中的标记不是必需的 — 唯一性是实时确定的)。
2. 保留每个签名的第一次出现,丢弃其余的。
3. 如果启用了 **Header overrides**,则通过 `HttpRequest.withUpdatedHeader` / `withAddedHeader` 将它们应用于每个请求。
4. 在后台线程上为每个唯一的、已被覆盖的请求调用 `api.organizer().sendToOrganizer(...)`。
从那里,右键点击 **Organizer → Extensions → …** 中的项目,将它们提供给任何扩展。扩展会读取你交给它的请求字节 — 包括你覆盖的 Header — 即使它是通过原始 socket 分发的(例如 HTTP Request Smuggler、Turbo Intruder),因为源字节正是这些扩展用于攻击采样的内容。
### Header 覆盖
在 Dedupe 标签页的 **Header overrides** 部分:
- 粘贴原始 Header 行(例如 `Cookie: a=1; b=2`, `Authorization: Bearer …`),每行一个。空行和 `#` 注释将被忽略。
- 选择 **Replace if present, add if missing** 或 **Replace only (don't add new headers)**。
- 勾选 **Apply header overrides when sending to Organizer** 并点击 **Apply**。
- 保留的 Header(`Host`, `Content-Length`, `Transfer-Encoding`)将被拒绝,并记录警告。
- 格式错误的行将在扩展日志中按行报告。
## 注意事项 / 边缘情况
- **现有历史记录可以被追溯标记。** 使用 Dedupe 标签页中的 **"Stamp existing history"** 按钮遍历 `api.proxy().history()` 并就地应用判决结果。或者勾选 **"Auto-stamp existing history when extension loads"** 以在每次加载时自动执行此操作(在重新打开保存的项目时非常有用)。该作业在后台线程上运行,可以取消。在通过之前会重置已见集合,以保持计数一致。
- **更改配置会重置已见集合**,因此你不会在不同的签名规则下得到混合的判决结果。
- **静态资源**(`.css`, `.js`, 图像、字体等)默认会被跳过,以保持已见集合较小。
- **内存上限**:在达到 *Max tracked signatures*(默认 200k)时,将停止添加新密钥,判决结果变为 `OVRF` — 防止在超大型测试中发生 OOM。
- **范围外**:启用 "In-scope only" 以跳过不在 target scope 中的所有内容。
- **路径归一化**(数字 ID → `{n}`,UUID → `{uuid}`,长十六进制 → `{hex}`)在每个预设中都是可选的;针对 IDOR / path traversal 开启它。
- Header 包含:自由文本字段(逗号分隔,小写)— 可用于在 Host Header 攻击中包含 `host` 别名等。
## 致谢
- AI 导出中每个请求的 **[case manifest](#case-manifest-per-request)** — 源请求、身份角色、唯一的原因、重放命令和预期的安全失败 — 由 **[Timur Yessenov (@Timur_Yessenov)](https://x.com/Timur_Yessenov)** 建议。感谢这个将导出转化为真正案例文件而不是一堆 HTTP 噪声的想法。
- **Body Only (Pretty JSON)** 响应标签页(以及匹配的 Save-for-AI 导出选项)的灵感来自 **[rikeshbaniya](https://github.com/rikeshbaniya)** 同名的 Burp 扩展,在这里使用无依赖的 JSON 重新缩进器重新实现。感谢你的灵感!
标签:AI辅助测试, Burp Suite 插件, HTTP流量分析, JS文件枚举, Web安全, 后台面板检测, 域名枚举, 蓝队分析, 越权测试