๐Ÿ›ก OWASP Lab
API5:2023 High

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/config are 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/users endpoint might be locked down, but DELETE /api/v1/users/{id} on the same route might not check the role
  • Swagger/OpenAPI docs left public โ€” /docs or /openapi.json exposes 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 separate require_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 /docs and /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.