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
// ❌ 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):
// ✅ 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
Desarrollador Backend Junior 2026
Lo que realmente necesitas saber para conseguir un puesto backend junior en 2026 y lo que puede esperar, filtrando las trampas de palabras de moda de las ofertas reales.
¿SQL o NoSQL?
Responde seis preguntas y te diré qué tipo de base de datos encaja con tu proyecto, y por qué.
Sigue leyendo
- CZESCortando el ruido: ¿qué lenguaje y framework de backend deberías elegir en 2026?
Cortando el ruido: ¿qué lenguaje y framework de backend deberías elegir en 2026?
25 de octubre de 2026 · 4 min de lectura - CZESDentro de PHP CVE-2026-115560: el desajuste entre Session Upload Progress y el serialize handler
Dentro de PHP CVE-2026-115560: el desajuste entre Session Upload Progress y el serialize handler
24 de octubre de 2026 · 4 min de lectura