IDOR (Insecure Direct Object Reference) es una de las vulnerabilidades más comunes y peligrosas en aplicaciones web. Este artículo explica cómo funciona, cómo detectarla con Burp Suite o ZAP, ejemplos reales en Django y las buenas prácticas para prevenirla.
1. ¿Qué es IDOR?
IDOR (Insecure Direct Object Reference) es una vulnerabilidad de autorización que ocurre cuando una aplicación web expone directamente referencias a objetos internos —archivos, registros de base de datos, documentos o funcionalidades— sin validar adecuadamente si el usuario autenticado tiene permiso para acceder a ellos.
Esta vulnerabilidad permite a un atacante acceder, visualizar, modificar o eliminar datos sensibles simplemente manipulando valores en la URL, parámetros POST o cabeceras, como user_id=123 o invoice_id=999.
Un ataque IDOR exitoso puede comprometer:
- Datos personales (emails, teléfonos, direcciones).
- Información financiera (facturas, transacciones).
- Credenciales almacenadas (a través de respuestas expuestas).
- Funcionalidades administrativas restringidas.
IDOR se clasifica dentro de las fallas de acceso no autorizado, y es especialmente común en aplicaciones con APIs RESTful o interfaces de usuario dinámicas, donde el backend confía ciegamente en los parámetros proporcionados por el cliente.
IDOR forma parte del grupo de vulnerabilidades asociadas a una mala implementación de controles de acceso, junto con Broken Access Control.
2. ¿Cómo funciona un ataque IDOR?
El flujo de ataque es sencillo, pero devastador:
El usuario autenticado (ej. Alice) accede a su recurso:
GET /api/v1/users/1001/profile→ devuelve sus datos.El atacante (Bob, con
user_id=1002) intenta acceder a:GET /api/v1/users/1001/profilemodificando manualmente el ID.Si el servidor no verifica que Bob tenga permiso para ver el perfil de Alice, devuelve la información de Alice.
El atacante puede escalar: iterar IDs, acceder a endpoints administrativos, o modificar datos.
Condiciones comunes que permiten IDOR:
- Uso de valores secuenciales, predecibles o guessables (
1,2,1000,1001). - Falta de validación en el lado del servidor (
if user.role == 'admin'ausente o mal implementado). - Confianza en el frontend para ocultar funcionalidades (no es suficiente; el backend debe revalidar).
- APIs que devuelven más datos de los necesarios (
/user/123devuelve email, password_hash, token, etc.).
Tipos comunes de IDOR:
- User-Based IDOR: Un usuario accede a perfiles/órdenes de otros.
- Admin IDOR: Acceso no autorizado a funcionalidades administrativas (
/admin/delete?user_id=1001). - File/Resource IDOR: Acceso a documentos privados (
/download?file_id=55). - Vertical vs Horizontal Privilege Escalation:
- Horizontal: acceder a otro usuario del mismo nivel (Bob → Alice).
- Vertical: elevar a nivel administrador (usuario → admin).
3. Herramientas
Burp Suite (Pro / Community) – esencial para testear IDOR
- Intercepta y modifica requests fácilmente.
- Repeater: cambia
user_idy observa respuestas. - Intruder: automatiza el fuzzing de IDs (1, 2, 3, ..., 1000).
- Comparador de respuestas para detectar diferencias sutiles.
ZAP (OWASP Zed Attack Proxy) – alternativa gratuita
- Escaneo automático de endpoints con parámetros.
- Fuzzing integrado para identificar respuestas inesperadas.
- Ideal para pruebas iniciales en entornos restringidos.
Autorización (extensión de Burp Suite)
- Detecta automáticamente brechas en autorización.
- Compara respuestas entre usuarios con distintos roles.
- Muy útil para identificar casos donde un usuario normal accede a contenido de admin.
SecLists – ids.txt
- Lista predefinida de valores comunes (secuenciales, UUIDs, etc.).
- Usada con Intruder o en scripts personalizados.
- Disponible en: https://github.com/danielmiessler/SecLists
Scripts personalizados (Python, Bash)
- Automatización rápida para atacar APIs específicas.
- Ideal para pruebas controladas o PoCs.
- Ejemplo: uso de
requestspara iterar IDs con sesión autenticada.
4. Ejemplo conceptual
Supón que una app de facturación tiene este endpoint:
GET /api/invoices/555 HTTP/1.1
Host: billing.example.com
Authorization: Bearer user_session_xyzLa respuesta:
{
"id": 555,
"user_id": 2001,
"amount": "1250.00",
"date": "2025-03-15",
"details": "Servicio de hosting anual"
}Un atacante cambia 555 por 556, 557, etc. Si el servidor solo verifica autenticación, no autorización, puede obtener facturas de otros usuarios.
No hace falta SQLi, solo manipulación de referencias directas.
5. PoC
He desarrollado una aplicación Django vulnerable a IDOR para fines educativos. El código y pruebas están disponibles en: github.com/wh01s17/idor_vulnerable
git clone https://github.com/wh01s17/idor_vulnerable
cd idor_vulnerable
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver 0.0.0.0:8000La app tiene:
- Dos usuarios: Alice (ID: 1) y Bob (ID: 2).
- Cada uno con sus facturas (
/invoices/). - Endpoint vulnerable:
GET /invoices/<id>/
Escenario de ataque (Bob → Alice)
Bob inicia sesión:
curl -c cookies.txt -X POST http://localhost:8000/login/ \
-d "username=bob&password=bob123"Consulta su factura:
curl -b cookies.txt http://localhost:8000/invoices/2/
# Respuesta: {"id": 2, "user_id": 2, "amount": "350.00", ...}Intenta acceder a la factura de Alice (ID 1):
curl -b cookies.txt http://localhost:8000/invoices/1/
# ¡Éxito! Respuesta: {"id": 1, "user_id": 1, "amount": "1200.00", ...}El backend no valida:
# 🚫 Vulnerable: no comprueba user_id
invoice = get_object_or_404(Invoice, id=invoice_id)Solución correcta:
# ✅ Segura: vincula al usuario autenticado
invoice = get_object_or_404(Invoice, id=invoice_id, user=request.user)6. IDOR vs Broken Access Control
| Aspecto | IDOR (Insecure Direct Object Reference) | Broken Access Control (BAC) |
|---|---|---|
| Definición | Acceso a objetos vía manipulación directa de IDs | Falta de validación de permisos en cualquier operación |
| Nivel de granularidad | Más específico: ataca objetos individuales (facturas, perfiles) | Más amplio: incluye IDOR, funcionalidades, rutas, APIs |
| Vector principal | Parámetros en URL, body o headers | Cualquier punto donde se salta autorización (roles, scopes) |
| Puede incluir | Cambio de user_id=123 en una petición | Acceso a /admin siendo usuario normal |
| Ejemplo típico | GET /user/123 → ver perfil ajeno | GET /api/deleteAllUsers → sin permisos |
| En OWASP Top 10 | Históricamente parte de BAC | Categoría A01 en OWASP Top 10 2021 y 2023 |
| ¿Es IDOR un BAC? | Sí, es un subtipo común de Broken Access Control | No, BAC es la categoría general |
💡 Conclusión: IDOR es una manifestación específica de Broken Access Control. Hoy se reporta bajo A01 en OWASP, pero sigue siendo un nombre clave en pentesting.
7. Patrones de logs y señales
- Múltiples requests al mismo endpoint (
/user/X,/invoice/X) desde la misma IP. - Series de códigos
200 OKpara IDs distintos en rápida sucesión (fuzzing). - Respuestas válidas para IDs no pertenecientes al usuario autenticado.
- Códigos
403 Forbiddensolo cuando se accede a IDs muy altos o extremos (indicando intentos bloqueados). - Logs de aplicación mostrando acceso a
user_id=100siendo usuario101. - Actividad fuera del rango normal (ej: usuario normal accede a
?admin=true).
Ejemplo de línea de access_log (Django):
"GET /invoices/101/ HTTP/1.1" 200 821 "user=bob"
"GET /invoices/102/ HTTP/1.1" 200 902 "user=bob"
"GET /invoices/103/ HTTP/1.1" 200 762 "user=bob"
# Bob (user 2) accediendo a facturas de usuarios 101-103 -> clara señal de IDORComandos de monitoreo:
# Ver últimas 50 peticiones a /invoices/
grep "/invoices/" logs/django.log | tail -50
# Contar cuántos IDs distintos ha consultado una IP
grep "192\.0\.2\.10" access.log | grep "/invoices/" | grep -o "/[0-9]*" | sort | uniq -c
# Detectar usuarios que acceden a muchos objetos
awk '{print $NF}' access.log | sort | uniq -c | sort -nr | head
# último campo = usuario; contar accesos por user8. Mitigación y defensa más comunes contra IDOR
Validar siempre el ownership en el backend:
# Django (correcto)
invoice = get_object_or_404(Invoice, id=invoice_id, user=request.user)Usar identificadores no guessables (UUIDs):
- Evita secuencias predecibles:
user-8a3f9d2c-e1b4-4c1f-8f3a-d5d7c1b8e9a1 - No elimina IDOR, pero dificulta el fuzzing.
Control de acceso basado en rol (RBAC):
if request.user.role == 'admin'antes de ejecutar acciones sensibles.
Implementar ABAC (Attribute-Based Access Control) en sistemas complejos:
- Evaluar políticas por atributos: departamento, geolocalización, etc.
No exponer IDs directos si no es necesario:
- Mapear internamente:
/docs/share/abc123→ ID 999 en DB.
Monitoreo de comportamiento anómalo:
- Alertas por acceso rápido a múltiples recursos.
- Uso de SIEMs para correlacionar acceso y roles.
Pruebas de autorización en cada capa:
- No confiar solo en el frontend: el backend debe validar todo.
Auditar APIs con herramientas de pruebas de acceso:
- Usar Burp Suite + Autorización extension.
- Probar con múltiples usuarios simulados.
Registro completo de acceso a recursos sensibles:
- Loggear:
user_id,target_id,endpoint,timestamp,resultado.
Ejemplo de middleware de autorización (Django):
def owner_required(model, id_param):
def decorator(view_func):
def wrapper(request, *args, **kwargs):
obj_id = kwargs[id_param]
obj = get_object_or_404(model, id=obj_id)
if obj.user != request.user:
return HttpResponseForbidden("Acceso denegado")
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# Uso:
@owner_required(Invoice, 'invoice_id')
def invoice_detail(request, invoice_id):
...IDOR nos enseña una lección fundamental: la autenticación no es autorización.
Aunque el usuario esté logueado, eso no significa que pueda ver todo. Validar el acceso a cada objeto, especialmente en APIs, es esencial para proteger la privacidad y la integridad de los datos.
Aplicar controles de acceso estrictos en el backend, usar UUIDs, y auditar accesos son prácticas que detienen el 99% de estos ataques. En entornos modernos, herramientas como Burp Suite y Autorización hacen trivial detectarlos.
Con esta base, puedes comenzar a descubrir y corregir una de las vulnerabilidades más comunes y subestimadas en aplicaciones web.
¡Hasta el próximo post!