hunt-shadow-api

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OWASP 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/
    ,
    /api/2023-01-01/
    ) or
    Accept
    /
    X-API-Version
    headers are in play.
  • A changelog, release notes, or deprecation notice references removed/old API behavior.
  • A mobile APK/IPA (via
    apk-redteam-pipeline
    /
    ios-redteam-pipeline
    ) hardcodes endpoints that look like an older backend version than the current web app calls.
  • Multiple OpenAPI/Swagger specs are discoverable, or
    info.version
    in one spec implies others exist.
DO NOT use for single-version APIs with no version history — there's nothing to diff; go straight to
hunt-api-misconfig
for direct exploitation of the one surface that exists.

出现以下情况时触发:
  • 可以看到版本化路径(
    /v1/
    、
    /v2/
    、
    /api/2023-01-01/
    ),或者使用了
    Accept
    /
    X-API-Version
    header。
  • changelog、release notes 或 deprecation notice 引用了已删除或旧版 API 行为。
  • 移动端 APK/IPA(通过
    apk-redteam-pipeline
    /
    ios-redteam-pipeline
    )硬编码了看起来比当前 Web 应用所用后端版本更旧的 endpoint。
  • 可以发现多个 OpenAPI/Swagger 规范,或者某个规范中的
    info.version
    暗示还存在其他规范。
不要将其用于没有版本历史的单版本 API——此时没有可对比的内容;请直接使用
hunt-api-misconfig
,对唯一存在的攻击面进行直接漏洞利用。

Stage 1 — Enumerate the Full Version Surface

Stage 1 — 枚举完整的版本攻击面

bash
undefined
bash
undefined

Path-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"
done
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"
done

Wayback 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


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
404
is a zombie- endpoint candidate — carry it into Stage 3.


当有多个规范可以解析时(一个当前规范,以及一个归档或旧规范),直接对比它们的 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 访问。一个仅记录在旧规范中、但仍返回非
404
内容的 route,就是 zombie-endpoint 候选项——将其带入 Stage 3。

Stage 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
    429
    on the old version means rate-limiting was added later and never backported.
  • 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
    ,
    hunt-idor
    , etc.).
  • 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 突发发送相同数量的请求;如果旧版本没有返回
    429
    ,而当前版本存在该限制,则说明 rate limiting 是后来添加的,且从未回移到旧版本。
  • 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/
    ,
    /staging/
    ) — reuse
    hunt-source-leak
    's JS-bundle grep patterns for this specifically.
  • Check
    robots.txt
    /
    sitemap.xml
    for disallowed API paths — a self-inflicted disclosure.
  • Mobile-app endpoint inventories (via
    apk-redteam-pipeline
    /
    ios-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.

  • 在 JS bundle 中 grep 当前可见 UI 流程不会触发的 API 调用(
    /internal/
    、
    /admin/
    、
    /debug/
    、
    /_internal/
    、
    /test/
    、
    /staging/
    )——专门针对此目的复用
    hunt-source-leak
    的 JS-bundle grep 模式。
  • 检查
    robots.txt
    /
    sitemap.xml
    中被禁止访问的 API path——这属于系统主动造成的信息披露。
  • 移动应用的 endpoint 清单(来自
    apk-redteam-pipeline
    /
    ios-redteam-pipeline
    )通常会引用比当前 Web 应用更旧的后端版本。应将每个源自 APK/IPA 的 endpoint 视为与在线 Web API 进行版本差异对比的候选项。

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
    200
    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.

  • 单纯的版本差异(response shape 不同、字段仅做表面重命名)属于 Informational。真正的发现是与安全相关的回归——沿版本历史回溯时,auth、rate-limit 或 validation 变得 weaker。
  • 在声明存在行为差异之前,确认旧 endpoint 并非只是当前实现的 alias/proxy——应发送在旧逻辑与新逻辑下确实会产生不同行为的 payload,而不只是比较 response body 中的版本字符串。
  • 如果某个 path 返回
    200
    ,但只提供静态的“this API version is deprecated, use v2”消息,则不构成发现——必须确认底层 operation 仍会执行。

Severity Table

严重性表

FindingSeverity
Old version bypasses auth entirely where current version requires itCritical
Old version missing rate-limit present on current versionMedium–High (chain via
hunt-brute-force
)
Old version leaks extra fields (PII, internal IDs) vs. currentMedium–High
Old version accepts payloads the current version now validates/sanitizesHigh (chain to the underlying injection class)
Version is reachable but behaviorally identical to currentInformational

发现项严重性
当前版本要求 auth,但旧版本完全绕过 authCritical
当前版本存在 rate-limit,但旧版本缺失Medium–High(可通过
hunt-brute-force
chain)
旧版本相比当前版本泄露了额外字段(PII、内部 ID)Medium–High
当前版本会校验或清理 payload,但旧版本仍然接受High(可 chain 至底层 injection 类型)
版本仍可访问,但行为与当前版本完全相同Informational

Related Skills & Chains

相关 Skills 与 Chains

  • hunt-api-misconfig
    — 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-subdomain
    — owns host/subdomain-level discovery (
    api-v1.target.com
    as its own host, potential takeover). This skill owns what happens once you're inside a given host's version surface.
  • hunt-source-leak
    — JS-bundle grep for internal/undocumented calls; reused here specifically for version-diffing rather than secret extraction.
  • apk-redteam-pipeline
    /
    ios-redteam-pipeline
    — mobile builds routinely hardcode an older API version; every mobile-sourced endpoint is a version-diff candidate.
  • hunt-brute-force
    — a rate-limit regression found here is only a complete finding once chained to actual brute-forceable impact (login, OTP, enumeration).
  • hunt-api-misconfig
    —— 负责在获得规范或 endpoint 后进行漏洞利用(mass assignment、JWT 攻击、OData、Swagger-chain 攻击)。本 skill 会为其提供更明确的目标:“这里有一个 validation 比当前版本更弱的 zombie endpoint。”
  • hunt-subdomain
    —— 负责 host/subdomain 级发现(例如将
    api-v1.target.com
    作为独立 host 进行发现,以及潜在的接管风险)。本 skill 负责进入给定 host 的版本攻击面之后所进行的分析。
  • hunt-source-leak
    —— 使用 JS-bundle grep 查找内部或未文档化调用;本 skill 专门将其复用于版本差异对比,而非 secret extraction。
  • apk-redteam-pipeline
    /
    ios-redteam-pipeline
    —— 移动端构建产物经常硬编码较旧的 API 版本;每个源自移动端的 endpoint 都是版本差异对比的候选项。
  • hunt-brute-force
    —— 此处发现的 rate-limit 回归,只有在 chain 至实际可 brute-force 的影响(登录、OTP、enumeration)后,才构成完整发现。