First Commit
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s

This commit is contained in:
dela
2026-10-09 08:38:16 +08:00
commit 0335d572de
756 changed files with 201663 additions and 0 deletions
+425
View File
@@ -0,0 +1,425 @@
---
name: api-recon
description: 收集网站API接口时调用该skill。
---
# API Recon(前端接口侦察)
在**已授权**前提下,尽可能完整地发现:**后端 API**(路径、方法、参数、响应体)、**前端路由**、**UI 功能触发点**(Tab、弹窗、表格操作等)。
---
## 边界与禁止(Agent 必读 · 违反即越界)
本 skill **仅做 API / 参数面侦察**,不是漏洞挖掘或渗透利用阶段。
### 任务边界
| 范围 | 允许 | 禁止 |
|---|---|---|
| **目标** | 枚举 path、method、参数、路由、UI 触发点 | SQLi/XSS/越权/爆破/fuzz 漏洞、改包攻击、破坏性操作 |
| **鉴权** | Hook + stub/mock 绕过**客户端**登录门 | 向用户索要或猜测账号密码;尝试真实登录表单提交 |
| **运行时** | 无凭据下 hook 接口,用 mock 响应让 SPA 进入登录后壳层 | 依赖真实后端会话才能继续的流程 |
### 无凭据动态分析(Phase 3 默认)
1. 通过 `preload.js` / `runtime_harvest.js` **拦截并 stub** 登录、权限、菜单等 bootstrap 接口;
2. 对业务查询接口返回 **结构正确、业务码成功、数据可为空** 的 mock body;
3. 使前端在无后端或 401 环境下仍能渲染登录后页面,从而触发更多 XHR/fetch/WebSocket;
4. **空数据、空白表格、占位 UI 均属预期**——勿为此转向真实登录或漏洞测试。
**一句话**:用 mock 撑开前端路由与组件挂载,**只录 outbound 请求**;后端返回什么不重要,重要的是前端**还会发哪些接口**。
### 流程硬禁止
| 禁止 | 替代做法 |
|---|---|
| Phase 1 完成前 grep/curl/Read 主 entry `index-*.js` 提取 API path | 跑 `OUTDIR/harvest_static.py` |
| 手写 `extract_apis.py` 等替代 harvest 的脚本 | 改 `OUTDIR/harvest_static.py` 后重跑 |
| 同一 grep/命令失败 ≥2 次仍重复 | 换策略:读 tool_logs、改 harvest、查 reference |
| 跳过门禁 A/B,直接跑 `scripts/` 原版 | 复制到 OUTDIR 并按目标改 |
| 真实用户名/密码、OTP、OAuth 等鉴权 | stub/mock(见上文) |
| 以「拿真实数据」为由跳过 stub,做越权/注入测试 | 只录 outbound,属 recon 边界 |
| 删除、导出敏感数据、批量写等不可逆操作 | coverage 点击亦同 |
| 未完成 runtime + 动态枚举,声称已获全部页面和接口 | 见「完成定义」或标注局限 |
| 未完成参数触发矩阵 + diff,声称已掌握全部参数 | Phase 3b 矩阵 + Phase 5 diff |
| 用单一 runtime 样本推断必填/可选 | 多样本 diff 或校验规则/错误反推 |
---
## 两层模型 + 运行模式
| 层 | 产出 | 上限 |
|---|---|---|
| **静态**(JS bundle) | 全量 endpoint 路径、路由草案、组包点字段候选 | 无 HTTP 方法;参数须 Phase 1b;漏掉运行时拼接 URL |
| **运行时**(活会话) | 方法 + body + 响应 + 动态 URL + WS/SSE;多样本 diff 补全参数 | 页面须实际渲染才会发请求;单样本不足以定必填/可选 |
| 运行模式 | 引擎 | 适用 |
|---|---|---|
| **depth** | `runtime_harvest.js`(Puppeteer) | API 清单、METHOD/params/响应体、WS/SSE、可复现批量跑 |
| **coverage** | browser + `preload.js` | 点 Tab/弹窗/表格,功能点覆盖更深 |
| **both** | 先 depth 再 coverage | 最完整,耗时最长 |
**参数方法论**(无通用脚本):path 用 harvest/正则;参数用 **锚点扩窗 + UI 绑定链 + 多样本 diff + 错误反推**(grep 配方见 [reference.md](reference.md) J 节)。
---
## 完成定义
全部满足方可声称 recon 完成:
- [ ] **静态**:Phase 1 harvest 产出 `api_static.txt`、`routes.txt`、`js/`
- [ ] **运行时**:至少 depth 或 coverage 之一;coverage/both 须 **Hook 生效 + 动态枚举环**
- [ ] **进壳**:访问业务 path 时非 `/login`(注意 hash 路由)
- [ ] **参数**:coverage/both 完成参数触发矩阵 + `param_samples.json`;Phase 5 合并 `params_merged.json`
- [ ] **深度**(若模块页空白):Phase 4 权限树还原并重跑,直至出现 **module 级 API**(非仅 locale/bootstrap)
- [ ] **交付**:Phase 5 产出齐全(见 Phase 5 产出表);`insert_assets` 写入服务与端点资产
---
## 脚本与门禁
`scripts/` 仅为参考模板,**禁止**直接跑原版并当最终结果。
**规则**:先读 → 按目标改 → 写入 `OUTDIR`(如 `recon/`)→ 记 `CHANGES.md`;不匹配则按方法论重写,只借结构。
| 门禁 | 何时 | 参考脚本 → OUTDIR 副本 | 常见必改项 |
|---|---|---|---|
| **A(静态)** | Phase 0 后、**第一次**跑 harvest/spider 前 | `harvest_static.py` / `spider_mpa.py` | **多数站点默认 regex 可直接跑**;仅 manifest/方言不匹配时改 endpoint 正则、webpack/Vite `publicPath`、MPA exclude/cookie |
| **B(运行时)** | Phase 2 后、跑 depth/coverage 前 | `runtime_harvest.js` / `preload.js` + `config.json` | Cookie/localStorage 键、neutralize 成功值、stubs、login 正则、api 前缀、hash/history |
**SPA 强制顺序**(不可交换;Phase 编号优先于「先探索再脚本」):
| 步骤 | 必须 | 禁止 |
|---|---|---|
| Phase 0 完成后 | 下一条 Bash = `python3 OUTDIR/harvest_static.py <URL> OUTDIR` | curl/grep/Read 主 entry `index-*.js`(通常 >500KB) |
| 门禁 A | 复制脚本 → 按需小改 → **立刻运行** | 先手工提取 API 再决定是否 harvest |
| Phase 1 完成前 | `wc -l` 校验产出;404 改 harvest 重试 | 手写 extract 脚本;对未下载 URL 反复 grep |
| Phase 1b 起 | grep 仅 `OUTDIR/js/*.js` | 用主 bundle 代替 harvest |
- ✅ 复制 `harvest_static.py` → (可选)改 regex → **立即运行**
- ❌ curl 主 bundle → grep 多次 → 写临时 extract → 最后才 harvest
- **MPA**:Phase 0 后下一条 Bash = `python3 OUTDIR/spider_mpa.py ...`
---
## 工具与输出约束
| 约束 | 说明 |
|---|---|
| 大文件 | >100KB 的 `index-*.js` **禁止** Read/grep 进上下文;用 OUTDIR 脚本批处理 |
| grep 输出 | 必须 `\| head -20` 或 `-m 5`;对话只保留 path 摘要,勿贴 bundle 片段 |
| 校验 | 用 `wc -l`、`ls \| wc -l`;勿 Read 整目录 |
| regex 初探 | 可选、≤1 次、仅 ≤50KB 小 chunk 或 HTML;正式静态以 harvest 为准 |
| reference | 配方/模板/排障见 [reference.md](reference.md),勿重复 inline 全文 |
---
## 执行路线图
```
Phase 0 分类 + OUTDIR
→ 门禁 A → Phase 1 harvest(★ 立刻运行 ★)
→ Phase 1b 参数逆向
→ Phase 2 鉴权三道门 → config.json
→ 门禁 B → Phase 3 运行时 + 参数矩阵
→ Phase 4 权限树(必要时)→ 重跑 Phase 3
→ Phase 5 合并报告 + insert_assets批量插入所有发现的服务、端点api资产,无论如何插入时不允许漏掉已发现的资产
```
按序勾选;**前一项未完成不得进入下一 Phase**。
1. [ ] **Phase 0**:初探 SPA/MPA;创建 `OUTDIR` → [Phase 0](#phase-0--分类)
2. [ ] **门禁 A + Phase 1**:复制脚本 → **立刻** harvest → `wc -l` 校验 → [Phase 1](#phase-1--静态)
3. [ ] **Phase 1b**:锚点扩窗 + 绑定层 → `param_candidates.json` → [Phase 1b](#phase-1b--参数逆向)
4. [ ] **Phase 2**:鉴权三道门 → `config.json` → [Phase 2](#phase-2--鉴权三道门)
5. [ ] **门禁 B**:调整 runtime 脚本 → [Phase 3](#phase-3--运行时)
6. [ ] **Phase 3**:depth / coverage / both;确认进壳;参数触发矩阵 → `param_samples.json`
7. [ ] **Phase 4**(若需要):权限树 → patch stubs → 重跑 Phase 3 → [Phase 4](#phase-4--权限树还原)
8. [ ] **Phase 5**:合并产出 + 报告 + `insert_assets` → [Phase 5](#phase-5--合并与报告)
---
## Phase 0 — 分类
拉取入口 HTML,**创建 `OUTDIR`**(勿改 skill 内 `scripts/`):
- **SPA**:空壳 + `<div id=app>` + chunk → Phase 1–5
- **MPA**:SSR + `<form>`、无 endpoint bundle → 门禁 A 后:
```bash
python3 recon/spider_mpa.py <BASE_URL> <OUTDIR> [--cookie "session=..."] [--max 300] [--depth 5] [--exclude "logout|delete|destroy"]
```
产出 `forms.txt`、`links.txt`、`api_inline.txt`。SPA 若 forms ≈ 0 → 切 Phase 1。
---
## Phase 1 — 静态
遵守 [脚本与门禁](#脚本与门禁) · [工具与输出约束](#工具与输出约束)。
```bash
python3 recon/harvest_static.py <BASE_URL> <OUTDIR>
```
harvest:解析 HTML script → webpack/Vite manifest → 下载全部 lazy chunk → 产出 `js/`、`api_static.txt`、`routes.txt`、`chunkmap.txt`。
```bash
wc -l OUTDIR/api_static.txt OUTDIR/routes.txt
ls OUTDIR/js | wc -l
```
- chunk 数 vs manifest:404 须改 harvest 重试,勿手工 curl 逐个 chunk
- `api_static.txt` 过少 → 放宽 OUTDIR 内 endpoint 正则后重跑(见 reference)
### Phase 1b — 参数逆向
path 来自 Phase 1;参数字段须单独 recon。grep 规则见 [工具与输出约束](#工具与输出约束)。
**完成标准**:重要接口能答——字段名、传输位置、类型推断、是否必填、样本值、置信度。
#### 1b.0 — 传输形态
| 形态 | 参数在哪 | 静态优先看 |
|---|---|---|
| REST JSON | body + query | path 锚点旁 `(params\|data\|body)\s*:\s*\{` |
| GraphQL | `variables` | gql 模板、`$page: Int` |
| 传统 form | urlencoded | `<form>`、`FormData` |
| 文件上传 | multipart | `FormData.append` |
| 路径参数 | `/user/:id` | 路由表 + `useParams` / `$route.params` |
| 加密/签名 | 包进 `sign`/`data` | Hook 加密函数入参(reference D 节) |
产出:每接口标注 `transport: query|json|form|graphql|encrypted`。
#### 1b.1 — 锚点扩窗
以已知 path 为锚,扩窗口找组包对象:
```bash
grep -n '"/api/user/list"' OUTDIR/js/*.js | head -20
grep -rhoaE '.{0,120}("/api[^"]+").{0,200}' OUTDIR/js/*.js | head -20
grep -rhoaE '(params|data|body|payload)\s*:\s*\{' OUTDIR/js/*.js | head -20
```
| 包装层 | 参数线索 |
|---|---|
| axios 实例 | `data` / `params` |
| 统一 request | 拦截器注入全局字段 |
| OpenAPI 客户端 | 生成 method 签名 |
| React Query / SWR | hook 第二参数 |
| Vue composable | composable 入参 |
类型残留:`yup`/`zod`/rules、`Form.Item name=`、内嵌 Swagger。
→ `param_candidates.json`:`{ path, fields[], source: "static-callsite", confidence }`
#### 1b.2 — 绑定层
```
Form field → onFinish/handleSubmit → transform → API payload
```
| 绑定源 | 手法 |
|---|---|
| 表单 submit | 跟 submit → transform → API |
| 表格搜索 | `getFieldsValue()` → `params` |
| 路由 | `:id` / `?tab=` |
| 拦截器 | 全局 `tenantId`、分页、sign |
| 枚举 select | `options` → API 枚举值 |
DevTools call stack 从 `fetch`/`XHR.send` 往上追组包函数。
#### 1b.3 — 组包三问(≠ Phase 2 鉴权三门)
| 问 | 要答什么 |
|---|---|
| **组装** | payload 在哪 build、transform 痕迹 |
| **校验** | required、pattern、enum |
| **传输** | path / query / body / multipart / 头 |
拦截器门(Phase 2)顺带读全局注入字段(Authorization、`X-Tenant-Id`、sign)。
#### 1b.4 — 与 Phase 3 衔接
候选字段来自静态/绑定层;**必填/可选/条件依赖**须 Phase 3 参数矩阵 + diff + Phase 5 错误反推。
---
## Phase 2 — 鉴权三道门
在 `OUTDIR/js/` grep(带 `head`),写入 `config.json`(配方见 reference):
| 门 | 问题 | 关键词 |
|---|---|---|
| **渲染门** | 如何判断已登录? | `isLogin`、`getToken`、Cookie/localStorage |
| **拦截器门** | 什么触发跳 `/login`? | `response_code`、`errno`、axios interceptor |
| **内容门** | 菜单/权限从哪来? | `menu`、`permission`、`role`、`acl`、`routes` |
禁止把 localStorage 键名当凭据——须从 chunk/请求链确认。
**出口 = 门禁 B**:结论落到 `config.json`,并改 `OUTDIR/runtime_harvest.js` / `preload.js`。
### Phase 2b — API 观察(可选)
用 OUTDIR 内 `preload.js` 确认会话键名、Authorization、嵌套 API URL:
| 配置 | 产出 |
|---|---|
| `recordDetail: true` | `__API_RECON_DETAIL__` |
| `observe.xhrHeaders: true` | headers 观察 |
| `extractUrlsFromResponse: true` | 响应内子 API |
| `observe.storageReads/cookieReads: true` | 回填 config |
| `neutralizeVueRouter: true` | `__API_RECON_ROUTES__` |
coverage 每轮导出:`__API_RECON_LOG__`、`__API_RECON_DETAIL__`、`__API_RECON_ROUTES__`、`__API_RECON_OBSERVE__`。
---
## Phase 3 — 运行时
须已过门禁 B;遵守 [边界与禁止](#边界与禁止agent-必读--违反即越界) · 无凭据 mock 策略。
`config.json` 设置 `"runtimeMode": "depth" | "coverage" | "both"`(模板见 reference)。
### Hook 与 stub(depth + coverage 共用)
| 层 | 范围 | 目的 |
|---|---|---|
| L1 精确 | auth/权限/bootstrap stub | 过首屏鉴权 |
| L2 负向修正 | 所有 JSON 响应 | 未登录码 → 成功 |
| L3 兜底 | 未命中 L1 的 `/api` 等 | 空成功体,撑开 UI |
- **depth**:fake auth + `forward` 改业务码 + `stubs`;遍历 `routes`(hash/history);产出 `runtime_api.json`
- **coverage**:**document-start** 注入 `preload.js`(CDP `addScriptToEvaluateOnNewDocument` 或 Userscript)
验证:`window.__API_RECON_PRELOAD__` 存在;业务 path 不回 `/login`。
```bash
cd recon && npm install
node runtime_harvest.js config.json
```
### 3b — coverage 动态枚举(必做)
1. 主导航/侧栏 — 每项点击,等网络 1–3s
2. Tab — `role=tab`、`.ant-tabs-tab`
3. 表格 — 首行查看/编辑/详情
4. 工具栏 — 导出、筛选、新建(**避免不可逆删除**)
5. 每进模块 — 合并 API/路由
6. SPA — 对 `routes.txt` 未覆盖 path 受控 `pushState`(MPA 禁止)
**参数触发矩阵**(必做):每模块按操作类型各录一次,**diff 多样本**:
| 操作 | 通常多出的参数 |
|---|---|
| 列表首屏 | 分页 + 默认筛选 |
| 点搜索 | keyword、filter |
| 高级筛选 | 更多 optional |
| 新建/编辑 | 完整 entity |
| 批量/导出/排序 | `ids[]`、`exportType`、`sortField` |
**stub 下 outbound body/headers 仍真实**——以请求为准。录制 → `scan_raw.json`、`param_samples.json`、`api_detail.json`。
- **Vue**:`neutralizeVueRouter: true` + document-start preload
- **React**:`routes.txt` + 侧栏点击 + `pushState`
- **both**:先 3a depth,再 3b coverage
---
## Phase 4 — 权限树还原
**触发**:模块页空白 / 每路由仅 bootstrap(如 locale)→ 内容门未过。
| 现象 | 含义 |
|---|---|
| 进壳成功 | 渲染门 + 拦截器门已过 |
| 侧栏缺项/点击空白 | stub shape 或权限码不全 |
| 每路由 API 相同且极少 | `v-if permission` 未通过 |
| `routes.txt` 远少于 bundle | 须从 auth 模块补全 |
```bash
grep -rhoaE '"/api[^"]*(permission|perm|role|menu|acl)[^"]*"' OUTDIR/js/*.js | sort -u | head -30
grep -rhoaE 'userRouteAuth|getResultTree|routeMap|routeLink|menuList|authList' OUTDIR/js/*.js | head -20
```
典型链:`role_permissions`(flat codes)+ `permissions/all`(tree)→ `getResultTree` → `userRouteAuth[CODE].url`。
```bash
python3 recon/extract_route_map.py recon/js recon/
python3 recon/build_perm_tree.py recon/js recon/ --config recon/config.json
```
中间产出:`route_map.json`、`userRouteAuth.json`、`permissions_tree.json`、`*_stub.json`、`perm_codes_all.txt`。
stub 检查:外层 `response_code` 与拦截器门一致;flat codes 与 tree 对齐;`routes` 覆盖 `route_map` 全部 link。
更新 `config.json` 后**重跑 Phase 3**。大型 SPA 可调 `waitUntil`、`routeTimeout`、`perRouteMs`(见 reference A3/I 节)。
---
## Phase 5 — 合并与报告
### 产出表
| 文件 | 阶段 | 内容 |
|---|---|---|
| `js/`、`api_static.txt`、`routes.txt`、`chunkmap.txt` | 1 | 静态 bundle 与 path |
| `param_candidates.json` | 1b | 静态参数字段候选 |
| `config.json` | 2 | 三道门 + runtime 配置 |
| `runtime_api.json` | 3a | depth 详细录制(含 WS/SSE) |
| `param_samples.json`、`scan_raw.json`、`api_detail.json` | 3b | 多样本、点击日志、detail |
| `route_map.json` 等 | 4 | 权限树中间文件(若执行) |
| `params_merged.json` | 5 | 合并参数字段 + 置信度 |
| `api_merged.txt` | 5 | `METHOD /path [params] [static\|runtime\|both]` |
| `site_map.json` | 5 | 路由、API、params、功能点、局限 |
| **insert_assets** | 5 | 将所有服务、端点资产写入资产库 |
### 5b — 参数合并
从 `param_samples.json` diff,**无通用合并脚本**。置信度规则见 reference J7(高/中/低/待触发)。
### 5c — 错误反推
授权范围内可发不完整请求读 400(**属参数 recon,非漏洞测试**):`field 'x' is required`、枚举错误等。注意 `data` 包装、`variables`、加密前 `bizData`。
报告须注明:runtimeMode、静态/运行时 API 数、参数置信度、未覆盖模块、相对参考脚本的 `CHANGES.md` 摘要。
`site_map.json` 建议结构:
```json
{
"site": "https://example.com",
"runtimeMode": "both",
"appType": "vue-spa",
"routeGuardStrategy": ["nav-neutralize", "L1-auth", "L2-patch", "forward"],
"apisFromStatic": [],
"apisFromRuntime": [],
"apis": [],
"params": [{ "method": "POST", "path": "/api/user/list", "transport": "json", "fields": [] }],
"frontendRoutes": [],
"routesVerifiedByClick": [],
"featuresTriggered": [],
"limitations": ""
}
```
更多字段与 grep 配方见 [reference.md](reference.md)。
---
## 通用说明
- **框架无关**:webpack/Vite/Angular lazy load 方法相同
- **传输**:REST/JSON、GraphQL、WebSocket、SSE;gRPC-web 不在范围
- **SSR**:客户端 fetch 可录;RSC/Server Actions 不完全可枚举
- **盲区**:JSVMP、WASM、HMAC/mTLS 强校验 → 静态 + 标注局限
- **参数盲区**:条件联动、hidden params、WASM 组包 → 「待触发」/「不可达」
- **静态是安全网**:runtime 被挡时静态仍能枚举 endpoint
---
## 附加资源
- Grep 配方、`config.json` 模板、排障、Hook、参数逆向 J 节、site_map 模板:**[reference.md](reference.md)**
- 参考脚本路径见 [脚本与门禁](#脚本与门禁) 表