BBLABSBBLABS
LaboratoriosAprender desde 0FuncionesPreciosTestimoniosDetrás de BBLABS
  1. Inicio
  2. Labs
  3. Playground de Client-Side Path Traversal
Insane1h

Playground de Client-Side Path Traversal

Por @gorka

Playground en un solo puerto con 3 mini-apps independientes. C1 (fácil) introduce el concepto base: la SPA concatena un parámetro de URL en un `fetch` sin sanitizar y un `../` lo redirige a otro endpoint. C2 (medio) añade un bot admin Puppeteer y un bypass de validación con URL encoding + `decodeURIComponent` tardío. C3 (difícil) encadena un allowlist con `%`, un path collapse `..` y el truco de `?` para mantener intacto el body POST mientras se redirige el fetch a `/api/audit/flag-export` con cookie auditor. La flag se entrega únicamente cuando la cadena del C3 se completa via bot. Estado en memoria, gating progresivo server-side, código vulnerable accesible al alumno desde el propio navegador

Este hackeo se reportó sin recompensa

859 visitas8 completadosActualizado sept 2026
Iniciar sesión para empezar

Aprende a encontrar este bug

Un hackeo real, 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

MA1
@maxioliveraHace 5 días
flyingwhales2
@flyingwhalesago 2026
zynap3
@zynapjul 2026
XL
@xl4njul 2026
cgomezpobbb
@cgomezpobbbjul 2026
AL
@alex.burja.2000jul 2026
pl4nkton
@pl4nktonjun 2026
w4tchw0lf
@w4tchw0lfjun 2026

Objetivos

1
Identificar que el patrón `fetch('/api/x/' + userInput)`
2
Resolver el Challenge 1
3
Resolver el Challenge 2
4
Resolver el Challenge 3
5
Entregar la Flag

Información

Dificultad
Insane
Duración
1h
Bounty
Sin recompensa
Completados
8
Creador
gorka@gorka
Actualizado
sept 2026

Descarga el entorno

Reprodúcelo y encuentra el bug tú mismo

Crear cuenta

Herramientas

Burp SuiteNavegador

Prerequisitos

  • Comprensión de URLS
  • Familiaridad con fetch()

Tags

Path Traversal

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.

CSPT-VULN es un playground autocontenido. Un solo proceso Node.js + Express
en el puerto 1301 sirve 3 sub-aplicaciones independientes bajo
/challenge-1, /challenge-2, /challenge-3, más un hub en / que muestra
el estado de las 3 (🔒 / 🔓 / ✅) y un writeup interactivo en /writeup.

El bug es siempre el mismo: el frontend construye una URL para un
fetch(...) concatenando un valor que viene de la query string sin
sanitizar. El alumno controla ese valor; el navegador normaliza la ruta;
la request llega al servidor apuntando a otro endpoint que ofrece más
privilegios o más información.

Capa 1 — La Biblioteca de Atlas (CWE-22 / Easy)

Catálogo de libros por categoría. Vista vulnerable (challenge-1.js):

const categoria = new URLSearchParams(location.search).get('categoria') || 'ficcion';
fetch('/api/v1/library/' + categoria);

Payload: ?categoria=../admin-vault. Browser construye
/api/v1/library/../admin-vault, normaliza a /api/v1/admin-vault. El
endpoint sensible devuelve { unlock: true } y, como la sesión es la del
usuario, el backend marca c1 = true. Sin bot — el fetch lo hace el propio
navegador del alumno.

Capa 2 — Soporte Atlas (CWE-22 + CWE-602 / Medium)

Sistema de tickets. La página /challenge-2/view carga challenge-2.js,
que lee ?ticket= en su forma cruda con un regex, valida que no haya
/ ni .. literales, decodifica y concatena:

const ticketRaw = location.search.match(/[?&]ticket=([^&]+)/)?.[1];
if (ticketRaw.indexOf('/') !== -1 || ticketRaw.indexOf('..') !== -1) reject();
const ticketId = decodeURIComponent(ticketRaw);
fetch('/api/tickets/' + ticketId);

Payload encoded: ticket=%2E%2E%2Fadmin%2Fusers%2Fusr_ATK_xxxx%2Fpromote.
La validación pasa (no hay / ni .. literales en el raw). El decode
produce ../admin/users/usr_ATK_xxxx/promote. Browser normaliza,
/api/tickets/../admin/users/usr_ATK_xxxx/promote →
/api/admin/users/usr_ATK_xxxx/promote. Como el bot admin es quien abrió
la URL, su cookie autoriza la promoción. El backend resuelve "qué atacante
disparó este bot" via botManager.getActiveJobOwner() y marca su c2.

Pista pública: GET /api/admin/_self/audit-policy lista los endpoints
documentados para el rol support-bot — entre ellos
/api/admin/users/:userId/promote.

Capa 3 — Auditoría Cumplimiento (CWE-22 + CWE-602 + CWE-918 / Hard)

Panel interno donde el bot auditor revisa casos con un operator_token
firmado por boot. Vista vulnerable (challenge-3.js):

const refRaw = location.search.match(/[?&]ref=([^&]+)/)?.[1];
if (!/^[A-Za-z0-9._%\-]+$/.test(refRaw)) reject();  // allowlist permite %
const ref = decodeURIComponent(refRaw);
fetch('/api/audit/case/' + ref + '/review', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    operator_token: window.__AUDITOR_TOKEN,  // inyectado solo si cookie = bot auditor
    decision: 'approve',
    notes: 'Reviewed via dashboard',
  }),
});

Payload: ref=%2E%2E%2Fflag-export%3F. Allowlist pasa (todos los chars son
alfanuméricos + . + %). Decode → ../flag-export?. Fetch URL string =
/api/audit/case/../flag-export?/review. El URL parser del navegador:

  1. Detecta ? → cambia a query state. Path queda en
    /api/audit/case/../flag-export.
  2. Normalización: .. consume case/. Path final: /api/audit/flag-export.
  3. Query final: /review.

El POST viaja a /api/audit/flag-export con el body intacto (incluyendo el
operator_token que el servidor inyectó en window.__AUDITOR_TOKEN solo
porque la cookie es la del bot auditor). El backend valida, devuelve la
flag, y la acredita al ownerSessionId del job activo en botManager.

Sesiones, bots y crédito

  • Sesión humana: cookie cspt_sid = <random 16 bytes>, emitida en el
    primer GET / o /api/progress.
  • Sesión bot admin: cspt_sid = bot-admin-<random>, inyectada por
    Puppeteer antes de cada navegación del bot admin.
  • Sesión bot auditor: cspt_sid = bot-auditor-<random>, equivalente.
  • Cuando llega una petición con cookie de bot, el handler consulta
    botManager.getActiveJobOwner() para saber qué sesión humana disparó
    al bot — y le acredita el resultado (promotion, flag).

Por qué este patrón es real

Reportes públicos relevantes que inspiran el lab:

  • Slack Workspace Takeover via CSPT (HackerOne disclosed): el cliente
    construía paths con input del usuario y un admin bot seguía links
    internos.
  • GitLab CSPT (CVE-2021-22165): construcción client-side de paths en
    un widget de notificaciones.
  • Medusa CSPT (PortSwigger research, 2024): patrón de validación sobre
    forma raw + decode tardío en plataformas e-commerce.

El lab condensa el patrón en tres formas progresivas y entrena al alumno
para reconocerlo en código JS real.

Mitigaciones (cubiertas en el writeup)

  • Validar después de decodificar, no antes.
  • Allowlist sin % si el segmento no necesita escapes.
  • Usar new URL(...) con encodeURIComponent(segment) por cada segmento,
    no concatenación de strings.
  • Aislar tokens privilegiados: window.__AUDITOR_TOKEN no debería estar
    expuesto en una página que acepta input de query string.
  • Defense in depth server-side: el endpoint /api/audit/flag-export
    podría exigir un HMAC del path completo + body, validable solo si el path
    es el esperado.
  • Allowlist del bot por path, no solo por host: rechazar URLs que
    contengan caracteres sensibles como .., %2E%2E, ? en sufijo.

Flag

FLAG{a29ee59e1fd23ecc5a4d2d53880a6866}

Verificación:

echo -n 'cspt-vuln::client-side-path-traversal::final-2026' | md5sum
# a29ee59e1fd23ecc5a4d2d53880a6866

O simplemente bash flag/verify.sh.

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.