flawopen.com/bola-idor/Python

● CWE-639 · Kritisch
Sicherheitsforschung · FlawOpen

Fehlerhafte Autorisierung auf Objektebene (BOLA / IDOR) in Python

Verhindern Sie BOLA (CWE-639) und IDOR in Python und FastAPI, indem Datenbankabfragen zwingend an die Mandantenidentität des authentifizierten Benutzers gekoppelt werden.

💡 Einfache Erklärung (ELI5)

Ein Hotelschlüssel für Zimmer 204 öffnet auch Zimmer 205, wenn man einfach die Zahl austauscht. Das Hotel hat den Gast authentifiziert, aber die Berechtigung für dieses konkrete Zimmer nicht geprüft.

Kernkonzepte & Begriffe

Authentication vs. Authorization
Authentifizierung bestätigt, WER der Benutzer ist (gültiger Login). Autorisierung bestätigt, WELCHE Ressourcen dieser Benutzer einsehen oder ändern darf.
BOLA (Broken Object Level Authorization)
Die häufigste OWASP-API-Schwachstelle, bei der ein Endpunkt eine Objektreferenz (z. B. /api/invoices/1042) offenlegt, ohne die Mandantenberechtigung zu überprüfen.
IDOR (Insecure Direct Object Reference)
Die übergeordnete Schwachstellenklasse (CWE-639), bei der interne Implementierungsobjekte ohne Zugriffssteuerungsprüfungen direkt Benutzern offengelegt werden.
Tenancy Scope Injection
Ein Architekturmuster, bei dem Datenbankabfragen auf der ORM-Ebene automatisch WHERE organization_id = :auth_user_org_id erzwingen.
Non-Enumerable Identifiers (UUIDv4)
Verwendung kryptografisch zufälliger 128-Bit-Bezeichner anstelle sequentieller automatischer Inkremente, um vorhersehbare Aufzählungen zu verhindern.

Schritt-für-Schritt Angriffsablauf

Step 1

Legitimate Sign-In

An attacker creates a legitimate account on the platform and receives a valid JWT authentication bearer token.

Step 2

Resource Inspection

The attacker accesses their own billing invoice via GET /api/invoices/1042 and observes sequential database identifiers in use.

Step 3

Identifier Perturbation

The attacker modifies the URL to request GET /api/invoices/1041 using their own valid authentication token.

Step 4

Flawed Controller Logic

The FastAPI backend confirms the token is valid, but queries the database solely by Invoice.id == 1041 without verifying tenant ownership.

Step 5

Cross-Tenant Exfiltration

The server returns confidential invoice records, billing details, and personal data belonging to another organization.

Step 6

Automated Harvest

The attacker scripts a loop iterating over IDs 1 through 100,000, draining the entire multi-tenant database.

Quellcode: Verwundbar vs. Sicher

✕ VERWUNDBARE IMPLEMENTIERUNG
# VULNERABLE: Verifies user login, but queries object solely by client ID
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session

@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    # Flaw: Attacker passes invoice_id belonging to another tenant
    invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")
        
    # Leaks confidential billing records of competitor organizations!
    return invoice
✓ GEHÄRTETER SICHERHEITS-PATCH
# HARDENED: Query is strictly bound to the authenticated tenant's organization ID
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session

@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    # Defense-in-depth: query filters by BOTH invoice_id AND authenticated organization_id
    invoice = db.query(Invoice).filter(
        Invoice.id == invoice_id,
        Invoice.organization_id == current_user.organization_id
    ).first()
    
    if not invoice:
        # Return 404 rather than 403 to prevent object existence enumeration
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Invoice not found")
        
    return invoice

Checkliste für Engineering & Systemsicherheit

References