๐Ÿ›ก OWASP Lab
API9:2023 Medium

Improper Inventory Management

Theory

API9 Improper Inventory Management addresses a problem specific to organisations with many teams, microservices, and long-lived products: you can't secure what you don't know exists. Old API versions, staging environments, shadow endpoints, and forgotten integrations accumulate over time and are never subjected to the same security scrutiny as the current production API.

Why Inventory Fails

  • Old versions not decommissioned โ€” /api/v0/ was replaced by /api/v1/ but the server was never updated to redirect or block v0. It still runs, still returns data, often with fewer security controls because "security was added in v1"
  • Staging in production โ€” a /staging/ prefix or an entire staging server reachable from the internet with real production data and relaxed auth requirements
  • Shadow endpoints โ€” debug routes, test endpoints, and admin utilities added during development and never removed. Not in the OpenAPI spec; only discoverable by fuzzing.
  • Third-party integrations โ€” a vendor added an API integration that you didn't document and no longer use; the endpoint still exists and accepts requests
  • Microservice sprawl โ€” hundreds of internal services with their own APIs; no central registry; security team has no visibility into what's deployed

Finding Shadow Endpoints

# Fuzz for common API paths using wordlists:
ffuf -u https://api.target.com/FUZZ -w common-api-endpoints.txt -mc 200,201,204,302

# Common paths to check:
/api/v0/users
/api/v2/admin
/api/internal/
/api/debug/
/api/health
/api/test/
/swagger.json
/openapi.json
/graphql
/api/v1/debug/config

# Check old API versions:
GET /api/v0/users  โ†’  returns ALL users with plaintext passwords, no auth required
# (This is exactly the challenge on this page)

Vulnerable Code โ€” Old API Version

# Original API v0 โ€” written before security requirements were established
@app.get("/api/v0/users")
def v0_list_users(db = Depends(get_db)):
    # No authentication! Written before the auth middleware was added.
    # "v0 is internal only" โ€” was never truly restricted
    users = db.exec(select(User)).all()
    return users   # returns password_plain, internal_notes, all fields

Fixed Code โ€” Version Management

# Option 1: Return 410 Gone for deprecated API versions
from fastapi import APIRouter

v0_router = APIRouter(prefix="/api/v0")

@v0_router.get("/{path:path}")
def v0_deprecated():
    raise HTTPException(
        status_code=410,
        detail="API v0 is deprecated and decommissioned. Please migrate to /api/v1/"
    )

# Option 2: Redirect old versions to new:
@v0_router.get("/users")
def v0_users_redirect():
    return RedirectResponse(url="/api/v1/users", status_code=301)

# Option 3: Block at the API gateway / ingress level:
# nginx config:
# location /api/v0/ {
#   return 410;
# }

Real-World Breaches

  • Bumble (2020) โ€” A deprecated API version with no authentication allowed querying user profiles by incrementing a user ID. An old endpoint was never removed when the new authenticated version was deployed. 95 million profiles were exposed.
  • Peloton (2021) โ€” An internal API endpoint exposed without authentication returned full user data for any Peloton user; discovered via API fuzzing
  • Parler (2021) โ€” An undocumented API version with no rate limiting and predictable media IDs allowed scraping of all 70 million posts including GPS-tagged images

How to Fix โ€” Checklist

  • Maintain an API inventory โ€” every API version and endpoint should be documented in OpenAPI/Swagger; unapproved endpoints should fail security scanning
  • Decommission old versions โ€” set a sunset date for deprecated API versions; return 410 Gone after the sunset date
  • Fuzz your own APIs โ€” run ffuf or OWASP ZAP active scan against your own API regularly; any 200 from an unknown path is a finding
  • API gateway as the single entry point โ€” route all API traffic through a gateway (Kong, AWS API Gateway, Apigee) that enforces authentication and rate limits at the infrastructure level
  • Environment isolation โ€” staging environments must not be reachable from the internet; use VPN or IP allowlisting
Challenge 1

Old API Version Still Running

The v0 API was replaced by v1, but was never decommissioned. It has fewer security controls and returns the flag.

Hint
GET /api/v0/users โ€” no authentication required on the old version.
Challenge 2

Hidden Debug Endpoint

A debug endpoint was never removed from production. It was discovered via fuzzing common paths.

Hint
Try GET /api/v1/debug/config