6 min read

API Security
Beginner
API9 API8

API Reconnaissance & Discovery

Before testing, you need to discover and understand the API surface. Look for documentation, endpoints, and understand the data flow. Proper reconnaissance is crucial for identifying shadow APIs and deprecated endpoints.

Authorization boundary

Discovery traffic is still traffic: fuzz only hostnames named in the rules of engagement, and treat every third-party, CDN or acquisition host you uncover as out of scope until the client puts it in writing.

Finding API Documentation

Developers often leave documentation accessible. Check common paths for Swagger/OpenAPI specs or GraphQL playgrounds.

common-paths.txt

  • /api
  • /api/v1
  • /api/docs
  • /api/swagger
  • /api/swagger.json
  • /api/swagger.yaml
  • /api/openapi.json
  • /api/openapi.yaml
  • /swagger-ui.html
  • /swagger/index.html
  • /docs
  • /redoc
  • /graphql
  • /graphiql

Fuzzing for Endpoints

Use tools like ffuf to discover hidden endpoints using wordlists.

Fuzz for API endpoints:

ffuf — endpoint fuzzing
ffuf -u https://target.com/FUZZ -w /usr/share/wordlists/api-endpoints.txt
ffuf -u https://target.com/api/FUZZ -w /usr/share/wordlists/common-api-endpoints.txt

Specialized Discovery

Tools like Kiterunner are designed specifically for API discovery.

Kiterunner scan:

kr scan — kite routes
kr scan https://target.com -w routes-large.kite
kr scan https://target.com -w routes-large.kite -A=apiroutes-210228

Analyzing Documentation

If you find Swagger/OpenAPI files, analyze them to understand the API structure.

Check for exposed Swagger/OpenAPI:

Probing for OpenAPI documents
curl -s https://target.com/swagger.json | jq .
curl -s https://target.com/api-docs | jq .
curl -s https://target.com/openapi.json | jq .

Source Code Analysis

JavaScript files often contain references to API endpoints. Tools like LinkFinder can extract them.

Use LinkFinder:

linkfinder — endpoints in app.js
python3 linkfinder.py -i https://target.com/app.js -o cli

Standard Files

Don't forget to check robots.txt and sitemap.xml.

robots.txt and sitemap.xml
curl https://target.com/robots.txt
curl https://target.com/sitemap.xml

API Endpoint Enumeration

Once you have some endpoints, try to guess others based on common REST patterns.

Common REST API patterns:

REST resource method inventory
GET    /api/v1/users
GET    /api/v1/users/1
POST   /api/v1/users
PUT    /api/v1/users/1
PATCH  /api/v1/users/1
DELETE /api/v1/users/1

Version Enumeration

Check for different API versions or internal/admin paths.

  • /api/v1/users
  • /api/v2/users
  • /api/v3/users
  • /api/beta/users
  • /api/internal/users
  • /api/private/users
  • /api/admin/users

Parameter Discovery

Hidden parameters can often lead to vulnerabilities. Use tools like Arjun or ffuf.

Parameter discovery with Arjun:

arjun — hidden parameters
arjun -u https://target.com/api/users -m GET
arjun -u https://target.com/api/users -m POST

Wordlist-based parameter fuzzing:

ffuf — parameter name fuzzing
ffuf -u "https://target.com/api/users?FUZZ=test" -w params.txt -fs 0

HTTP Method Enumeration

Check which HTTP methods are supported on an endpoint.

Method enumeration loop
for method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE; do
  echo "Testing $method"
  curl -X $method https://target.com/api/endpoint -v 2>&1 | head -20
done

Version drift is the finding

Always check for different API versions. Older versions (v1, v2) may lack security controls that were added in newer versions. Also look for /internal/, /admin/, /debug/ endpoints.

The Inventory Deliverable (API9)

Improper inventory management is not a vulnerability you point at — it is the artefact that proves one. Reconnaissance is finished when you can hand over a row per host and version, with the authentication each one actually requires. Without that table the client cannot tell a retired v1 from a live v1, which is how a deprecated endpoint becomes an unpatched door.

Host / base path Version Auth required Documented? Evidence
api.target.com/api v3 (current) User token; anonymous → 401 Yes — openapi.json 200 with token, 401 without
api.target.com/api v1 (retired per docs) None — anonymous 200 No — absent from spec 200 without any credential
legacy.target.com unknown API key in query string No Kiterunner hit, key visible in JS bundle

Fill the Auth required column by replaying the same request three ways: no credential, a low-privilege user token, and an admin token. A host that answers the first request is the finding; a host that answers the second but not the third is BOLA territory — take it to 03 — Authorization.

Spec vs live: paths the documentation forgot

The documented surface and the reachable surface are two different lists. Compare them literally; both files must be sorted, which is what comm requires. This is POSIX sh, so use temporary files rather than process substitution.

spec-vs-live.sh
# 1. Paths the spec claims, from the OpenAPI document
jq -r '.paths | keys[]' openapi.json | sort > documented.txt

# 2. Paths that answered, one per line from your ffuf/kr output
sort -u found.txt > live.txt

# 3. Live but undocumented — shadow API, the API9 finding
comm -13 documented.txt live.txt

# 4. Documented but dead — stale doc or a blocked route, worth confirming
comm -23 documented.txt live.txt

Run it once per version prefix and once per host: a path that is documented for v3 and undocumented for v1 is exactly the drift this category is about. Field-level diffing — which properties an endpoint returns versus which the schema declares — lives in 04 — Mass Assignment.

Evidence Collection

For each shadow or undocumented endpoint, capture:

Inventory row: Host, base path, version, the exact request line that reached it, and the credential it accepted — filled into the table above.

Spec-vs-live diff: Both sorted path lists and the comm output, plus the revision of the OpenAPI document you diffed against.

Status line and response headers (server banner, X-Powered-By), and the field names of any exposed spec or debug route — redact the values, keep the keys.

False Positive Identification

  • A spec route that refuses you: Swagger UI or a raw openapi.json returning 401/403 to an anonymous request is protected correctly. The finding is a 200 to a caller who should not have the document, not the route existing.
  • Catch-all routing: Wildcard DNS and SPA fallbacks answer every path. Re-send a random path of the same shape: if it returns the same status and body, the hit was the fallback, not an endpoint.

Testing Checklist

Discovery

  • ☐ Fuzz the documented and guessable paths on every host in scope, with a random-path baseline sent alongside so fallbacks are attributable.
  • ☐ Diff spec paths against observed paths once per version prefix and once per host.
  • ☐ Search JS bundles, robots.txt and sitemap.xml for paths the specification omits.

Confirmation

  • ☐ Replay every recorded hit three ways: no credential, a low-privilege token, an admin token.

Remediation & Defense

One record per host and version

  • • Record base path, owning team, auth model, data class, retirement date and deploy source. Generate it from the gateway or ingress config rather than by hand, so it cannot drift.

Restrict the documentation itself

  • • Serve Swagger UI, GraphQL playgrounds and raw specs only to internal networks or authenticated users. A public spec is an enumeration head start, and it still will not list the endpoints that matter.

Retire, do not just hide

  • • Decommission deprecated versions at the edge (410, not pass-through), remove their DNS records and keys, and disable directory listings and verbose errors on anything still reachable.

API Reconnaissance Practice

Practice endpoint discovery and enumeration on deliberately vulnerable API applications.

vAPI — Endpoint Discovery Custom Lab easy
Swagger enumerationffuf fuzzingVersion discovery
crAPI — Shadow API Discovery Custom Lab easy
Kiterunner scanJS endpoint extractionParameter fuzzing