Escritos

¿Qué es una vulnerabilidad IDOR? Autenticación vs. autorización y cómo prevenir BOLA

Si al cambiar /invoices/1042 por /invoices/1043 en la URL ves la factura privada de otro cliente, tu backend sufre IDOR (Broken Object Level Authorization), la vulnerabilidad n.º 1 del OWASP API Security Top 10.

Furkan Özay24 de octubre de 2026 · 3 min de lectura
czCZAY.DEV // EDITORIALES
GÜVENLIK

#Güvenlik · 3 MIN DE LECTURA

FURKAN ÖZAYVOL. 2026 // #C61D

Imagina que inicias sesión en una plataforma de comercio electrónico, abres tu última factura en https://example.com/api/invoices/1042, cambias por curiosidad la URL de la barra del navegador a 1043.

Si el servidor responde con el nombre completo, la dirección de casa y el número de identificación fiscal de otro cliente, ese sistema tiene una vulnerabilidad catastrófica de IDOR (Insecure Direct Object Reference), clasificada por OWASP como API1: Broken Object Level Authorization (BOLA), el fallo más común y peligroso de las APIs modernas.

· · ·

1. ¿Por qué ocurre IDOR? Confundir autenticación con autorización

Casi todos los desarrolladores backend se acuerdan de añadir un middleware de autenticación (requireAuth o verifyJWT) a las rutas protegidas. Sin embargo, la autenticación y la autorización responden a dos preguntas completamente distintas:

  • Autenticación (401 Unauthorized): «¿Quién eres? ¿Has iniciado sesión en una cuenta válida?»
  • Autorización (403 Forbidden): «¿Eres realmente el propietario del recurso n.º 1043 o tienes permiso para acceder a él?»

El patrón de código vulnerable

typescript
// ❌ VULNERABLE: Checks if the user is logged in, but never checks ownership!
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
  const invoice = await db.invoices.findUnique({
    where: { id: Number(req.params.id) }
  });
  return res.json(invoice);
});

Como el atacante sí ha iniciado sesión en su propia cuenta válida, ¡requireAuth lo deja pasar! Después puede escribir un script de 5 líneas que itere de 1 a 500,000 y extraiga toda tu base de datos en cuestión de minutos.

· · ·

2. El mito del UUID: «Uso UUIDv4, ¡así que estoy a salvo!»

Advertencia: La seguridad por oscuridad NO es seguridad

Reemplazar enteros secuenciales (1042) por UUID aleatorios (f47ac10b-58cc-4372-a567-0e02b2c3d479) hace más difícil adivinar IDs por fuerza bruta, pero no corrige la vulnerabilidad IDOR. Los IDs de objetos se filtran con frecuencia en perfiles públicos, payloads de webhooks, el historial del navegador o respuestas de GraphQL. Si no se comprueba la propiedad, cualquiera que obtenga el UUID puede leer o eliminar el registro.

· · ·

3. La solución real: comprobar la propiedad en la consulta a la base de datos

Nunca confíes en un ID de req.params o req.body por sí solo. Vincula siempre la búsqueda directamente a la identidad de la sesión/JWT del usuario autenticado (req.user.id):

typescript
// ✅ SECURE: Combines object ID with the authenticated user's ID in the WHERE clause
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
  const invoice = await db.invoices.findFirst({
    where: {
      id: req.params.id,
      userId: req.user.id // <- Strict Object-Level Authorization!
    }
  });
 
  if (!invoice) {
    // Return 404 (or 403) so attackers cannot even probe if the ID exists
    return res.status(404).json({ error: 'Invoice not found' });
  }
 
  return res.json(invoice);
});

Si usas PostgreSQL, puedes ir un paso más allá y aplicar Row-Level Security (RLS) en la capa del motor de base de datos, de modo que una consulta, matemáticamente, no pueda devolver filas que pertenezcan a otro tenant.

Hoja de ruta y herramienta interactivas

Sigue leyendo