BBLABSBBLABS
LaboratoriosAprender desde 0FuncionesPreciosTestimoniosDetrás de BBLABS
  1. Inicio
  2. Labs
  3. IDOR horizontal en la API de contenidos de un editor de notebooks colaborativos
Media55 min

IDOR horizontal en la API de contenidos de un editor de notebooks colaborativos

Por @gorka

La API de contenidos de notebooks localiza el workspace por el clusterId que manda el cliente sin comprobar que sea el tuyo: opera y lee el árbol de notebooks de otro miembro de la organización.

Este hackeo se reportó sin recompensa

361 visitas8 completadosActualizado nov 2026
Iniciar sesión para empezar

Aprende a encontrar este bug

Un hackeo real de HackerOne, reproducido para que lo practiques.

Crea tu cuenta y practica bugs reales que se pagaron. Descarga el entorno, encuéntralo y aprende la técnica exacta — tu camino a tu primer bounty.

650

hunters entrenando

50

labs de hackeos reales

380

completaciones

$12.000

pagados por estos bugs

40 flags capturadas esta semana
Crear cuenta
Ya tengo cuenta

Acceso a todos los labs · sin permanencia · cancela cuando quieras

Hackers que lo han resuelto· 8

espejoramirezhugo1
@espejoramirezhugoHace 12 días
emperador2
@emperadorHace 19 días
kyzk33
@kyzk3Hace 28 días
kaneki
@kanekiHace 29 días
benjaminnocervigni
@benjaminnocervigniHace 29 días
sharat chandra
@sharat chandraoct 2026
3nk1
@3nk1oct 2026
r00thor
@r00thorsept 2026

Objetivos

1
Descubrir en el propio editor qué parámetro identifica tu workspace ante la API de contenidos.
2
Encontrar el clusterId del owner de la organización en la superficie de colaboración del equipo.
3
Contrastar el 403 de la gestión de clusters frente al 200 de la API de contenidos con el mismo clusterId ajeno.
4
Confirmar con una escritura ajena (create) que la API de contenidos no comprueba propiedad del cluster.
5
Listar y leer el árbol de notebooks del cluster del owner mediante el mismo fallo de autorización.
6
Localizar el notebook de secretos de conexión del owner y extraer el root_api_token (la flag).
7
Descartar los 4 decoys del lab por razonamiento acotado, no por fuerza bruta ni adivinando UUIDs.

Información

Plataforma
HackerOne
Dificultad
Media
Duración
55 min
Bounty
Sin recompensa
Completados
8
Creador
gorka@gorka
Actualizado
nov 2026

Descarga el entorno

Reprodúcelo y encuentra el bug tú mismo

Crear cuenta

Herramientas

curlNavegadorDevToolsPython requests

Prerequisitos

  • HTTP y APIs REST
  • Uso básico de curl
  • Lectura de JSON

Tags

IDORBACAPI Abuse

Logro que recibirás

Cuando resuelvas este lab desbloqueas este logro compartible

Cuando resuelvas este lab desbloqueas este logro compartible

Writeups de la comunidad

Gratis · sin cuenta

La checklist que repaso en cada objetivo nuevo

47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.

Te das de baja en un clic, desde cualquier correo.

Contenido del lab

Escenario

Kernelia es un SaaS de notebooks de datos colaborativos: los equipos de analítica escriben notebooks en SQL y Python contra sus almacenes de datos, y cada miembro de una organización tiene un workspace personal respaldado por un cluster de cómputo dedicado, identificado por un clusterId (UUID). Los notebooks y carpetas de cada workspace se gestionan a través de una API de contenidos que el propio editor consume para listar, leer y crear ficheros dentro del cluster sobre el que estás trabajando; y una página de colaboración de equipo donde cada miembro puede ver el workspace de los demás, para referenciarse y coordinar el trabajo.

Eres Sasha Morel, analista de baja privilegia recién incorporada al equipo de datos de Kernelia. Tienes tu propia cuenta, tu propio workspace y acceso legítimo a la plataforma como cualquier compañero — no hay nada roto en tu login. Tu misión como pentester: demostrar que, estando autenticada simplemente como una miembro más del equipo, puedes alcanzar el workspace de Dana Voss, la owner/administradora de la organización, y desde ahí extraer el secreto de conexión al almacén de producción que su notebook privado guarda.

Qué tienes

  • Cuenta demo: attacker@kernelia.local / password123 — Sasha Morel, analista y miembro real de la organización, con su propio cluster (morel-sandbox) y notebooks de ejemplo. Es tu punto de partida legítimo, no la vulnerabilidad.
  • El editor de notebooks de tu propio workspace y la página "Team Workspaces", donde el equipo colabora y ve el estado de cada workspace.

Guía paso a paso

  1. Recon + aprende el shape (baseline autorizado). Login con attacker@kernelia.local. Aterrizas en tu workspace; GET /api/auth/me te devuelve tu propio clusterId, y tu editor dispara GET /api/v1/notebooks/contents?clusterId=<tuyo>&path=/, que pinta tu árbol de notebooks. Operar sobre tu propio cluster funciona (estás autorizado) — pero acabas de aprender algo importante: la API de contenidos se direcciona por un clusterId que TÚ mandas como parámetro, no algo que el servidor deriva de tu sesión.

  2. Enumeración cross-org del clusterId del owner (el signpost crítico). La página "Team Workspaces" dispara GET /api/v1/org/workspaces: una funcionalidad de colaboración legítima que lista a cada miembro del equipo con su rol, el nombre de su workspace, su clusterId, el número de notebooks y su última actividad. La fila de Dana Voss (role: "owner") expone su clusterId en claro — sin adivinar ningún UUID: está ahí, pensado para que los compañeros se referencien unos a otros.

  3. El contraste diagnóstico y el IDOR. Antes de asumir que "todo está abierto", prueba a cambiar los ajustes del cluster de Dana: POST /api/v1/clusters/<clusterId de Dana>/settings → 403 not_cluster_owner — la propiedad del cluster SÍ se comprueba ahí. Repite la misma idea contra la API de contenidos con ese mismo clusterId ajeno: GET /api/v1/notebooks/contents?clusterId=<Dana>&path=/ → 200 con el árbol completo de Dana. Mismo identificador, misma sesión, dos comportamientos distintos — la API de contenidos es la anómala. Confírmalo escribiendo: POST /api/v1/notebooks/contents con {clusterId:<Dana>, path:"/poc-...", type:"directory", name:"..."} → 201, y la carpeta aparece de verdad al re-listar el cluster de Dana (o en el feed GET /api/v1/org/activity).

  4. Escalada a la flag. El árbol de Dana incluye un notebook llamado connection-secrets.ipynb, junto a otros de producción (prod-warehouse-etl.ipynb, org-billing.ipynb, carpeta ops/). El nombre no deja lugar a dudas. Léelo con el mismo fallo: GET /api/v1/notebooks/contents?clusterId=<Dana>&path=/connection-secrets.ipynb → su celda de configuración trae root_api_token = FLAG{...}, rodeado de campos de acompañamiento ficticios (service_connection_string, warehouse_password, signing_secret). Ese root_api_token es la flag.

Canales de feedback (no vuelas a ciegas)

  • Tu propio árbol de notebooks renderiza en 200 desde el paso 1 — confirmas que la API responde y que el clusterId es un parámetro tuyo, manipulable.
  • GET /api/v1/org/workspaces responde 200 con el clusterId de cada miembro en claro, incluido el de Dana — no hace falta enumerar nada.
  • El contraste 403 (cluster-settings sobre el cluster ajeno) frente a 200 (contenidos con el mismo clusterId ajeno) es observable en cada request, sin ambigüedad.
  • El create ajeno responde 201, y la carpeta recién creada aparece al re-listar el cluster de Dana y en GET /api/v1/org/activity — no hace falta confiar a ciegas en el código de estado.
  • La lectura de connection-secrets.ipynb responde 200 con la celda de configuración en texto plano, root_api_token incluido.

Decoys (descártalos por razonamiento acotado)

  • Decoy A — tu propio cluster (baseline autorizado). La API de contenidos funciona con normalidad sobre tu clusterId: es lo esperado, estás autorizada sobre tu propio objeto. Enseña el shape sin ser el bug. Lo interesante es que la MISMA llamada funcione también con un clusterId ajeno.
  • Decoy B — la consola de clusters SÍ enforca propiedad. POST /api/v1/clusters/:clusterId/settings y GET /api/v1/clusters/:clusterId llaman assertClusterAccess → 403 not_cluster_owner sobre el cluster de Dana en una sola prueba. Prueba que la autorización a nivel de objeto existe en la plataforma y funciona; aísla que el fallo real vive en la API de contenidos, no en "toda la app".
  • Decoy C — el endpoint de administración de la org es role-gated. GET /api/v1/org/admin/audit y POST /api/v1/org/admin/members exigen rol admin/owner → 403 requires_org_admin para tu cuenta member en una sola prueba. Prueba que el RBAC vertical funciona; el bug NO es de rol — es horizontal, entre pares autenticados, no necesitas ser admin.
  • Decoy D — el token placeholder de tu propio workspace. Tu demo-credentials.ipynb trae un demo_api_token de aspecto jugoso, pero es un placeholder: su md5 no coincide con la flag en cuanto lo compruebas. Te obliga a concluir que el secreto real vive en el cluster de Dana, no en el tuyo.

La falla del laboratorio

Clase: CWE-639 (Authorization Bypass Through User-Controlled Key), con CWE-284 (Improper Access Control), CWE-863 (Incorrect Authorization) y CWE-200/CWE-522 (Exposure of Sensitive Information / Insufficiently Protected Credentials, por el objeto alcanzado) como CWEs relacionadas. OWASP A01:2021 Broken Access Control.

La ruta de contenidos de notebooks (GET/POST /api/v1/notebooks/contents) sí exige sesión (requireAuth): cualquier miembro autenticado de la organización pasa el filtro. El defecto no es de autenticación — es que, antes de resolver y operar sobre el objeto identificado por el clusterId recibido en la query o el body, el handler nunca invoca assertClusterAccess(req.user, clusterId). Esa función existe en el codebase, está bien implementada (compara al propietario real del cluster contra el usuario de la sesión) y la usan correctamente las rutas hermanas de gestión de clusters — simplemente no se llama desde la ruta de contenidos. No hay ningún if invertido que depurar ni una comparación rota: es una llamada ausente en un único handler, mientras su hermano la hace bien. Por eso el defecto es invisible a un grep de patrones sospechosos: el código que sí existe es funcionalmente correcto.

Este es el motivo por el que "requiere login" no es sinónimo de "está autorizado". La autenticación responde a "¿quién eres?"; el control de rol (RBAC, aquí correctamente aplicado en las rutas de administración de la org) responde a "¿qué clase de acciones te permite tu rol?"; pero ninguna de las dos responde a la pregunta que de verdad importa en un sistema multi-tenant con identificadores de objeto suministrados por el cliente: "¿este objeto concreto es tuyo?". Esa tercera comprobación —autorización a nivel de objeto— es ortogonal a las otras dos, y es precisamente la que falta aquí. El resultado es un IDOR horizontal: no hace falta escalar de member a admin, basta con ser un compañero cualquiera y conocer (o descubrir legítimamente) el clusterId de otro.

Lo que habría prevenido el bug es enunciable en una línea de código: invocar assertClusterAccess(req.user, clusterId) antes de que la API de contenidos toque el almacenamiento — exactamente la misma llamada que ya protege la consola de gestión de clusters. Alternativamente, derivar el clusterId de la sesión del usuario en vez de aceptar el que manda el cliente eliminaría la superficie de ataque por completo para el caso de "mi propio workspace", aunque limitaría funcionalidades legítimas de colaboración que sí necesitan aceptar un clusterId ajeno de forma controlada (con su propio check de acceso).

Lección: que un endpoint "requiera sesión" no significa que esté autorizado a nivel de objeto — cada identificador de recurso que el cliente puede suministrar (un clusterId, un orderId, un documentId…) necesita su propio check de propiedad/acceso contra el usuario autenticado, y ese check hay que verificarlo comparando el comportamiento de rutas hermanas de la misma familia de API, no asumiendo que "si pide login, ya está protegido".

Laboratorio basado en la reproducción anonimizada (empresa ficticia Kernelia) de una técnica real de bug bounty sobre una plataforma de notebooks de datos cuya API de contenidos enrutaba operaciones de lectura y escritura por un identificador de cluster controlado por el cliente, sin comprobar la propiedad de ese cluster antes de operar.

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.

BBLABS LogoBBLABS

Laboratorios de hacking basados en hackeos reales. Aprende practicando sobre entornos vulnerables, con la resolución explicada paso a paso.

Producto

  • Funcionalidades
  • Precios
  • Labs
  • Aprender desde 0
  • Blog
  • Detrás de BBLABS

Legal

  • Términos
  • Privacidad
  • Cookies
  • Entrar
  • Crear cuenta

Para cualquier duda, bug, soporte o sugerencia puedes escribir a team@bblabs.es

© 2026 BBLABS. Todos los derechos reservados.