Principiante
CVSS 5.3 - Medio
15 min lectura

Insecure Direct Object References (IDOR)

Acceso no autorizado a objetos modificando parámetros de referencia

¿Qué es IDOR?

IDOR ocurre cuando una aplicación expone una referencia a un objeto interno (archivo, directorio, registro de base de datos) y no verifica adecuadamente si el usuario tiene permiso para acceder a ese objeto.

Ejemplo Clásico

// Usuario autenticado como ID=123
GET /api/users/123/profile → ✅ Su propio perfil

// Cambiar ID en la URL
GET /api/users/124/profile → ❌ Debería rechazar
GET /api/users/125/profile → ❌ Perfil de otro usuario

// Código vulnerable
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user); // ¡Sin verificar si req.user.id === req.params.id!
});

Ejemplos Reales

1. Documentos/Archivos

GET /api/documents/456/download
GET /invoices/invoice_123.pdf
GET /files?id=789

// Atacante prueba IDs secuenciales
for (let i = 1; i <= 1000; i++) {
  fetch(`/api/documents/${i}/download`);
}

2. Modificación de Datos

PUT /api/orders/456
{
  "status": "shipped",
  "address": "attacker address"
}

DELETE /api/posts/789
POST /api/admin/users/123/promote

3. APIs con UUIDs (menos obvios)

// UUIDs son más seguros pero NO suficientes
GET /api/files/a1b2c3d4-e5f6-7890-abcd-ef1234567890

// Si se exponen en respuestas, pueden ser enumerados
GET /api/users/me/documents
{
  "documents": [
    {"id": "uuid-123", "name": "doc1.pdf"},
    {"id": "uuid-456", "name": "doc2.pdf"}
  ]
}

// Atacante puede intentar acceder a uuid-456 de otro usuario

Mitigación

1. Verificar Autorización

// ✅ SEGURO
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  // Verificar que el usuario solo acceda a su propio perfil
  if (req.user.id !== parseInt(req.params.id) && req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

// Para documentos
app.get('/api/documents/:id', authenticate, async (req, res) => {
  const doc = await db.documents.findById(req.params.id);
  
  if (!doc) {
    return res.status(404).json({ error: 'Not found' });
  }
  
  // Verificar ownership
  if (doc.userId !== req.user.id) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  res.json(doc);
});

2. Usar Referencias Indirectas

// En lugar de exponer IDs directos, usar mapeo
// GET /api/documents/doc_abc123 (token de sesión)

const session = {
  userId: 123,
  documentMap: {
    'doc_abc123': 456, // ID real en DB
    'doc_xyz789': 457
  }
};

app.get('/api/documents/:token', (req, res) => {
  const realId = req.session.documentMap[req.params.token];
  if (!realId) {
    return res.status(404).json({ error: 'Not found' });
  }
  
  const doc = await db.documents.findById(realId);
  res.json(doc);
});

3. Filtrar por Usuario en Queries

// ✅ Siempre incluir userId en la query
app.get('/api/orders/:id', authenticate, async (req, res) => {
  const order = await db.orders.findOne({
    where: {
      id: req.params.id,
      userId: req.user.id  // ¡Filtrar por usuario!
    }
  });
  
  if (!order) {
    return res.status(404).json({ error: 'Order not found' });
  }
  
  res.json(order);
});

// Con Prisma
const order = await prisma.order.findFirst({
  where: {
    id: parseInt(req.params.id),
    userId: req.user.id
  }
});

4. UUIDs + Autorización

// UUIDs reducen enumeración pero NO son suficientes
import { v4 as uuidv4 } from 'uuid';

// Usar UUID como ID
const doc = await db.documents.create({
  id: uuidv4(), // "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  userId: req.user.id,
  content: req.body.content
});

// ¡Aún así verificar autorización!
app.get('/api/documents/:uuid', authenticate, async (req, res) => {
  const doc = await db.documents.findOne({
    where: {
      id: req.params.uuid,
      userId: req.user.id  // ¡Verificar ownership!
    }
  });
  
  if (!doc) return res.status(404).json({ error: 'Not found' });
  res.json(doc);
});

Checklist de Seguridad

  • ✓ Verificar autorización en CADA endpoint que accede a recursos
  • ✓ Nunca confiar en parámetros del cliente (IDs, UUIDs, etc)
  • ✓ Usar filtros WHERE con userId en todas las queries
  • ✓ Implementar RBAC (Role-Based Access Control) para recursos compartidos
  • ✓ Logging de intentos de acceso no autorizados
  • ✓ Usar UUIDs en lugar de IDs secuenciales (reduce enumeración)
  • ✓ Testing: Intentar acceder a recursos de otros usuarios en QA

Siguiente Paso

Aprende sobre vulnerabilidades en procesamiento de XML

XML External Entity (XXE)