API 5 min de lectura

BOLA en APIs: por qué sigue siendo el hallazgo nº 1 y cómo lo encadenamos hasta datos de otros clientes

Broken Object Level Authorization lidera el OWASP API Top 10 desde hace años y sigue siendo el hallazgo crítico más común en nuestros pentests de API. El mecanismo, las variantes que los escáneres no detectan y qué corrige de verdad.

También en PTENPT-BR
BOLA en APIs: por qué sigue siendo el hallazgo nº 1 y cómo lo encadenamos hasta datos de otros clientes

Un número en el lugar equivocado

GET /api/v2/invoices/48213 devuelve la factura del usuario autenticado. GET /api/v2/invoices/48212 devuelve la de otro cliente. Sin error, sin log, sin alerta. Eso es BOLA — Broken Object Level Authorization —, el ítem API1 del OWASP API Security Top 10, el mismo problema que el mundo web llamaba IDOR.

Es el hallazgo crítico más frecuente en nuestros pentests de API, y no por descuido de los equipos: nace de una decisión de arquitectura razonable (identificar objetos por ID) combinada con una comprobación que parece existir pero está en el lugar equivocado.

Por qué los escáneres no lo encuentran

Un escáner no sabe qué es "tuyo" y qué es "de otro". Ve dos respuestas 200 con JSON válido y sigue. Detectar BOLA exige dos contextos autenticados, entender el modelo de datos y hacer la pregunta correcta: ¿este usuario debería ver esto? Eso es trabajo manual, o tooling propio que carga el contexto de identidad, que es como lo hacemos.

Las variantes que más encontramos

1. ID secuencial o predecible

El caso clásico. Entero incremental, timestamp, DNI, correo en la URL. Enumerable en minutos.

2. El UUID que se filtra en otro sitio

"Usamos UUID v4, no se puede adivinar." Cierto, pero el UUID del cliente aparece en la respuesta de /api/orders, en el enlace del correo de confirmación, en el HTML de la página pública, en el webhook al partner. La oscuridad no es autorización.

3. Autorización solo en GET, no en PATCH

El equipo protegió la lectura. La escritura (PUT, PATCH, DELETE) usa otro handler, escrito después, sin el mismo middleware. Leer es malo; cambiar la dirección de entrega o la cuenta de cobro de otro cliente es peor.

4. El ID en el cuerpo, no en la URL

POST /api/transfer con {"from_account": "...", "to_account": "..."}. El backend confía en el from_account enviado por el cliente en lugar de derivarlo de la sesión. Lo hemos visto en una fintech regulada.

5. GraphQL y batching

query { user(id: 48212) { email, documents { url } } }. Los resolvers anidados suelen comprobar la autorización solo en el nodo raíz. Con alias y batching se enumeran miles de objetos en una sola petición.

6. Endpoints "internos" o legados

/api/v1/ siguió activo después de /v2/. O /internal/export sin autenticación porque "solo lo llama el frontend de admin". El frontend no es una frontera de seguridad.

7. Autorización por tenant, no por objeto

En un SaaS multi-tenant se comprueba si el usuario pertenece a la empresa X, pero no si el objeto pertenece a la empresa X. Un usuario de X lee objetos de Y cambiando el ID.

Cómo lo encadenamos: del 200 al impacto de negocio

Un BOLA aislado es grave. Lo que el informe debe mostrar es el camino hasta el daño real:

  1. Lectura de la factura de otro cliente → datos personales (RGPD).
  2. La factura incluye el UUID del documento → descarga del PDF con datos bancarios.
  3. El mismo patrón en PATCH /api/users/{id} → cambio del correo de recuperación → account takeover.
  4. Con la cuenta, acceso a POST /api/payouts → movimiento de dinero.

Cada paso es una petición documentada con request/response en el informe. La dirección no necesita entender IDOR; necesita entender "cualquier usuario podía cambiar la cuenta de cobro de cualquier otro".

Qué lo corrige de verdad

  • Deriva la identidad de la sesión, nunca del input. El from_account viene del token, no del JSON.
  • Autoriza sobre el objeto, en cada operación. Un único punto (policy, middleware, repositorio) que responde "¿puede este sujeto hacer esta acción sobre este objeto?", y todos los handlers pasan por él, incluidos los resolvers de GraphQL y los jobs internos.
  • Acota en la consulta, no después. WHERE id = ? AND owner_id = ? en la base de datos. Filtrar en memoria tras traerlo todo es una invitación a la fuga.
  • Tests automáticos de autorización en CI: para cada endpoint, dos usuarios, un objeto de cada uno, aserción de 403/404. Evita regresiones; no sustituye al pentest, que encuentra los caminos para los que nadie escribió un test.
  • Rate limit y alertas ante patrones de enumeración: no lo impide, pero acorta la ventana.
  • Retira el legado. Las versiones antiguas de la API necesitan fecha de apagado, no olvido.

Qué pedir en tu próximo pentest de API

Exige que la prueba se haga con al menos dos usuarios por rol y con el OpenAPI/Swagger a mano (grey-box). Pide cobertura explícita de BOLA, BFLA (autorización a nivel de función, API5), mass assignment (API3) y de los endpoints no documentados que encuentre el recon. Y pide el encadenamiento: un informe con 30 IDOR aislados vale menos que uno con el camino hasta el takeover.

Cómo lo prueba Pentest Machine

Nuestro API Pentest se guía por el OWASP API Top 10 y por la especificación de tu API, con múltiples identidades por rol y tooling propio que carga contexto de autorización durante la enumeración, lo que permite probar miles de combinaciones objeto × usuario × operación sin perder el análisis manual de las cadenas. Cada hallazgo incluye PoC reproducible, impacto de negocio y el patrón de corrección, y el retest está incluido.