{"id":"hunt-shadow-api","name":"hunt-shadow-api","summary":"Hunt shadow / zombie / undocumented API surface(OWASP API9 Inproper Inventory Management)— APIの全バージョン履歴(v1/v2/beta/レガシーパス、ヘッダーおよびサブドメインベースのバージョン管理)を列挙し、アクセス可能な…","body":"## OWASP API9 — Improper Inventory Management (Shadow / Zombie APIs)\n\nAs an API evolves, old versions and internal/staging routes routinely stay reachable without\nreceiving the same security fixes as the current version — because nobody tracks that they\nstill exist. The bug is rarely in one endpoint; it's in the **delta** between what an old\nversion enforces and what the current version enforces on the same operation.\n\n### When to use\n\nTrigger when:\n- Versioned paths are visible (`/v1/`, `/v2/`, `/api/2023-01-01/`) or `Accept`/`X-API-Version`\n  headers are in play.\n- A changelog, release notes, or deprecation notice references removed/old API behavior.\n- A mobile APK/IPA (via `apk-redteam-pipeline` / `ios-redteam-pipeline`) hardcodes endpoints\n  that look like an older backend version than the current web app calls.\n- Multiple OpenAPI/Swagger specs are discoverable, or `info.version` in one spec implies others\n  exist.\n\nDO NOT use for single-version APIs with no version history — there's nothing to diff; go\nstraight to `hunt-api-misconfig` for direct exploitation of the one surface that exists.\n\n---\n\n## Stage 1 — Enumerate the Full Version Surface\n\n```bash\n# Path-based versioning\nfor v in v1 v2 v3 v4 beta alpha internal legacy old 2022-01-01 2023-01-01 2024-01-01; do\n  curl -s -o /dev/null -w \"%{http_code} /api/$v/\\n\" \"https://$TARGET/api/$v/\"\ndone\n\n# Header-based versioning\ncurl -s -H \"X-API-Version: 1\" https://$TARGET/api/users\ncurl -s -H \"Accept: application/vnd.company.v1+json\" https://$TARGET/api/users\n\n# Subdomain-based versioning\nfor sub in api api-v1 api-v2 apiv1 apiv2 legacy-api old-api internal-api staging-api; do\n  curl -s -o /dev/null -w \"%{http_code} $sub\\n\" \"https://$sub.$TARGET/\"\ndone\n```\n\nA `200`/`401`/`403` on an old version path (anything but `404`/connection-refused) means the\nversion is still live and worth carrying into Stage 3, even if it demands auth.\n\n---\n\n## Stage 2 — Pull Every Reachable Spec, Not Just the Linked One\n\n```bash\nfor path in openapi.json swagger.json v1/swagger.json v2/swagger.json v3/api-docs \\\n            api-docs.json swagger/v1/swagger.json .well-known/openapi.json; do\n  curl -s -o /dev/null -w \"%{http_code} /$path\\n\" \"https://$TARGET/$path\"\ndone\n\n# Wayback Machine — a DEPRECATED version's spec often stays indexed after the live link is removed\ncurl -s \"http://web.archive.org/cdx/search/cdx?url=$TARGET/*swagger*&output=json&collapse=urlkey\"\ncurl -s \"http://web.archive.org/cdx/search/cdx?url=$TARGET/*openapi*&output=json&collapse=urlkey\"\n```\n\nWhen more than one spec resolves (a current one and an archived/old one), diff the endpoint\ninventories directly:\n```bash\njq -r '.paths | keys[]' v1-swagger.json | sort > /tmp/v1_paths.txt\njq -r '.paths | keys[]' v2-swagger.json | sort > /tmp/v2_paths.txt\ncomm -23 /tmp/v1_paths.txt /tmp/v2_paths.txt   # in v1 only — candidates for \"still live but forgotten\"\n```\nFor every path in that diff, confirm it's still reachable against the v1 base URL. A route\ndocumented only in the old spec that still returns something other than `404` is a zombie-\nendpoint candidate — carry it into Stage 3.\n\n---\n\n## Stage 3 — Behavioral Diff Between Old and Current Version\n\nFor each operation that exists in **both** versions, compare security-relevant behavior, not\nresponse shape. Response shape differences are Informational; behavioral security regressions\nare the finding.\n\n- **Auth strength.** Does the old version accept no token, an expired token, or a lower-\n  privilege token that the current version rejects?\n  ```bash\n  curl -s -H \"Authorization: Bearer $EXPIRED_TOKEN\" https://$TARGET/api/v1/users/me -w '\\n%{http_code}\\n'\n  curl -s -H \"Authorization: Bearer $EXPIRED_TOKEN\" https://$TARGET/api/v2/users/me -w '\\n%{http_code}\\n'\n  ```\n- **Rate limiting.** Burst the same number of requests against both versions' equivalent\n  endpoint; a missing `429` on the old version means rate-limiting was added later and never\n  backported.\n- **Input validation.** Send the identical injection/oversized/malformed payload to both; the\n  old version accepting what the new one rejects means hardening happened forward-only —\n  chain into whichever injection class the payload targets (`hunt-sqli`, `hunt-idor`, etc.).\n- **Field exposure.** Does the old version's response body include fields — internal IDs, other\n  users' data, internal notes, PII — that the current version has since redacted?\n\n---\n\n## Stage 4 — Deprecated / Internal Routes Never Referenced by the Current UI\n\n- Grep JS bundles for API calls no visible UI flow triggers (`/internal/`, `/admin/`, `/debug/`,\n  `/_internal/`, `/test/`, `/staging/`) — reuse `hunt-source-leak`'s JS-bundle grep patterns for\n  this specifically.\n- Check `robots.txt` / `sitemap.xml` for disallowed API paths — a self-inflicted disclosure.\n- Mobile-app endpoint inventories (via `apk-redteam-pipeline` / `ios-redteam-pipeline`) very\n  often reference an older backend version than the current web app calls. Treat every\n  APK/IPA-sourced endpoint as a version-diff candidate against the live web API.\n\n---\n\n## False-Positive Gate\n\n- A version difference alone (different response shape, cosmetic field renaming) is\n  Informational. The finding is a **security-relevant regression** — auth, rate-limit, or\n  validation that got weaker going backward in version history.\n- Confirm the old endpoint is not simply an alias/proxy to the current implementation before\n  claiming a behavioral difference — send a payload that would actually behave differently\n  under old vs. new logic, not just compare a version string in the response body.\n- A `200` on a path that just serves a static \"this API version is deprecated, use v2\" message\n  is not a finding — confirm the underlying operation still executes.\n\n---\n\n## Severity Table\n\n| Finding | Severity |\n|---|---|\n| Old version bypasses auth entirely where current version requires it | Critical |\n| Old version missing rate-limit present on current version | Medium–High (chain via `hunt-brute-force`) |\n| Old version leaks extra fields (PII, internal IDs) vs. current | Medium–High |\n| Old version accepts payloads the current version now validates/sanitizes | High (chain to the underlying injection class) |\n| Version is reachable but behaviorally identical to current | Informational |\n\n---\n\n## Related Skills & Chains\n\n- **`hunt-api-misconfig`** — owns exploitation once a spec or endpoint is in hand (mass\n  assignment, JWT attacks, OData, Swagger-chain attacks). This skill hands it a sharper target:\n  \"here's a zombie endpoint with weaker validation than the current one.\"\n- **`hunt-subdomain`** — owns host/subdomain-level discovery (`api-v1.target.com` as its own\n  host, potential takeover). This skill owns what happens once you're inside a given host's\n  version surface.\n- **`hunt-source-leak`** — JS-bundle grep for internal/undocumented calls; reused here\n  specifically for version-diffing rather than secret extraction.\n- **`apk-redteam-pipeline`** / **`ios-redteam-pipeline`** — mobile builds routinely hardcode an\n  older API version; every mobile-sourced endpoint is a version-diff candidate.\n- **`hunt-brute-force`** — a rate-limit regression found here is only a complete finding once\n  chained to actual brute-forceable impact (login, OTP, enumeration).","author":"@elementalsouls","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-shadow-api","license":"MIT","category":"coding","lang":"en","tokens":1827,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["web.archive.org"]}}