cd ~/blog

~/vulns/idor-insecure-direct-object-reference.md

Insecure Direct Object Reference (IDOR)

webautor: Jordy Pérez Osorio aviso legal
webaccess-controlIDOR

~6 min de lectura


1. Descripción técnica

Insecure Direct Object Reference (IDOR) es una vulnerabilidad de autorización que ocurre cuando una aplicación expone una referencia a un objeto interno, como un archivo, un registro o una cuenta, y no comprueba si el usuario puede operar sobre ese objeto concreto.

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 pertenece a la categoría Broken Access Control. El identificador manipulable facilita localizar el objeto, pero la vulnerabilidad real es la ausencia de una decisión de autorización en el servidor.

IDOR forma parte del grupo de vulnerabilidades asociadas a una mala implementación de controles de acceso, junto con Broken Access Control.

2. Impacto

Una IDOR puede afectar la confidencialidad cuando permite leer objetos ajenos, la integridad cuando permite modificarlos o eliminarlos y, en operaciones privilegiadas, el control de cuentas o funciones administrativas. El alcance depende de las acciones disponibles para la referencia manipulada y de los datos asociados al objeto.

3. Mecanismo y condiciones necesarias

Un flujo representativo es el siguiente:

  1. El usuario autenticado (ej. Alice) accede a su recurso:
    GET /api/v1/users/1001/profile → devuelve sus datos.

  2. El atacante (Bob, con user_id=1002) intenta acceder a:
    GET /api/v1/users/1001/profile modificando manualmente el ID.

  3. Si el servidor no verifica que Bob tenga permiso para ver el perfil de Alice, devuelve la información de Alice.

  4. La misma comprobación se repite sobre otros identificadores para determinar el alcance, sin asumir que todos los objetos o acciones tienen idénticos controles.

Condiciones comunes que permiten IDOR:

  • Referencias controlables por el cliente, sean secuenciales, UUID o nombres de archivo.
  • Falta de una comprobación de autorización sobre el objeto y la acción solicitada.
  • Confianza en el frontend para ocultar funcionalidades (no es suficiente; el backend debe revalidar).
  • APIs que devuelven más datos de los necesarios (/user/123 devuelve 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).
  • Horizontal Privilege Escalation: acceder a objetos de otro usuario del mismo nivel.
  • Vertical Privilege Escalation: ejecutar una operación reservada para un rol superior.

4. Validación y detección

  1. Burp Suite (Pro / Community)

    • Intercepta y modifica requests fácilmente.
    • Repeater: cambia user_id y observa respuestas.
    • Intruder: automatiza el fuzzing de IDs (1, 2, 3, ..., 1000).
    • Comparador de respuestas para detectar diferencias sutiles.
  2. ZAP (OWASP Zed Attack Proxy)

    • Escaneo automático de endpoints con parámetros.
    • Fuzzing integrado para identificar respuestas inesperadas.
    • Ideal para pruebas iniciales en entornos restringidos.
  3. 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.
  4. SecLists ids.txt

  5. Scripts personalizados (Python, Bash)

    • Automatización rápida para atacar APIs específicas.
    • Ideal para pruebas controladas o PoCs.
    • Ejemplo: uso de requests para iterar IDs con sesión autenticada.

5. 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_xyz

La 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.


6. Laboratorio controlado

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

# Clonar y arrancar
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:8000

La 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)

  1. 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)

    7. Relación con Broken Access Control

    AspectoIDOR (Insecure Direct Object Reference)Broken Access Control (BAC)
    DefiniciónAcceso a objetos vía manipulación directa de IDsFalta de validación de permisos en cualquier operación
    Nivel de granularidadMás específico: ataca objetos individuales (facturas, perfiles)Más amplio: incluye IDOR, funcionalidades, rutas, APIs
    Vector principalParámetros en URL, body o headersCualquier punto donde se salta autorización (roles, scopes)
    Puede incluirCambio de user_id=123 en una peticiónAcceso a /admin siendo usuario normal
    Ejemplo típicoGET /user/123 → ver perfil ajenoGET /api/deleteAllUsers → sin permisos
    En OWASP Top 10Se reporta como una manifestación de BACCategoría A01 en OWASP Top 10 2021 y 2025
    ¿Es IDOR un BAC?Sí, es un subtipo común de Broken Access ControlNo, 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.


    8. Patrones de detección

    • Múltiples requests al mismo endpoint (/user/X, /invoice/X) desde la misma IP.
    • Series de códigos 200 OK para IDs distintos en rápida sucesión (fuzzing).
    • Respuestas válidas para IDs no pertenecientes al usuario autenticado.
    • Códigos 403 Forbidden solo cuando se accede a IDs muy altos o extremos (indicando intentos bloqueados).
    • Logs de aplicación mostrando acceso a user_id=100 siendo usuario 101.
    • 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 IDOR

    Comandos 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 user

    9. Mitigación

    • 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):
        ...

    10. Explotación documentada

    10.1 107-quick-3

    El identificador del perfil se incluye en la URL. Al sustituirlo por otros valores, el servidor devuelve perfiles de clientes distintos y expone pares de usuario y contraseña. El hallazgo se valida comparando la identidad de la sesión con el propietario del objeto devuelto, no solamente observando que el identificador sea predecible.

    10.2 05-darkhole_1

    La petición de cambio de contraseña contiene el identificador del usuario objetivo. Al modificarlo por el identificador de la cuenta administrativa, el backend aplica el cambio sin comprobar que la sesión tenga autorización sobre esa cuenta. Este caso afecta a la integridad y produce Horizontal o Vertical Privilege Escalation según el modelo de roles de la aplicación.

    11. Clasificación

    • Tipo: Insecure Direct Object Reference / Broken Access Control
    • CWE:
      • CWE-639: Authorization Bypass Through User-Controlled Key

    12. Referencias

    // relacionados