hunt-shadow-api
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOWASP API9 — Improper Inventory Management (Shadow / Zombie APIs)
OWASP API9 — 不当库存管理(影子/僵尸 API)
As an API evolves, old versions and internal/staging routes routinely stay reachable without
receiving the same security fixes as the current version — because nobody tracks that they
still exist. The bug is rarely in one endpoint; it's in the delta between what an old
version enforces and what the current version enforces on the same operation.
随着 API 不断演进,旧版本以及 internal/staging routes 往往会继续处于可访问状态,却没有获得与当前版本相同的安全修复——因为没人跟踪它们仍然存在。问题很少出在单个 endpoint;真正的问题在于旧版本与当前版本在同一 operation 上所执行策略之间的 差异。
When to use
使用场景
Trigger when:
- Versioned paths are visible (,
/v1/,/v2/) or/api/2023-01-01//Acceptheaders are in play.X-API-Version - A changelog, release notes, or deprecation notice references removed/old API behavior.
- A mobile APK/IPA (via /
apk-redteam-pipeline) hardcodes endpoints that look like an older backend version than the current web app calls.ios-redteam-pipeline - Multiple OpenAPI/Swagger specs are discoverable, or in one spec implies others exist.
info.version
DO NOT use for single-version APIs with no version history — there's nothing to diff; go
straight to for direct exploitation of the one surface that exists.
hunt-api-misconfig出现以下情况时触发:
- 可以看到版本化路径(、
/v1/、/v2/),或者使用了/api/2023-01-01//Acceptheader。X-API-Version - changelog、release notes 或 deprecation notice 引用了已删除或旧版 API 行为。
- 移动端 APK/IPA(通过 /
apk-redteam-pipeline)硬编码了看起来比当前 Web 应用所用后端版本更旧的 endpoint。ios-redteam-pipeline - 可以发现多个 OpenAPI/Swagger 规范,或者某个规范中的 暗示还存在其他规范。
info.version
不要将其用于没有版本历史的单版本 API——此时没有可对比的内容;请直接使用 ,对唯一存在的攻击面进行直接漏洞利用。
hunt-api-misconfigStage 1 — Enumerate the Full Version Surface
Stage 1 — 枚举完整的版本攻击面
bash
undefinedbash
undefinedPath-based versioning
Path-based versioning
for v in v1 v2 v3 v4 beta alpha internal legacy old 2022-01-01 2023-01-01 2024-01-01; do
curl -s -o /dev/null -w "%{http_code} /api/$v/\n" "https://$TARGET/api/$v/"
done
for v in v1 v2 v3 v4 beta alpha internal legacy old 2022-01-01 2023-01-01 2024-01-01; do
curl -s -o /dev/null -w "%{http_code} /api/$v/\n" "https://$TARGET/api/$v/"
done
Header-based versioning
Header-based versioning
curl -s -H "X-API-Version: 1" https://$TARGET/api/users
curl -s -H "Accept: application/vnd.company.v1+json" https://$TARGET/api/users
curl -s -H "X-API-Version: 1" https://$TARGET/api/users
curl -s -H "Accept: application/vnd.company.v1+json" https://$TARGET/api/users
Subdomain-based versioning
Subdomain-based versioning
for sub in api api-v1 api-v2 apiv1 apiv2 legacy-api old-api internal-api staging-api; do
curl -s -o /dev/null -w "%{http_code} $sub\n" "https://$sub.$TARGET/"
done
A `200`/`401`/`403` on an old version path (anything but `404`/connection-refused) means the
version is still live and worth carrying into Stage 3, even if it demands auth.
---for sub in api api-v1 api-v2 apiv1 apiv2 legacy-api old-api internal-api staging-api; do
curl -s -o /dev/null -w "%{http_code} $sub\n" "https://$sub.$TARGET/"
done
旧版本路径返回 `200`/`401`/`403`(任何非 `404`/connection-refused 的结果),都意味着该版本仍然在线,值得将其带入 Stage 3,即使它要求身份认证。
---Stage 2 — Pull Every Reachable Spec, Not Just the Linked One
Stage 2 — 拉取所有可访问的规范,而不只是已链接的规范
bash
for path in openapi.json swagger.json v1/swagger.json v2/swagger.json v3/api-docs \
api-docs.json swagger/v1/swagger.json .well-known/openapi.json; do
curl -s -o /dev/null -w "%{http_code} /$path\n" "https://$TARGET/$path"
donebash
for path in openapi.json swagger.json v1/swagger.json v2/swagger.json v3/api-docs \
api-docs.json swagger/v1/swagger.json .well-known/openapi.json; do
curl -s -o /dev/null -w "%{http_code} /$path\n" "https://$TARGET/$path"
doneWayback Machine — a DEPRECATED version's spec often stays indexed after the live link is removed
Wayback Machine — a DEPRECATED version's spec often stays indexed after the live link is removed
curl -s "http://web.archive.org/cdx/search/cdx?url=$TARGET/*swagger*&output=json&collapse=urlkey"
curl -s "http://web.archive.org/cdx/search/cdx?url=$TARGET/*openapi*&output=json&collapse=urlkey"
When more than one spec resolves (a current one and an archived/old one), diff the endpoint
inventories directly:
```bash
jq -r '.paths | keys[]' v1-swagger.json | sort > /tmp/v1_paths.txt
jq -r '.paths | keys[]' v2-swagger.json | sort > /tmp/v2_paths.txt
comm -23 /tmp/v1_paths.txt /tmp/v2_paths.txt # in v1 only — candidates for "still live but forgotten"For every path in that diff, confirm it's still reachable against the v1 base URL. A route
documented only in the old spec that still returns something other than is a zombie-
endpoint candidate — carry it into Stage 3.
404curl -s "http://web.archive.org/cdx/search/cdx?url=$TARGET/*swagger*&output=json&collapse=urlkey"
curl -s "http://web.archive.org/cdx/search/cdx?url=$TARGET/*openapi*&output=json&collapse=urlkey"
当有多个规范可以解析时(一个当前规范,以及一个归档或旧规范),直接对比它们的 endpoint 清单:
```bash
jq -r '.paths | keys[]' v1-swagger.json | sort > /tmp/v1_paths.txt
jq -r '.paths | keys[]' v2-swagger.json | sort > /tmp/v2_paths.txt
comm -23 /tmp/v1_paths.txt /tmp/v2_paths.txt # in v1 only — candidates for "still live but forgotten"对于该差异中的每个 path,都要确认它仍可通过 v1 base URL 访问。一个仅记录在旧规范中、但仍返回非 内容的 route,就是 zombie-endpoint 候选项——将其带入 Stage 3。
404Stage 3 — Behavioral Diff Between Old and Current Version
Stage 3 — 旧版本与当前版本之间的行为差异
For each operation that exists in both versions, compare security-relevant behavior, not
response shape. Response shape differences are Informational; behavioral security regressions
are the finding.
- Auth strength. Does the old version accept no token, an expired token, or a lower-
privilege token that the current version rejects?
bash
curl -s -H "Authorization: Bearer $EXPIRED_TOKEN" https://$TARGET/api/v1/users/me -w '\n%{http_code}\n' curl -s -H "Authorization: Bearer $EXPIRED_TOKEN" https://$TARGET/api/v2/users/me -w '\n%{http_code}\n' - Rate limiting. Burst the same number of requests against both versions' equivalent
endpoint; a missing on the old version means rate-limiting was added later and never backported.
429 - Input validation. Send the identical injection/oversized/malformed payload to both; the
old version accepting what the new one rejects means hardening happened forward-only —
chain into whichever injection class the payload targets (,
hunt-sqli, etc.).hunt-idor - Field exposure. Does the old version's response body include fields — internal IDs, other users' data, internal notes, PII — that the current version has since redacted?
对于同时存在于两个版本中的每个 operation,应比较与安全相关的行为,而不是 response shape。Response shape 差异属于 Informational;行为安全回归才是需要报告的发现。
- Auth strength。 旧版本是否接受无 token、过期 token 或低权限 token,而当前版本会拒绝这些 token?
bash
curl -s -H "Authorization: Bearer $EXPIRED_TOKEN" https://$TARGET/api/v1/users/me -w '\n%{http_code}\n' curl -s -H "Authorization: Bearer $EXPIRED_TOKEN" https://$TARGET/api/v2/users/me -w '\n%{http_code}\n' - Rate limiting。 向两个版本的等价 endpoint 突发发送相同数量的请求;如果旧版本没有返回 ,而当前版本存在该限制,则说明 rate limiting 是后来添加的,且从未回移到旧版本。
429 - Input validation。 向两个版本发送完全相同的 injection、超大或畸形 payload;如果旧版本接受而新版本拒绝,则说明加固措施仅向前生效——根据 payload 针对的 injection 类型继续 chain(例如 、
hunt-sqli等)。hunt-idor - Field exposure。 旧版本的 response body 是否包含当前版本已经隐藏的字段,例如内部 ID、其他用户数据、内部备注或 PII?
Stage 4 — Deprecated / Internal Routes Never Referenced by the Current UI
Stage 4 — 当前 UI 从未引用的弃用或内部 Route
- Grep JS bundles for API calls no visible UI flow triggers (,
/internal/,/admin/,/debug/,/_internal/,/test/) — reuse/staging/'s JS-bundle grep patterns for this specifically.hunt-source-leak - Check /
robots.txtfor disallowed API paths — a self-inflicted disclosure.sitemap.xml - Mobile-app endpoint inventories (via /
apk-redteam-pipeline) very often reference an older backend version than the current web app calls. Treat every APK/IPA-sourced endpoint as a version-diff candidate against the live web API.ios-redteam-pipeline
- 在 JS bundle 中 grep 当前可见 UI 流程不会触发的 API 调用(、
/internal/、/admin/、/debug/、/_internal/、/test/)——专门针对此目的复用/staging/的 JS-bundle grep 模式。hunt-source-leak - 检查 /
robots.txt中被禁止访问的 API path——这属于系统主动造成的信息披露。sitemap.xml - 移动应用的 endpoint 清单(来自 /
apk-redteam-pipeline)通常会引用比当前 Web 应用更旧的后端版本。应将每个源自 APK/IPA 的 endpoint 视为与在线 Web API 进行版本差异对比的候选项。ios-redteam-pipeline
False-Positive Gate
误报过滤
- A version difference alone (different response shape, cosmetic field renaming) is Informational. The finding is a security-relevant regression — auth, rate-limit, or validation that got weaker going backward in version history.
- Confirm the old endpoint is not simply an alias/proxy to the current implementation before claiming a behavioral difference — send a payload that would actually behave differently under old vs. new logic, not just compare a version string in the response body.
- A on a path that just serves a static "this API version is deprecated, use v2" message is not a finding — confirm the underlying operation still executes.
200
- 单纯的版本差异(response shape 不同、字段仅做表面重命名)属于 Informational。真正的发现是与安全相关的回归——沿版本历史回溯时,auth、rate-limit 或 validation 变得 weaker。
- 在声明存在行为差异之前,确认旧 endpoint 并非只是当前实现的 alias/proxy——应发送在旧逻辑与新逻辑下确实会产生不同行为的 payload,而不只是比较 response body 中的版本字符串。
- 如果某个 path 返回 ,但只提供静态的“this API version is deprecated, use v2”消息,则不构成发现——必须确认底层 operation 仍会执行。
200
Severity Table
严重性表
| Finding | Severity |
|---|---|
| Old version bypasses auth entirely where current version requires it | Critical |
| Old version missing rate-limit present on current version | Medium–High (chain via |
| Old version leaks extra fields (PII, internal IDs) vs. current | Medium–High |
| Old version accepts payloads the current version now validates/sanitizes | High (chain to the underlying injection class) |
| Version is reachable but behaviorally identical to current | Informational |
| 发现项 | 严重性 |
|---|---|
| 当前版本要求 auth,但旧版本完全绕过 auth | Critical |
| 当前版本存在 rate-limit,但旧版本缺失 | Medium–High(可通过 |
| 旧版本相比当前版本泄露了额外字段(PII、内部 ID) | Medium–High |
| 当前版本会校验或清理 payload,但旧版本仍然接受 | High(可 chain 至底层 injection 类型) |
| 版本仍可访问,但行为与当前版本完全相同 | Informational |
Related Skills & Chains
相关 Skills 与 Chains
- — owns exploitation once a spec or endpoint is in hand (mass assignment, JWT attacks, OData, Swagger-chain attacks). This skill hands it a sharper target: "here's a zombie endpoint with weaker validation than the current one."
hunt-api-misconfig - — owns host/subdomain-level discovery (
hunt-subdomainas its own host, potential takeover). This skill owns what happens once you're inside a given host's version surface.api-v1.target.com - — JS-bundle grep for internal/undocumented calls; reused here specifically for version-diffing rather than secret extraction.
hunt-source-leak - /
apk-redteam-pipeline— mobile builds routinely hardcode an older API version; every mobile-sourced endpoint is a version-diff candidate.ios-redteam-pipeline - — a rate-limit regression found here is only a complete finding once chained to actual brute-forceable impact (login, OTP, enumeration).
hunt-brute-force
- —— 负责在获得规范或 endpoint 后进行漏洞利用(mass assignment、JWT 攻击、OData、Swagger-chain 攻击)。本 skill 会为其提供更明确的目标:“这里有一个 validation 比当前版本更弱的 zombie endpoint。”
hunt-api-misconfig - —— 负责 host/subdomain 级发现(例如将
hunt-subdomain作为独立 host 进行发现,以及潜在的接管风险)。本 skill 负责进入给定 host 的版本攻击面之后所进行的分析。api-v1.target.com - —— 使用 JS-bundle grep 查找内部或未文档化调用;本 skill 专门将其复用于版本差异对比,而非 secret extraction。
hunt-source-leak - /
apk-redteam-pipeline—— 移动端构建产物经常硬编码较旧的 API 版本;每个源自移动端的 endpoint 都是版本差异对比的候选项。ios-redteam-pipeline - —— 此处发现的 rate-limit 回归,只有在 chain 至实际可 brute-force 的影响(登录、OTP、enumeration)后,才构成完整发现。
hunt-brute-force