Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel IntermedioGratis

IDOR y BAC — metodología manual para encontrar broken access control

Cómo identificar IDOR (Insecure Direct Object Reference) y BAC (Broken Access Control) manualmente: dual-account testing, parameter swap, role escalation, hidden parameters.

Gorka El Bochi11 de mayo de 202614 min

Respuesta rápida

IDOR y BAC siguen siendo el #1 de OWASP API Security en 2026. La regla: scanners automáticos no los encuentran — requieren contexto de negocio (¿qué hace este endpoint?) y dos cuentas para verificar el access boundary. La metodología: dos cuentas (A y B), capturar requests de A en Burp, swappear IDs/cookies por los de B, observar la respuesta. Si A puede ver/editar/borrar recursos de B, es IDOR. Si un user normal puede llegar a endpoints de admin, es BAC vertical. Bounties típicos: €500-€10.000+ según el impacto.


Setup — dos cuentas en paralelo

El error de principiante: probar IDOR con una sola cuenta. Sin la cuenta B no tienes baseline — no sabes si el server rechaza al user de A o si simplemente el ID no existe.

Stack:

  • Burp Suite (Community vale) con dos browsers (Chrome perfil A, Firefox perfil B).
  • O dos perfiles del mismo browser + extensiones tipo Auth Analyzer o Autorize.
  • Match-and-replace en Burp para swappear cookies automáticamente.
css
Browser A (Chrome)  →  Burp Proxy  →  target.com
Browser B (Firefox) →  Burp Proxy  →  target.com

Captura un request de A, manda a Repeater, cambia solo la cookie/header de auth por la de B (todo lo demás igual). Si la respuesta es la misma que la de A → IDOR confirmado.

[!tip] Auth Analyzer extension Autoriza Burp a re-enviar cada request con la sesión de un user diferente y comparar la respuesta. Lo más rápido para auditar 50+ endpoints sin script.


Las 8 superficies donde aparece IDOR

#SuperficieEjemplo de URLTest
1Path parameter numérico/api/users/1234512344, 12346, 0, -1
2Path parameter UUID/api/orders/abc-def-ghiUUID de B obtenido por otra vía
3Query parameter/profile?userId=42Cambiar a 43
4Body JSON field{"userId": 42, "data": "..."}Swap userId al de B
5Header customX-User-Id: 42Cambiar a 43
6Cookie valueCookie: uid=42; ...Cambiar a 43
7Hidden form field<input type="hidden" name="user_id" value="42">Cambiar antes de submit
8JWT claim{"sub": "42", "role": "user"}Re-firmar con sub=43 si firma débil

El truco de oro: muchas apps validan el ID en el path pero no el del body. Si el endpoint es POST /api/users/42/profile con body {"userId": 42, "newName": "..."}, prueba dejar el path en 42 pero meter userId: 43 en el body. Frecuentemente el server ignora el path y confía en el body.


Cómo obtener IDs ajenos

Sin IDs de la cuenta B (o de otros users) no puedes confirmar IDOR. Fuentes:

  • Tu propia cuenta B: regístrala con email distinto y observa qué IDs aparecen.
  • Endpoints de listado público: /users/search, /api/leaderboard, /comments?postId=X.
  • Funcionalidades que filtran IDs: avatars (/avatars/USER_ID.jpg), notifications (from_user_id), public posts.
  • URLs compartidas: invitación, magic link, recibo de compra que incluye order ID.
  • Source maps + bundles JS: a veces los routers en frontend listan IDs hardcoded de demos.
  • GraphQL introspección + queries de búsqueda sin filtro de visibilidad.

[!warning] Privacy + scope Si encuentras IDOR que filtra PII (email, phone, dirección, DNI), documenta CON tu propia cuenta de control — no tires bulk extraction contra users reales. La mayoría de programs prohíbe extraction de >1-2 records de cuentas no propias.


IDOR horizontal vs vertical

Horizontal — mismo rol, otra identidad. User A accede a recursos de user B:

http
GET /api/orders/B_ORDER_ID
Authorization: Bearer JWT_DE_A
                                    ← 200 OK con datos de B = IDOR horizontal

Vertical — escalación de privilegios. User normal accede a endpoints/funciones de admin:

http
DELETE /api/admin/users/12
Authorization: Bearer JWT_DE_USER_NORMAL
                                    ← 200 OK = BAC vertical

Vertical pesa más en CVSS — usualmente Critical (9.0+). Bounties típicos €5K-€20K+.


Parameter swap — el patrón más rentable

Casi todos los IDOR rentables vienen de swappear un parámetro en un endpoint que parece inofensivo:

Cambio de email — sin verificación

http
PUT /api/users/me/email
{"email": "atacante@evil.tld", "userId": ID_DE_VICTIMA}

Server actualiza email del userId del body, ignora el me del path → reset password vía email controlado → ATO.

Cambio de password — oldPassword no verificado

http
POST /api/users/CHANGE_PASSWORD
{"userId": ID_VICTIMA, "newPassword": "atacante"}

Endpoint no pide oldPassword. Tipicamente bounty €3K-€8K.

Cancelación de orden — refund a cuenta atacante

http
POST /api/orders/B_ORDER_ID/refund
{"refundTo": "atacante_bank_account"}

Asignación de rol en team

http
POST /api/teams/T_ID/members
{"userId": ATACANTE_ID, "role": "owner"}

Si el endpoint no verifica que el actor sea owner del team → cualquier user puede unirse como owner a cualquier team.


Hidden parameters — los que generan bounties grandes

Muchos endpoints aceptan parámetros que no aparecen en requests legítimos del UI. El backend los procesa si están presentes, pero el frontend nunca los manda. Resultado: features no documentadas, a veces de admin.

Wordlist de parámetros frecuentes

kotlin
admin, isAdmin, is_admin, role, userRole, accountType
debug, test, dev, sandbox, preview
force, bypass, override, skipAuth
verified, isVerified, emailVerified
status, accountStatus, suspended, banned
internal, system, super, root
hidden, public, visible

Tools

bash
# Arjun — descubre parámetros HTTP ocultos
arjun -u https://target.com/api/users -m GET

# Param Miner (Burp extension)
# Right-click request → Extensions → Param Miner → Guess params

Ejemplos de bounty real con hidden params

  • POST /api/users con body normal {email, password} + extra {"role": "admin"} → user nuevo creado como admin.
  • PUT /api/posts/X con extra {"approvedBy": "system"} → bypassea cola de moderación.
  • GET /api/orders/X?debug=true → response expandida con campos internos (PII, internal IDs, cost).

Function-level BAC — endpoints que el frontend no llama

El UI normal nunca llama a /api/admin/users desde un user normal — pero el endpoint sigue existiendo y a veces no chequea el rol.

Cómo encontrarlos

  1. Login como admin (test account si el program lo permite, o user normal con permisos altos).
  2. Captura todos los endpoints que el admin dashboard llama.
  3. Logout, login como user normal.
  4. Re-tira los mismos endpoints. ¿Hay alguno que responda 200?

Frecuentemente encuentras endpoints que verifican el token (auth) pero no el rol (authorization).

http
admin → GET /api/admin/users → 200 [lista de users]
user  → GET /api/admin/users → 200 [lista de users]    ← BAC vertical

Method swap

Algunos endpoints solo validan el método HTTP correcto. Prueba:

swift
POST /api/admin/users      403 Forbidden
GET  /api/admin/users      200 OK              BAC
PUT  /api/admin/users/42   403 Forbidden
PATCH /api/admin/users/42  200 OK              BAC

Trailing slash + extension trick

Algunos WAFs filtran rutas exactas. Prueba variantes:

swift
/api/admin/users      403
/api/admin/users/     200             trailing slash bypass
/api/admin/users.json  200            extension
/api/admin/users;.css  200            matrix params
/api/admin/users%20   200             trailing whitespace
/api/Admin/users      200             case
/api/admin//users    → 200            ← double slash

IDOR en GraphQL

GraphQL multiplica la superficie: un solo endpoint expone N queries. El control de acceso suele estar a nivel de resolver, no de field — muy fácil olvidarse de alguno.

Test pattern

graphql
query {
  user(id: "OTRO_USER_ID") {
    email
    phoneNumber
    addresses { street }
    paymentMethods { last4 }
  }
}

Si la app no chequea ownership en el resolver de user, lo devuelve. Repite con node(id: ...) (interface common en relay) y con queries de búsqueda sin filtro de visibilidad.

Mutations

graphql
mutation {
  updateUser(id: "VICTIMA_ID", input: { email: "atacante@evil.tld" }) {
    id
  }
}

Muchos backends GraphQL verifican que el user está autenticado pero no que sea owner del id que pide modificar.


Reporting tips

  • Severity boost: si el IDOR filtra PII regulado (DNI, datos médicos, financieros) → impact privacy, GDPR.
  • Account takeover chain: si IDOR permite cambiar email → ATO inmediato, severity Critical.
  • Bulk extraction warning: documenta el patrón con 2-3 records de tu cuenta + cuenta control. NO extraigas miles.
  • Bypass docs: lista cada filtro intentado por el server (path validation, body validation, method check) y cómo lo bypaseaste — sube el reward y el dev fix más sólido.

Hunting checklist

  • Setup dos cuentas (A y B) en paralelo con Burp/Auth Analyzer.
  • Mapea todos los endpoints que aceptan ID en path, query, body, header o cookie.
  • Swap ID con sesión de B, observa respuesta.
  • Prueba todas las acciones (GET / PUT / DELETE / POST) — algunas validan GET pero no DELETE.
  • Hidden parameters con Arjun + Param Miner.
  • Login como admin (si tienes), captura endpoints admin, reproduce con user normal.
  • Method swap + trailing slash + extension tricks en endpoints 403.
  • GraphQL: prueba user(id), node(id), queries de listado sin filtro.
  • Cross-tenant: si la app es multi-tenant, prueba accesos cross-org.
  • Documenta con 2-3 records máx, never bulk extract.

Labs relacionados

Practica IDOR horizontal/vertical, hidden params y BAC function-level en labs de IDOR y BAC.

Practica esto en un lab

Idor Api Newsletter Suscriptores

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta
Premium · 1 técnica más

Hay un payload extra al final

Los 12 parámetros ocultos que tests automáticos no detectan pero generan bounties de €1500-€5000 con regularidad.

Desbloquear

5 €/mes · cancela cuando quieras

Artículos relacionados