Files
dela 0335d572de
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
First Commit
2026-10-09 08:38:16 +08:00

18 KiB
Raw Permalink Blame History

name, description
name description
api-recon 收集网站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 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,勿重复 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
  2. 门禁 A + Phase 1:复制脚本 → 立刻 harvest → wc -l 校验 → Phase 1
  3. Phase 1b:锚点扩窗 + 绑定层 → param_candidates.json → Phase 1b
  4. Phase 2:鉴权三道门 → config.json → Phase 2
  5. 门禁 B:调整 runtime 脚本 → Phase 3
  6. Phase 3:depth / coverage / both;确认进壳;参数触发矩阵 → param_samples.json
  7. Phase 4(若需要):权限树 → patch stubs → 重跑 Phase 3 → Phase 4
  8. 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. 主导航/侧栏 — 每项点击,等网络 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 模块补全
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": [] }],
  "frontendRoutes": [],
  "routesVerifiedByClick": [],
  "featuresTriggered": [],
  "limitations": ""
}

更多字段与 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

附加资源

  • Grep 配方、config.json 模板、排障、Hook、参数逆向 J 节、site_map 模板:reference.md
  • 参考脚本路径见 脚本与门禁 表