关于 hunt-spa-api
hunt-spa-api 是面向安全测试人员的技能,用于从单页应用的 JavaScript 包中发现 API 主机和路由,并检查未认证访问。它强调先建立受保护端点对照、仅以必要证据验证问题。
主要功能
- 01提取 JavaScript 包中的 API 主机与路由
- 02查找延迟加载的代码块
- 03对照检查未认证端点
- 04验证访问控制差异并限制取证范围
适用场景
- 01检查登录页面背后的 API 是否独立验证身份
- 02从 SPA 代码中梳理未公开的 API 路由
- 03验证不同路由组是否存在认证缺失
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-spa-api安装后在 Claude Code 中输入/hunt-spa-api确认已加载。
详细介绍
何时使用此技能
适用于以下情况:
- 目标主机返回一个很小的 HTML 外壳,以及大型
/static/js/*.js或/_next/static/*捆绑文件(React/Vue/Angular/Next/Svelte SPA) - 你发现名为
console、app、dashboard、portal、admin、panel、manage、internal的子域名 - 侦察发现任何
*api*、*-api*、api.*主机 - 登录页面受 OAuth/SSO 限制(前端的身份验证机制并不能说明 API 是否强制身份验证)
核心要点:SPA 是后端 API 的客户端,它会向任何查看源代码的人提供该 API 的完整映射——包括主机、路由,有时还有密钥。 登录页面受到保护,并不能说明其背后的 API 是否会检查令牌。身份验证通常在 网关/登录 处实施,却可能在 API 的某个 路由组 中缺失。
不要因为“应用需要登录”就跳过这一步——这恰恰是它最有用的时候。
操作步骤(5 步)
1. 获取外壳并列出所有捆绑文件
curl -s https://console.target.com/ -o index.html
# React/CRA:
grep -oE '/static/js/[^"]+\.js' index.html
# Next.js:
grep -oE '/_next/static/[^"]+\.js' index.html
# generic:
grep -oiE 'src="[^"]+\.js[^"]*"' index.html
下载每个捆绑文件(文件可能有数 MB,这没关系,里面都是路由数据):
mkdir bundles
for j in $(grep -oE '/static/js/[^"]+\.js' index.html | sort -u); do
curl -s "https://console.target.com$j" -o "bundles/$(echo "$j"|tr '/' '_')"
done
2. 从捆绑文件中提取 API 主机、路由和密钥
B=bundles/*.js
# Backend API hosts (incl. dev/beta/staging variants — often weaker auth)
grep -ohiE 'https://[a-z0-9.-]*(api|console|backend|service)[a-z0-9.-]*\.target\.com[a-z0-9/_-]*' $B | sort -u
# Versioned API base paths
grep -ohiE '/api/v[0-9]+/?' $B | sort -u
# Route literals — minified bundles store routes as STRING segments, not full URLs.
# Grep for quoted "resource/action" strings:
grep -ohiE '"[a-z0-9_-]+/[a-z0-9_/-]+"' $B | tr -d '"' \
| grep -iE '(login|user|account|order|billing|invoice|payment|deal|report|token|otp|password|reset|admin|profile|auth|upload|export|role|permission|dashboard|wallet|finance|sales)' | sort -u
# Secrets (validate before trusting — most AIza keys are Maps/analytics, not Auth)
grep -ohiE '(AIza[0-9A-Za-z_-]{35}|AKIA[0-9A-Z]{16}|sk_live_[0-9A-Za-z]+|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}|apiKey["'"'"']?\s*[:=]\s*["'"'"'][^"'"'"']+)' $B | sort -u
注意:压缩器会将路由存储为拼接后的字符串片段(例如 "account/payment/list"),而不是完整的 /api/v2/... URL,因此简单搜索 /api/v* 不会有结果。请搜索资源名路由字符串,并自行补上基址。
懒加载(异步)分块——不要只检查 HTML 中引用的捆绑文件。 CRA/webpack SPA 在 index.html 中只引用 runtime+main+vendor;带编号的异步路由分块名称位于 main.js 的分块映射中,并在运行时加载,因此只搜索外壳中 /static/js/*.js 会漏掉非即时加载分块中的路由。
grep -oE '[0-9]+:"[a-f0-9]+"' bundles/*main*.js # {chunkId:"hash"} pairs
# reconstruct /static/js/<id>.<hash>.chunk.js, download each, re-run route/host/secret harvest
# asset-manifest.json (if present) lists them all -> hunt-source-leak
3. 建立对照组——找到一个确实受限的端点
在判定存在漏洞之前,向一个你预期受到保护的端点发送未经身份验证的请求,并记录正确拒绝请求时的响应:
curl -s -X POST https://api.target.com/api/users -H 'Content-Type: application/json' -d '{}'
# secure → {"error":"Missing or invalid authorization header"} or HTTP 401
这就是你的对照。相邻 API(例如第二个 API 主机,或同一主机上的另一个路由组)是理想的对照对象——它们使用相同技术栈,因此不同的响应意味着真实的授权缺失,而非行为差异。
4. 对每个路由组进行未经身份验证的测试,使用两种方法
对于发现的每条路由,在不带 Authorization 标头的情况下发送请求,并与对照组比较:
for r in <routes>; do
curl -s -o /tmp/r -w "[%{http_code}] $r\n" -X POST -H 'Content-Type: application/json' -d '{}' "https://api.target.com/api/v2/$r"
done
判断方式:
401/"Missing authorization"→ 受限(符合预期)。继续。200且包含数据 → 未经身份验证的数据泄露。发现漏洞。400 "field X is mandatory"→ 路由在未经身份验证检查的情况下处理了请求,并进入业务逻辑验证 → 身份验证绕过;提供最少量字段以确认。200+ 详细的数据库/堆栈错误(例如PROCEDURE db_x.sp_y does not exist)→ 未经身份验证即访问到数据层;同时表明存在 SQLi 攻击面。- 名为
is_admin/is_internal/requested_by/role_id/account_type的必填字段 → 授权取决于客户端提供的参数——设置权限标记即可自行提升权限。属于严重级别。
5. 扩展验证并证明(尽量少做)
- 一个端点返回的 ID(
account_id、order_id、deal_id)是其他端点使用的键——这些 ID 能证明整个路由器都可访问,而不只是单条路由。 - 测试
dev-/beta-/staging-API 变体——它们通常身份验证更弱或已禁用。 - 检查响应标头:
Access-Control-Allow-Origin: *会加剧问题(任何 Web 来源都能从受害者的浏览器读取响应)。 - **只进行满足最低证明要求的测试。**少量记录(或
totalCount)就能确认检查缺失。不要枚举整张表——参见redteam-mindset的数据最小化边界。漏洞在于缺少身份验证,而非数据量。
“SSO 登录背后的 API”真正意味着什么
一种常见且危险的架构:
console.target.com(SPA)→ 登录使用 Entra/Okta/Google OAuth(看起来无懈可击)。api.target.com(后端)→ 有些路由组会验证 Bearer 令牌,有些路由组却忘了加中间件。
如果 API 没有在每条路由上独立验证令牌,那么前端登录就只是演戏。无论登录界面锁得多严,都要直接、不带凭据地测试 API。
反模式
- “应用要求登录,所以 API 一定受到了保护。” 不对——直接以未认证状态测试 API。这正是重点。
- “压缩后的包,没法阅读。” 你不需要阅读它——用 grep 搜索主机名/路由/机密。只需 5 分钟。
- “
/api/v1/foo返回了 404,所以这里没有 API。” 不对。可能是基础路径或请求方法不对。试试/api/、/api/v2/、用 POST 而不是 GET,以及从压缩包中找到的精确路由字符串(Express 的 404 会回显路径——可用它来校准)。 - “找到了 Aiza 密钥 → 严重机密。” 先验证——大多数是 Maps/分析服务密钥(identitytoolkit 返回
CONFIGURATION_NOT_FOUND表示未启用 Auth)。不要夸大结论。 - 拿到 200 响应后就转储整个数据集。 在 PoC 阶段就停下。(
redteam-mindset。) - 把创建账户/写入端点当作“证据”。 读取端点就足以证明存在认证缺口,无需创建状态。绝不要为了“演示”而 POST 到
create/signup/upload——这属于破坏性写入,需要针对每项操作获得明确授权。
相关技能与链路
hunt-api-misconfig— API 映射完成后,针对每条路由运行更全面的错误配置检查矩阵(方法篡改、批量赋值、JWT alg 混淆)。hunt-idor—account_id/order_id等参数可直接用于对已发现路由进行 IDOR 测试。hunt-source-leak— 源映射(*.js.map)可还原原始源代码,以便更深入地提取路由/机密;使用的是同样的采集技巧。hunt-nextjs— 对于 Next.js 目标,在此基础上叠加中间件绕过(x-middleware-subrequest)和/_next/data路由测试。redteam-mindset— 数据最小化原则决定了第 5 步的边界:证明检查缺失即可,不要导出整张表。recon-scope-triage— 测试前确认 API 主机确实属于目标(不要攻击同名第三方的 API)。