API Discovery
Modern apps are API-first. The web frontend is often just a thin wrapper over the same endpoints the mobile app uses - and those mobile endpoints are frequently less hardened than the web surface. Finding the full API attack surface, including undocumented and legacy endpoints, is where I spend most of my time on modern targets.
Finding API Documentation
Start by looking for self-documenting endpoints. Many teams expose these without realizing it's a recon gift.
# Common Swagger/OpenAPI paths
for path in \
/swagger \
/swagger-ui \
/swagger-ui.html \
/swagger.json \
/swagger.yaml \
/api-docs \
/api-docs/swagger.json \
/openapi \
/openapi.json \
/openapi.yaml \
/v1/swagger \
/v2/swagger \
/v3/swagger \
/api/v1/swagger.json \
/docs \
/redoc \
/api/redoc \
/internal/swagger; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://target.com$path")
[ "$code" != "404" ] && echo "$code $path"
doneIf you find a Swagger or OpenAPI spec, extract every endpoint from it:
# Parse OpenAPI JSON for all paths
curl -s https://target.com/openapi.json | \
python3 -c "
import json, sys
spec = json.load(sys.stdin)
base = spec.get('servers', [{'url': ''}])[0].get('url', '')
for path in spec.get('paths', {}).keys():
print(f'{base}{path}')
"GraphQL
GraphQL endpoints are everywhere now, and introspection - when it works - hands you the entire schema.
# Common GraphQL paths
for path in /graphql /graphiql /api/graphql /v1/graphql /gql /graph; do
curl -s -o /dev/null -w "%{http_code} $path\n" -X POST \
-H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' \
"https://target.com$path"
done | grep -v 404
# Test for introspection
curl -s -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}' | jq .
# If introspection is "disabled" - bypass attempt
# Some implementations block __schema but not __type
curl -s -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __type(name: \"Query\") { fields { name } } }"}' | jq .graphw00f - Fingerprint GraphQL Engines
Different GraphQL engines have different vulnerabilities. Know which one you're dealing with.
python3 graphw00f.py -d -t https://target.com/graphqlInQL - GraphQL Introspection Scanner
python3 -m inql -t https://target.com/graphql
# Generates a full schema dump and individual query files you can throw at BurpFinding APIs Through JS Analysis
The frontend JS almost always contains API endpoint paths - either hardcoded or constructed dynamically.
# Pull all JS files from a target
katana -u https://target.com -jc -silent | grep "\.js$" > jsfiles.txt
# Extract potential API paths from each file
while read jsurl; do
curl -s $jsurl | grep -oE '(\/api\/[a-zA-Z0-9_/.-]+|\/v[0-9]+\/[a-zA-Z0-9_/.-]+)'
done < jsfiles.txt | sort -u > api_paths_from_js.txt
# Or use xnLinkFinder for more complete extraction
python3 xnLinkFinder.py -i https://target.com -sp https://target.com -sf target.com -o links.txtMobile App Traffic as API Source
The mobile app talks to the same backend APIs, often with less input validation and without the same WAF rules applied to the web frontend.
Setup: MitM mobile traffic
# On your machine - run mitmproxy
mitmproxy --mode transparent -p 8080
# Configure your phone to route through your machine, install mitmproxy cert
# Then browse the app normally and watch trafficTools that extract APIs from APKs without running them:
# apkleaks - extract URLs, endpoints, secrets from APK
apkleaks -f target.apk -o apkleaks_output.txt
# androguard + grep
apktool d target.apk -o decompiled/
grep -r "api\|endpoint\|v1\|v2" decompiled/ | grep -v ".class:" | grep "http"Wordlist-Based API Fuzzing
Once you know it's an API, use API-specific wordlists.
# Assetnote's API routes wordlist is the best I've used
ffuf -u https://target.com/api/FUZZ \
-w /opt/wordlists/httparchive_apiroutes_2023.txt \
-mc 200,201,204,301,302,400,401,403,405 \
-fc 404 \
-t 50 \
-H "Content-Type: application/json" \
-o api_fuzz.json -of json
# Also hit versioned paths
for version in v1 v2 v3 v4 api/v1 api/v2; do
ffuf -u "https://target.com/$version/FUZZ" \
-w /opt/wordlists/api/common_api_endpoints.txt \
-mc 200,401,403,405 -fc 404 -t 30 -o "${version//\//_}_fuzz.json" -of json &
done
waitVersion Enumeration
Old API versions often don't get the same security fixes as current ones. Finding /api/v1 when the app uses /api/v3 is worth testing.
for v in v0 v1 v2 v3 v4 v5 v0.1 v1.0 v2.0 beta internal admin; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://target.com/api/$v/users")
[ "$code" != "404" ] && echo "$code /api/$v"
doneAPI Discovery Flow
graph TD A[Target App] --> B[Check for Swagger/OpenAPI docs] A --> C[GraphQL endpoint detection] A --> D[Extract endpoints from JS files] A --> E[Mobile app traffic capture] B --> F[Full endpoint list] C --> F D --> F E --> F F --> G[Wordlist fuzz remaining paths] G --> H[Test each endpoint - auth bypass, IDOR, injection]
Related
- Content Discovery - Swagger/OpenAPI often found through directory fuzzing
- Parameter Discovery - API endpoints have hidden parameters too
- JavaScript Analysis - deep dive on extracting paths from JS