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
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 -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.txtSpecialized Discovery
Tools like Kiterunner are designed specifically for API discovery.
Kiterunner scan:
kr scan https://target.com -w routes-large.kite
kr scan https://target.com -w routes-large.kite -A=apiroutes-210228Analyzing Documentation
If you find Swagger/OpenAPI files, analyze them to understand the API structure.
Check for exposed Swagger/OpenAPI:
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:
python3 linkfinder.py -i https://target.com/app.js -o cliStandard Files
Don't forget to check robots.txt and sitemap.xml.
curl https://target.com/robots.txt
curl https://target.com/sitemap.xmlAPI Endpoint Enumeration
Once you have some endpoints, try to guess others based on common REST patterns.
Common REST API patterns:
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/1Version 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 -u https://target.com/api/users -m GET
arjun -u https://target.com/api/users -m POSTWordlist-based parameter fuzzing:
ffuf -u "https://target.com/api/users?FUZZ=test" -w params.txt -fs 0HTTP Method Enumeration
Check which HTTP methods are supported on an endpoint.
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
doneVersion drift is the finding
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.
# 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.txtRun 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.