IDOR: qué es, cómo se explota y cómo se corrige el acceso a datos de otros usuarios
· 12 min de lectura
Aprende qué es un IDOR (referencia directa insegura a objetos), por qué basta cambiar un número en la URL para ver datos ajenos, cómo se detecta y se explota en un laboratorio autorizado y cómo se corrige con controles de autorización en el servidor.
Imagina que entras a una tienda en línea, abres tu factura y en la barra de direcciones ves /factura?id=1045. Por curiosidad cambias el número a 1044 y aparece la factura de otra persona, con su nombre, su dirección y lo que compró. No usaste ninguna herramienta ni escribiste código: solo cambiaste un número.
Eso es un IDOR, y es una de las vulnerabilidades más comunes y más rentables en programas de bug bounty, precisamente porque es fácil de pasar por alto al programar y muy fácil de explotar. En esta lección vas a ver qué es, por qué ocurre, dónde suele esconderse, cómo se detecta y se explota en un laboratorio autorizado y cómo se corrige de verdad.
Practica estas técnicas solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Acceder a datos ajenos sin permiso es un delito, aunque solo hayas cambiado un número.
Qué es un IDOR#
IDOR significa Insecure Direct Object Reference, en español referencia directa insegura a objetos. Ocurre cuando una aplicación usa un identificador que controla el usuario (un número, un nombre de archivo, un UUID) para acceder a un recurso, y no comprueba si ese usuario tiene derecho a acceder a ese recurso concreto.
La aplicación sí sabe quién eres, porque iniciaste sesión. Lo que no hace es preguntarse si la factura, el pedido o el perfil que pides es tuyo. Esa comprobación que falta se llama autorización a nivel de objeto.

En el mundo de las APIs, OWASP llama a este mismo problema BOLA (Broken Object Level Authorization), y lo sitúa en el primer puesto de su lista de riesgos de APIs. IDOR y BOLA son, en la práctica, la misma vulnerabilidad con dos nombres.
Por qué ocurre: la causa raíz#
Mira este código, muy parecido al que se encuentra en aplicaciones reales:
// /factura.php?id=1045
session_start();
if (empty($_SESSION['usuario_id'])) {
header('Location: /login');
exit;
}
$stmt = $db->prepare('SELECT * FROM facturas WHERE id = ?');
$stmt->execute([$_GET['id']]);
$factura = $stmt->fetch();
mostrar($factura);
El código comprueba que hay una sesión (autenticación) y usa una consulta parametrizada, así que no hay inyección SQL. Parece seguro. Pero busca la factura solo por su número, sin mirar a quién pertenece. Cualquier usuario con sesión iniciada puede ver cualquier factura cambiando el id.
El error de fondo es confundir dos preguntas distintas:
| Pregunta | Nombre | ¿La resuelve el código de arriba? |
|---|---|---|
| ¿Quién eres? | Autenticación | Sí, con la sesión |
| ¿Puedes hacer esto con este objeto concreto? | Autorización | No: aquí está el IDOR |
Tipos de IDOR#
| Tipo | Qué consigue el atacante | Ejemplo |
|---|---|---|
| Lectura horizontal | Ver datos de otro usuario de su mismo nivel | Ver el pedido de otro cliente |
| Modificación horizontal | Cambiar datos de otro usuario | Editar la dirección de envío de otra cuenta |
| Borrado | Eliminar recursos ajenos | Borrar el comentario o el archivo de otra persona |
| Vertical | Acceder a recursos de un rol superior | Abrir la ficha de un administrador o una función de administración |
| A ciegas | Ejecutar una acción sobre otro objeto sin ver el resultado | Cancelar la suscripción de otro usuario |
Dónde aparece#
Cualquier lugar donde la petición incluya un identificador de un recurso es candidato:
- Parámetros de la URL:
/pedido?id=8812,/usuarios/531/perfil. - Cuerpos de formularios y JSON:
{"cuenta_id": 77, "monto": 50}en una transferencia o un cambio de datos. - Rutas de API REST:
GET /api/v1/tickets/3002,DELETE /api/v1/direcciones/19. - Descargas de archivos:
/descargar?archivo=contrato_1045.pdf. - Cabeceras y cookies: una cookie
user_id=531que la aplicación usa para decidir de quién son los datos. - Funciones secundarias: exportar a PDF, enviar por correo, imprimir o compartir, que a menudo se programan con menos cuidado que la pantalla principal.
Un detalle importante: usar UUID largos y aleatorios en lugar de números consecutivos hace que los identificadores sean difíciles de adivinar, pero no corrige el IDOR. Los UUID se filtran en otras respuestas, en correos o en el historial, y si el servidor no comprueba el dueño, quien tenga el UUID entra.
Cómo se detecta#
La técnica de las dos cuentas#
La forma más fiable de encontrar un IDOR es trabajar con dos cuentas propias dentro de un entorno autorizado:
- Con la cuenta A, crea un recurso (un pedido, una nota, un archivo) y anota su identificador.
- Con la cuenta B, intenta leer, modificar o borrar ese recurso usando el identificador de la cuenta A.
- Si la cuenta B lo consigue, hay un IDOR.
Así no tocas datos de nadie más y tienes una prueba clara y reproducible.
Qué probar en cada petición#
| Prueba | Qué buscas |
|---|---|
| Cambiar el identificador por el de la otra cuenta | Que el servidor devuelva o modifique el recurso ajeno |
| Cambiar el método HTTP (de GET a PUT, PATCH o DELETE) | Que la lectura esté protegida pero la modificación no |
| Repetir el identificador en otro sitio de la petición | Que un id en el cuerpo pese más que el de la URL |
| Probar funciones secundarias (exportar, imprimir, compartir) | Endpoints menos revisados que la pantalla principal |
| Quitar o cambiar la cookie de rol o de usuario | Que la aplicación confíe en datos que controla el cliente |
Herramientas#
- Burp Suite: para interceptar peticiones, cambiar identificadores y repetirlas con el Repeater. Su extensión Autorize repite automáticamente cada petición con la sesión de otra cuenta y marca las que deberían haber fallado.
- Navegador con dos perfiles: una cuenta en cada perfil facilita la técnica de las dos cuentas sin cerrar sesión.
- Revisión de código: buscar consultas que filtran solo por
idsin incluir el dueño o el rol.
Explotación paso a paso en un laboratorio#
Vamos a recorrer un IDOR típico en una API de perfiles dentro de un laboratorio autorizado. La aplicación tiene una sección «Mi perfil» que, al cargar, hace esta petición:
GET /api/perfil/1031 HTTP/1.1
Host: IP_DEL_LAB
Cookie: session=TU_SESION
Y el servidor responde con tus datos:
{"id": 1031, "usuario": "estudiante", "correo": "[email protected]", "rol": "usuario"}
Paso 1: identificar el identificador#
El número 1031 está en la ruta y coincide con tu propio id. Es un objeto referenciado directamente: candidato perfecto.
Paso 2: probar con otro identificador#
En Burp, envía la petición al Repeater y cambia el número:
GET /api/perfil/1030 HTTP/1.1
Host: IP_DEL_LAB
Cookie: session=TU_SESION
Si la respuesta trae el perfil de otro usuario, el servidor no está comprobando a quién pertenece el perfil.
{"id": 1030, "usuario": "maria", "correo": "[email protected]", "rol": "usuario"}
Paso 3: medir el alcance#
En un laboratorio, el siguiente paso es comprobar hasta dónde llega. Con Burp Intruder, o con un pequeño script, puedes recorrer un rango de identificadores:
for id in $(seq 1000 1040); do
curl -s -b "session=TU_SESION" "http://IP_DEL_LAB/api/perfil/$id"
echo
done
Fíjate en los perfiles con "rol": "admin" o en campos que no deberían verse, como tokens, direcciones o datos de pago.
Paso 4: probar la escritura#
Leer es grave, pero modificar suele serlo más. Si existe una petición para actualizar el perfil, prueba a enviarla con el identificador ajeno:
PUT /api/perfil/1030 HTTP/1.1
Host: IP_DEL_LAB
Cookie: session=TU_SESION
Content-Type: application/json
{"correo": "[email protected]"}
Si el cambio se aplica, el atacante podría cambiar el correo de otra cuenta y luego usar «recuperar contraseña» para quedarse con ella. Así es como un IDOR se convierte en robo de cuentas.
Paso 5: buscar el salto vertical#
Por último, revisa si con el mismo método puedes llegar a recursos de administración, como /api/perfil/1 o /api/admin/usuarios/1030. Muchas veces el control de acceso por rol también falla en los mismos puntos.
Qué puede lograr un atacante#
| Impacto | Ejemplo real |
|---|---|
| Filtración de datos personales | Nombres, correos, direcciones, documentos de identidad de todos los clientes |
| Robo de cuentas | Cambiar el correo o el teléfono de otra cuenta y restablecer su contraseña |
| Fraude | Ver o modificar pedidos, facturas, saldos o transferencias ajenas |
| Escalada de privilegios | Acceder a funciones o datos de administradores |
| Sabotaje | Borrar archivos, comentarios o configuraciones de otros usuarios |
Como los identificadores suelen ser consecutivos, un IDOR casi nunca afecta a un solo usuario: con un script se recorre la base de datos entera.
Cómo se corrige#

La regla es simple: en cada petición, el servidor debe comprobar que el usuario tiene permiso sobre ese objeto concreto. No basta con que haya iniciado sesión.
Filtrar siempre por el dueño#
La corrección más directa es incluir al usuario de la sesión en la propia consulta:
// Vulnerable: busca solo por id
$stmt = $db->prepare('SELECT * FROM facturas WHERE id = ?');
$stmt->execute([$_GET['id']]);
// Seguro: el objeto debe ser del usuario de la sesión
$stmt = $db->prepare('SELECT * FROM facturas WHERE id = ? AND usuario_id = ?');
$stmt->execute([$_GET['id'], $_SESSION['usuario_id']]);
$factura = $stmt->fetch();
if (!$factura) {
http_response_code(404); // no revelar si existe
exit;
}
Responder con 404 en lugar de 403 evita confirmar al atacante que ese identificador existe.
Centralizar la autorización#
Repetir la comprobación a mano en cada endpoint es como acaba olvidándose en uno. Es mejor una función o una política central que todos usen:
# Python (Flask): una comprobación reutilizable
def obtener_propio(modelo, objeto_id, usuario):
objeto = modelo.query.get_or_404(objeto_id)
if objeto.usuario_id != usuario.id and not usuario.es_admin:
abort(404)
return objeto
@app.get("/api/pedidos/<int:pedido_id>")
@login_required
def ver_pedido(pedido_id):
pedido = obtener_propio(Pedido, pedido_id, current_user)
return jsonify(pedido.a_dict())
La mayoría de frameworks tienen mecanismos para esto, como las políticas de Laravel o los guards y policies de otros entornos. Úsalos en lugar de reinventarlos.
No confiar en lo que envía el cliente#
El servidor debe sacar la identidad de la sesión o del token, nunca de un campo que el usuario pueda cambiar. Si la API recibe "usuario_id": 531 en el cuerpo para decidir de quién son los datos, el atacante solo tiene que cambiar ese número.
Denegar por defecto y probarlo#
- Denegar por defecto: si no hay una regla explícita que permita el acceso, se niega.
- Pruebas automáticas: añade a tus tests un caso que pida el recurso de otro usuario y espere un 404 o 403. Es la forma de que un IDOR no vuelva en la próxima versión.
- Registrar intentos: muchas peticiones seguidas a identificadores ajenos son una señal clara de enumeración.
Errores comunes al defenderse#
- Pensar que ocultar el botón protege. Si la interfaz no muestra el enlace pero el endpoint responde, el IDOR sigue ahí.
- Confiar en identificadores difíciles de adivinar. Los UUID ayudan, pero no sustituyen la comprobación del dueño.
- Proteger la lectura y olvidar la escritura. Es muy común que
GETcompruebe el dueño yPUToDELETEno. - Revisar solo la pantalla principal. Exportar, imprimir, compartir o las versiones antiguas de la API (
/api/v1/) suelen quedarse sin control. - Cifrar o codificar el identificador. Convertirlo a Base64 o cifrarlo en el cliente no cambia nada si el servidor no comprueba el dueño.
Preguntas frecuentes#
¿Qué diferencia hay entre IDOR y BOLA?#
Ninguna en la práctica. IDOR es el nombre clásico en aplicaciones web y BOLA (Broken Object Level Authorization) es el que usa OWASP para APIs. Ambos describen la falta de una comprobación de autorización sobre un objeto concreto.
¿Usar UUID en vez de números corrige el IDOR?#
No. Dificulta adivinar identificadores, pero si un UUID se filtra (en otra respuesta, un correo o una URL compartida), cualquiera puede usarlo. La corrección es comprobar el dueño en el servidor.
¿Un IDOR es una vulnerabilidad grave?#
Depende de los datos y de las acciones expuestas, pero suele serlo: con identificadores consecutivos se puede recorrer la base de datos completa, y si permite modificar datos puede llevar al robo de cuentas.
¿Cómo se encuentra un IDOR sin acceso al código?#
Con la técnica de las dos cuentas: creas recursos con una cuenta e intentas acceder a ellos con otra, en un entorno autorizado. Herramientas como Burp Suite y su extensión Autorize automatizan gran parte del trabajo.
¿Es legal cambiar el número de una URL para ver qué pasa?#
Acceder a datos de otra persona sin autorización es un delito en la mayoría de países, aunque la técnica sea tan simple como cambiar un número. Hazlo solo en tus laboratorios, en sistemas propios o en programas de bug bounty que lo permitan expresamente.
Resumen#
- Un IDOR ocurre cuando la aplicación usa un identificador que controla el usuario y no comprueba si ese objeto le pertenece.
- Estar autenticado no es lo mismo que estar autorizado: la sesión dice quién eres, no qué objetos puedes tocar.
- Se detecta con la técnica de las dos cuentas, probando lectura, modificación y borrado en todos los endpoints.
- Se corrige filtrando siempre por el dueño en el servidor, centralizando la autorización, sacando la identidad de la sesión y denegando por defecto.
- Los UUID, ocultar botones o codificar identificadores no son la corrección.