flawopen.com/Vulnerabilities/Broken Object Authorization (BOLA/IDOR)

Broken Object Level Authorization (BOLA / IDOR)

Critical API Flaw CWE-639 Modern Web & Cloud
ELI5 — The Hotel Room Key with Number Tipex

Imagine checking into Room 204 at a hotel. You receive a keycard. But when you walk down the hall, you notice that if you scratch out '204' on the door handle sensor and write '205', the door opens and lets you rifle through another guest's luggage. The hotel checked that you were a registered guest (authentication), but forgot to verify whether your specific key belongs to Room 205 (object authorization). In BOLA/IDOR, an attacker simply increments an ID in an API request and accesses other users' private accounts.

Target: REST APIs, GraphQL mutations, microservices object access
Vector: Incrementing numerical IDs (/users/1042) or guessing UUIDs
Impact: Mass data exfiltration, unauthorized account modification, PII breaches
Remediation: Enforcing tenancy where-clauses in queries, policy authorization middleware

The Mechanism & Root Cause

Developers often assume that because a user is authenticated via JWT or session, they are permitted to request any resource ID passed in the request URL. Without an explicit check verifying that record.user_id == current_user.id, any registered user can view or delete records belonging to any other user.

invoice_controller.py (Vulnerable)Vulnerable
# VULNERABLE: Checks if user is logged in, but not if they OWN the invoice
@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, current_user: User = Depends(get_current_user)):
    # Attacker logged in as User A requests invoice_id=999 belonging to User B
    invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")
    return invoice  # Leaks another company's financial billing data!
invoice_controller.py (Hardened)Hardened
# HARDENED: Bind the database query directly to the authenticated tenant scope
@app.get("/api/invoices/{invoice_id}")
def get_invoice_safe(invoice_id: int, current_user: User = Depends(get_current_user)):
    # Query strictly filters by BOTH invoice ID and the authenticated user's organization
    invoice = db.query(Invoice).filter(
        Invoice.id == invoice_id,
        Invoice.organization_id == current_user.org_id
    ).first()
    
    if not invoice:
        # Return 404 to avoid leaking object existence to unauthorized callers
        raise HTTPException(status_code=404, detail="Invoice not found")
    return invoice

The Attack & Exploit Sequence

Defensive Engineering & Prevention Rules

Explore related security topics and post-mortems: Complete Security Directory →