Back

API09: Improper Inventory Management - Attack Vectors

Understanding the Attack Surface

⚠ EDUCATIONAL PURPOSE ONLY. These techniques are described so defenders can find and close gaps before attackers do. Only test systems you are explicitly authorized to test.

Attacking improper inventory management is fundamentally an exercise in discovery. The attacker's goal is to find the asset the defender forgot: an old version, a stale host, a debug route, or a partner endpoint that never made it into anyone's threat model. Because these assets are unmonitored by definition, exploitation is often quiet and can persist for a long time.

The attacker rarely needs a novel exploit. They need the weakest reachable copy of your functionality — and inventory gaps guarantee that a weaker copy usually exists.

Core Attack Flow

1. Map the surface
   |
   Enumerate subdomains, hosts, versions, and routes
   v
2. Find the weak variant
   |
   Compare auth/rate-limit behavior across versions & hosts
   v
3. Exploit the gap
   |
   Use the version/endpoint with missing or weaker controls
   v
4. Persist quietly
   |
   Operate through an unmonitored path to avoid detection

Attack Patterns

1. Enumerating Deprecated API Versions

The attacker takes a known current endpoint and simply decrements the version, looking for an older variant that still answers — often with weaker or absent authorization.

GET /api/v3/users/1001   -> 401 Unauthorized  (token + scope required)
GET /api/v2/users/1001   -> 401 Unauthorized
GET /api/v1/users/1001   -> 200 OK            (legacy: returns full record!)

The /v1 handler predates the current authorization model, so it happily returns data the newer versions protect. This is the single most common inventory-management attack.

2. Subdomain Enumeration to Find Shadow Hosts

Old staging, dev, and demo hosts frequently remain in DNS and stay internet-reachable. Certificate-transparency logs and brute-force resolvers reveal them quickly.

# Discover hosts from certificate transparency
curl -s "https://crt.sh/?q=%25.example.com&output=json" | jq -r '.[].name_value' | sort -u

# Result includes forgotten hosts:
#   dev-api.example.com
#   staging.example.com
#   api-old.example.com
#   internal-tools.example.com

GET https://dev-api.example.com/api/users   -> 200 OK  (debug mode, real data)

3. Non-Production Environments with Production Data

Staging often mirrors production data but ships with verbose errors, default credentials, or disabled security features.

GET https://staging.example.com/api/v3/orders

HTTP/1.1 500 Internal Server Error
{
  "error": "psycopg2.OperationalError",
  "stack": "at db.connect(host=staging-db.internal:5432, user=admin, password=changeme) ...",
  "debug": true
}

The leaked stack trace exposes internal hostnames and credentials that pivot the attacker deeper.

4. Missing Authentication on Old Versions

When authentication is added in a later version, the older one may never have required it at all.

# Current version enforces a bearer token
GET /api/v2/account/balance
Authorization: Bearer eyJ...     -> 200 OK

# Legacy version was written before auth existed
GET /api/v1/account/balance      -> 200 OK   (no Authorization header needed)

5. Undocumented Debug & Admin Endpoints

Diagnostic and administrative routes left in production are discovered by fuzzing common paths.

GET /api/_debug              -> 200 OK   (dumps environment variables)
GET /api/admin/users         -> 200 OK   (no auth, lists all users)
GET /internal/backup          -> 200 OK   (downloads a database dump)
GET /test/reset-db            -> 200 OK   (destructive dev utility, still live)

6. Framework Diagnostic Endpoints (Actuator, Docs, Introspection)

Framework defaults expose powerful endpoints if not explicitly disabled.

# Spring Boot Actuator left open
GET /actuator/env            -> configuration incl. secrets
GET /actuator/heapdump       -> full memory dump (tokens, sessions)
GET /actuator/mappings       -> a map of every route in the app

# Interactive docs / schema in production
GET /openapi.json            -> full machine-readable API spec
GET /swagger-ui/index.html   -> live, clickable API explorer
POST /graphql {"query":"{__schema{types{name}}}"}  -> introspection

These not only expose data directly, they hand the attacker a complete map of the rest of the API.

7. Interactive API Docs as a Discovery Oracle

A leaked OpenAPI/Swagger document is a gift: it enumerates every path, parameter, and expected payload, turning blind fuzzing into targeted requests.

# Pull the spec, then list every path it declares
curl -s https://api.example.com/openapi.json | jq -r '.paths | keys[]'

/api/v3/users
/api/v3/users/{id}
/api/v1/legacy/export     <-- undocumented elsewhere, but present in the spec
/api/internal/impersonate <-- an admin route the spec forgot to hide

8. Differing Security Controls Across Versions

Even when every version requires auth, the quality of the controls can differ. Attackers probe for the version with the weakest rate limiting, weakest object-level checks, or most generous scopes.

# v3 rate-limits login to 5/min; v1 never got rate limiting
POST /api/v1/login   (unlimited attempts -> credential stuffing / brute force)

# v3 checks object ownership; v1 trusts the id parameter (BOLA revived)
GET /api/v1/invoices/99999   -> 200 OK  (another tenant's invoice)

9. Retired-But-Reachable Services

A service "turned off" in the product may still be running on its original host or behind a load balancer rule nobody removed.

GET https://legacy-payments.example.com/api/charge   -> 200 OK

# The team migrated to a new processor months ago, but the old
# service still accepts requests and still holds live API keys.

10. Mobile / Client-Specific Shadow APIs

Mobile and single-page apps often talk to endpoints that the web API's protections never cover. Inspecting app traffic reveals them.

# Intercept mobile traffic, observe a private base URL:
POST https://mobile-api.example.com/v2/profile/update

# The mobile endpoint skips the WAF and the gateway's rate limits
# that guard api.example.com, and trusts a weaker client secret.

11. Parameter- and Header-Based Version Selection

Not all versioning lives in the path. Header- or query-based version routing can silently expose old code paths.

# Same path, older behavior selected by a header
GET /api/users/1001
Accept: application/vnd.example.v1+json   -> legacy handler, weaker checks

# Or via query string
GET /api/users/1001?api-version=1.0        -> routes to deprecated logic

12. Third-Party Data-Flow Blind Spots

Functionality exposed to (or by) a partner is frequently outside the data owner's monitoring. An attacker who cannot breach the front door probes the integration instead.

# A partner-facing endpoint returns sensitive data with weak identifiers
POST https://partner-api.example.com/lookup
{ "name": "Jane Doe", "dob": "1990-01-01", "zip": "10001" }

-> 200 OK  { "credit_score": 742, "ssn_last4": "1234" }

13. Stale Documentation / Changelogs Leaking Removed Routes

Old developer portals, cached docs, wikis, and changelogs describe endpoints that were "removed" but not actually taken offline.

# A cached changelog mentions:
#   "Deprecated /api/v1/export-all in favor of /api/v3/export"
# The attacker tries the old path directly:
GET /api/v1/export-all   -> 200 OK  (still exports everything)

14. JavaScript Bundle & Source-Map Mining

Front-end bundles and leaked source maps reveal API base URLs, internal endpoints, and feature-flagged routes never meant for production exposure.

# Extract endpoint strings from a production bundle
curl -s https://example.com/static/app.min.js | grep -oE '/api/[a-zA-Z0-9/_-]+'

/api/v3/users
/api/beta/experimental-search   <-- feature-flagged, but reachable
/api/internal/feature-flags     <-- internal config endpoint

Chaining Inventory Gaps

Individually, each gap is a foothold. Chained together, they become a full compromise:

1. crt.sh reveals dev-api.example.com  (stale host)
        v
2. /openapi.json on that host leaks every route  (docs disclosure)
        v
3. /api/v1/login has no rate limit  (control drift) -> credential stuffing
        v
4. /actuator/env exposes a database password  (framework default)
        v
5. Attacker pivots to internal systems using leaked credentials

Why These Attacks Succeed and Persist

Key Takeaways

  1. Version enumeration is the flagship attack — decrement the version and look for the copy that still answers.
  2. Discovery, not exploitation, is the hard part for defenders — attackers automate it trivially.
  3. Documentation and framework defaults are discovery oracles — an exposed spec maps your whole API.
  4. Shadow, mobile, and partner APIs bypass central defenses by design.
  5. Non-production hosts are production targets when reachable.
  6. Gaps chain — one stale host can unravel an entire environment.

Next Steps