flawopen.com/bola-idor/Python

● CWE-639 · Crítica
Investigación · FlawOpen

Autorización Rota a Nivel de Objeto (BOLA / IDOR) en Python

Aprenda a prevenir vulnerabilidades BOLA (CWE-639) e IDOR en FastAPI y Python enlazando las consultas a la base de datos con la identidad y organización del usuario autenticado.

💡 Explicación en Lenguaje Sencillo (ELI5)

Como una llave de hotel para la habitación 204 que también abre la 205 si cambias el número en la manija. El hotel autenticó al huésped pero no autorizó el acceso a ese objeto específico.

Conceptos Clave y Términos

Authentication vs. Authorization
La autenticación confirma QUIÉN es el usuario (inicio de sesión válido). La autorización confirma QUÉ recursos tiene permitido ver o modificar ese usuario específico.
BOLA (Broken Object Level Authorization)
La vulnerabilidad nº 1 de OWASP API donde un endpoint expone una referencia de objeto (p. ej., /api/invoices/1042) sin verificar la propiedad del inquilino (BOLA).
IDOR (Insecure Direct Object Reference)
La clase de vulnerabilidad más amplia (CWE-639) donde los objetos de implementación interna se exponen directamente a los usuarios sin comprobaciones de control de acceso.
Tenancy Scope Injection
Patrón arquitectónico en el que las consultas a la base de datos imponen automáticamente WHERE organization_id = :auth_user_org_id en la capa ORM.
Non-Enumerable Identifiers (UUIDv4)
Uso de identificadores criptográficamente aleatorios de 128 bits en lugar de enteros autoincrementales secuenciales para evitar la enumeración predecible.

Flujo de Ataque Paso a Paso

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.

Código Fuente: Vulnerable vs. Seguro

✕ IMPLEMENTACIÓN VULNERABLE
# 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
✓ PARCHE SEGURO Y ROBUSTO
# 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

Lista de Verificación de Seguridad para Ingeniería

References