Showing Posts From

Authorization

API Security Blueprint: Defending Microservices Against BOLA and BFLA Attacks

API Security Blueprint: Defending Microservices Against BOLA and BFLA Attacks

APIs are the connective tissue of modern software. They are also the primary attack surface. The OWASP API Security Top 10 places Broken Object Level Authorization (BOLA) and Broken Function Level Authorization (BFLA) at positions #1 and #5 respectively — and for good reason. These vulnerabilities are endemic, trivially exploitable, and catastrophically impactful. In microservices architectures, where dozens of APIs communicate internally and externally, a single misconfigured authorization layer creates a blast radius that spans entire domains of sensitive data.Understanding BOLA (Broken Object Level Authorization) BOLA occurs when an API exposes object identifiers (IDs) in requests but fails to verify that the authenticated user has permission to access that specific object. Vulnerable pattern: GET /api/v1/invoices/84729 Authorization: Bearer <user_A_token>If the server returns the invoice regardless of whether user_A owns invoice 84729, it is vulnerable to BOLA. An attacker simply iterates IDs: import requestsheaders = {"Authorization": f"Bearer {attacker_token}"} for invoice_id in range(80000, 90000): r = requests.get(f"https://api.example.com/api/v1/invoices/{invoice_id}", headers=headers) if r.status_code == 200: exfiltrate(r.json()) # Every 200 is someone else's dataThis is not a theoretical attack. BOLA was the root cause of the Optus (11M customer records), T-Mobile (2021), and dozens of fintech API breaches.Implementing Robust Object-Level Authorization Option 1: Database-level ownership checks (mandatory baseline) Never trust the client-supplied ID alone. Always enforce ownership in your query: # VULNERABLE def get_invoice(invoice_id: int, current_user: User): return db.query(Invoice).filter(Invoice.id == invoice_id).first()# SECURE — ownership enforced at query level def get_invoice(invoice_id: int, current_user: User): invoice = ( db.query(Invoice) .filter(Invoice.id == invoice_id, Invoice.owner_id == current_user.id) .first() ) if not invoice: raise HTTPException(status_code=404) # Always 404, never 403 — prevents enumeration return invoiceOption 2: Non-sequential, non-guessable identifiers Replace auto-increment integer IDs with UUIDs or ULIDs. This doesn't fix authorization logic, but it eliminates trivial enumeration: import ulidclass Invoice(Base): id: str = Column(String, primary_key=True, default=lambda: str(ulid.new()))Option 3: Relationship-scoped queries In multi-tenant systems, scope all queries to the tenant context extracted from the JWT: def get_invoices(current_user: User, db: Session): return ( db.query(Invoice) .join(Organization) .filter( Organization.id == current_user.organization_id, Invoice.organization_id == current_user.organization_id, ) .all() )Understanding BFLA (Broken Function Level Authorization) BFLA occurs when an API exposes administrative or privileged functions to users who should not have access — typically because authorization checks on function-level (not just data-level) are missing or incorrect. Attack scenario: # Regular user discovers admin endpoints via API spec exposure GET /api/v1/admin/users → 403 Forbidden (good) DELETE /api/v1/users/54321 → 200 OK (should be 403 for non-admins!) POST /api/v1/users/54321/promote → 200 OK (privilege escalation!)Common root causes:Frontend hides admin buttons, but backend doesn't enforce role checks API versioning gaps — /api/v2/ has checks, /api/v1/ endpoints were forgotten HTTP method confusion — GET /invoice/123 is authorized, DELETE /invoice/123 is not checked separatelyImplementing Function-Level Authorization Role-Based Access Control (RBAC) decorator pattern: from functools import wraps from enum import Enumclass Role(Enum): USER = "user" MANAGER = "manager" ADMIN = "admin"ROLE_HIERARCHY = {Role.USER: 0, Role.MANAGER: 1, Role.ADMIN: 2}def require_role(minimum_role: Role): def decorator(func): @wraps(func) async def wrapper(*args, current_user: User, **kwargs): if ROLE_HIERARCHY[current_user.role] < ROLE_HIERARCHY[minimum_role]: raise HTTPException( status_code=403, detail="Insufficient privileges" ) return await func(*args, current_user=current_user, **kwargs) return wrapper return decorator# Usage @router.delete("/users/{user_id}") @require_role(Role.ADMIN) async def delete_user(user_id: str, current_user: User = Depends(get_current_user)): ...@router.post("/users/{user_id}/promote") @require_role(Role.ADMIN) async def promote_user(user_id: str, current_user: User = Depends(get_current_user)): ...Attribute-Based Access Control (ABAC) for fine-grained policies: For complex enterprise systems, RBAC alone is insufficient. Use OPA as a centralized policy engine: # opa/policies/api_authz.rego package api.authzdefault allow = false# Users can only read their own resources allow { input.method == "GET" input.path == ["api", "v1", "invoices", invoice_id] input.user.id == data.invoices[invoice_id].owner_id }# Managers can read any invoice within their organization allow { input.method == "GET" input.path == ["api", "v1", "invoices", _] input.user.role == "manager" input.user.org_id == data.invoices[invoice_id].org_id }# Only admins can delete allow { input.method == "DELETE" input.user.role == "admin" }API Gateway-Level Enforcement Authorization logic must not live only in individual microservices — that creates drift and gaps. Enforce coarse-grained policies at the API gateway layer (Kong, AWS API Gateway, Apigee): # Kong declarative config — rate limit and JWT validation plugins: - name: jwt config: key_claim_name: kid claims_to_verify: - exp - iss - name: rate-limiting config: minute: 60 hour: 1000 policy: redis - name: opa config: server: http://opa-service:8181 policy: api/authz/allowDetection & Anomaly Monitoring Authorization vulnerabilities that reach production require behavioral detection: # Alert on BOLA enumeration patterns rule = { "name": "API Object Enumeration Detected", "condition": ( "same_user_id AND sequential_resource_ids AND " "request_count_1min > 50 AND " "response_mix_200_404" ), "action": ["block_user", "alert_security_team", "create_incident"], }Key signals to monitor:Same user accessing sequential or high-volume distinct object IDs in short windows 403/404 response volume spikes from a single source Unexpected HTTP method usage on resource endpoints (DELETE, PATCH from user-tier tokens)API Security Checklist Object-level authorization enforced at query layer — never trust client-supplied IDs alone Non-sequential identifiers (UUID/ULID) on all externally-exposed resources Function-level authorization enforced server-side via RBAC/ABAC on every endpoint HTTP method authorization verified — GET auth ≠ DELETE auth API spec not publicly exposed (redact or gate OpenAPI/Swagger in production) All API versions (v1, v2) have equivalent authorization coverage API gateway enforcing JWT validation, rate limiting, and OPA policy evaluation Behavioral anomaly detection alerting on enumeration patterns Regular BOLA/BFLA-targeted penetration tests (quarterly minimum)GurkhaX conducts API security audits, penetration testing, and authorization architecture reviews for microservices platforms. Request an API security assessment from our offensive security team.

Read IntelPOST #01