Broken Function Level Authorization
Theory
API5 Broken Function Level Authorization is the API-layer equivalent of missing function-level access control in web applications. The difference from BOLA (API1) is the level: BOLA is about which objects you can access; BFLA is about which actions/endpoints you can call. An admin-only function that any authenticated user can invoke is API5.
Why APIs Are Especially Prone to This
- RESTful naming reveals intent โ endpoints like
/api/v1/admin/stats,/api/v1/users/{id}/delete, or/api/v1/internal/configare guessable by convention even if not documented - Auth check โ role check โ developers add a token requirement (
Depends(get_current_user)) and consider the endpoint secured. The token proves identity but not privilege level. - HTTP method confusion โ a
GET /api/v1/usersendpoint might be locked down, butDELETE /api/v1/users/{id}on the same route might not check the role - Swagger/OpenAPI docs left public โ
/docsor/openapi.jsonexposes all routes including admin ones; attackers use this as a roadmap
Vulnerable Code
# VULNERABLE โ checks for a valid token but not for admin role
@app.get("/api/v1/admin/stats")
def admin_stats(user = Depends(get_current_user)):
# get_current_user only verifies the token is valid and not expired
# It does NOT check whether user.role == "admin"
return {
"total_users": db.count(User),
"total_revenue": db.sum(Order.total_price),
"admin_notes": "Internal P&L data",
}
Attack โ Regular User Calls Admin Endpoint
# Step 1: Authenticate as alice (regular user)
POST /api/v1/auth/login
{"username": "alice", "password": "alice123"}
# Response: {"token": "eyJ..."}
# Step 2: Discover admin endpoints by fuzzing or reading OpenAPI docs
GET /docs โ reveals GET /api/v1/admin/stats exists
# Step 3: Call the admin endpoint with alice's regular-user token
GET /api/v1/admin/stats
Authorization: Bearer eyJ...
# Response: 200 OK โ full admin data returned
# The server accepted alice's token; it only checked "is this a valid token?"
# It never checked "does this token belong to an admin?"
Fixed Code
# FIXED โ dedicated dependency that checks the role
def require_role(*roles: str):
def _check(user = Depends(get_current_user)):
if user.role not in roles:
raise HTTPException(
status_code=403,
detail=f"Requires role: {', '.join(roles)}"
)
return user
return _check
# Usage: only users with role "admin" or "superadmin" can call this
@app.get("/api/v1/admin/stats")
def admin_stats(admin = Depends(require_role("admin", "superadmin"))):
return {
"total_users": db.count(User),
"total_revenue": db.sum(Order.total_price),
}
# For write operations, also validate HTTP method at the middleware level:
@app.delete("/api/v1/users/{user_id}")
def delete_user(user_id: int, admin = Depends(require_role("admin"))):
...
Real-World Examples
- HackerOne โ Multiple programs โ Admin API endpoints (export all users, delete accounts, read internal analytics) accessible to regular authenticated users; consistently rated High/Critical. Some paid $5Kโ$25K.
- Verizon (2021) โ Regular authenticated API users could call administrative endpoints that triggered account changes on any customer account
- GitLab (2021) โ BFLA allowed regular members to trigger CI/CD pipelines in projects they had no write access to; code execution as the pipeline user
How to Fix โ Checklist
- Separate auth from authz โ
get_current_user()proves identity; a separaterequire_role()dependency enforces privilege - Apply role checks at the route level โ not inside the function body (easy to forget); use framework dependency injection
- Restrict OpenAPI docs in production โ require admin auth to access
/docsand/openapi.json, or disable them entirely - Fuzz your own API โ run a low-privilege token against every endpoint in your OpenAPI spec; any 200 from an admin route is a finding
- HTTP method allow-listing โ explicitly declare which HTTP methods are allowed per route; return 405 for undeclared methods
Challenge 1
Broken Function Level Auth โ Access Admin Stats
Log in as alice (regular user) and call the admin stats endpoint. The endpoint checks for a valid token but not for admin role.
Hint
GET /api/v1/admin/stats with any valid Bearer token.