| 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 默认)
- 通过
preload.js / runtime_harvest.js 拦截并 stub 登录、权限、菜单等 bootstrap 接口;
- 对业务查询接口返回 结构正确、业务码成功、数据可为空 的 mock body;
- 使前端在无后端或 401 环境下仍能渲染登录后页面,从而触发更多 XHR/fetch/WebSocket;
- 空数据、空白表格、占位 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 J 节)。
完成定义
全部满足方可声称 recon 完成:
脚本与门禁
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,勿重复 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。
- Phase 0:初探 SPA/MPA;创建
OUTDIR → Phase 0
- 门禁 A + Phase 1:复制脚本 → 立刻 harvest →
wc -l 校验 → Phase 1
- Phase 1b:锚点扩窗 + 绑定层 →
param_candidates.json → Phase 1b
- Phase 2:鉴权三道门 →
config.json → Phase 2
- 门禁 B:调整 runtime 脚本 → Phase 3
- Phase 3:depth / coverage / both;确认进壳;参数触发矩阵 →
param_samples.json
- Phase 4(若需要):权限树 → patch stubs → 重跑 Phase 3 → Phase 4
- Phase 5:合并产出 + 报告 +
insert_assets → Phase 5
Phase 0 — 分类
拉取入口 HTML,创建 OUTDIR(勿改 skill 内 scripts/):
- SPA:空壳 +
<div id=app> + chunk → Phase 1–5
- MPA:SSR +
<form>、无 endpoint bundle → 门禁 A 后:
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 — 静态
遵守 脚本与门禁 · 工具与输出约束。
python3 recon/harvest_static.py <BASE_URL> <OUTDIR>
harvest:解析 HTML script → webpack/Vite manifest → 下载全部 lazy chunk → 产出 js/、api_static.txt、routes.txt、chunkmap.txt。
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 为锚,扩窗口找组包对象:
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;遵守 边界与禁止 · 无凭据 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。
cd recon && npm install
node runtime_harvest.js config.json
3b — coverage 动态枚举(必做)
- 主导航/侧栏 — 每项点击,等网络 1–3s
- Tab —
role=tab、.ant-tabs-tab
- 表格 — 首行查看/编辑/详情
- 工具栏 — 导出、筛选、新建(避免不可逆删除)
- 每进模块 — 合并 API/路由
- 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 模块补全 |
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。
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 建议结构:
{
"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": [
更多字段与 grep 配方见 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
附加资源