← Lecciones

TEMA 01 · 20 MIN

IDOR: cuando cambiar un número de la URL te da los datos de otro

Tu API comprueba que el usuario ha iniciado sesión. Pero ¿comprueba que lo que pide es suyo?

Un IDOR (Insecure Direct Object Reference, referencia directa insegura a objetos) aparece cuando la API recibe un identificador (/api/invoices/1042) y devuelve el recurso sin comprobar que pertenece a quien lo pide. Basta con cambiar el número de la URL para leer datos de otra persona.

Es uno de los fallos más comunes en APIs reales: encabeza el OWASP API Security Top 10 como Broken Object Level Authorization. Y es facilísimo de introducir en un route handler de Next.js.

Autenticación no es autorización

  • Autenticación: ¿quién eres? (getSession devuelve tu usuario).
  • Autorización: ¿puedes hacer esto con este recurso?

El código vulnerable suele hacer bien lo primero y olvidar lo segundo:

const session = await getSession(req);
if (!session) return NextResponse.json({ error: "No autenticado" }, { status: 401 });

const { id } = await params;
// OJO: cualquier usuario autenticado puede leer cualquier factura
const { rows } = await db.query("SELECT * FROM invoices WHERE id = $1", [id]);

Que los ids sean números consecutivos lo hace más fácil de explotar, pero no es la causa: con UUIDs el fallo sigue ahí, solo que es más difícil adivinar el id. Un id difícil de adivinar no sustituye a la comprobación.

Laboratorio

Has iniciado sesión en Facturillas como Alicia. Primero vas a atacar la app como lo haría cualquiera con las herramientas del navegador. Luego vas a arreglar el código y la plataforma lanzará el ataque contra tu versión.

Cargando laboratorio…

Cómo se arregla bien

  1. Filtra por propietario en la propia consulta: WHERE id = $1 AND user_id = $2. Así es imposible olvidarse de comprobarlo después.
  2. Responde 404, no 403, cuando el recurso no es del usuario. Un 403 confirma que la factura existe.
  3. Toma el usuario de la sesión, nunca de la petición. Un userId en el cuerpo o en la query string lo controla el atacante.
  4. Centraliza la regla. Si cada handler la reimplementa, alguno la olvidará. En Supabase, la herramienta para esto es Row Level Security (lo veremos en el siguiente tema).

Checklist para tu proyecto: busca en tu código cada params.id, searchParams.get("id") o similar que acabe en una consulta. Para cada uno, pregúntate: ¿dónde compruebo que este recurso es del usuario de la sesión?