Writeup — Pulsar Social
Las salidas son reales, capturadas contra el contenedor desplegado (localhost:8585). Varios retos se resuelven leyendo el código igual que en módulos anteriores, porque su efecto completo solo se ve en un navegador real.
Reto 1 — XSS reflejado
XSS★★
El buscador vuelca el término de búsqueda en la página sin escaparlo:
$ curl -s -G localhost:8585/buscar --data-urlencode '' | grep -o "GO{[^}]*}" (usa el parámetro q) GO{lo_reflejado_tambien_se_ejecuta}
Flag: GO{lo_reflejado_tambien_se_ejecuta}
Lección: en un navegador real, ese mismo enlace ejecutaría el script en cuanto la víctima lo abriera.
Reto 2 — XSS almacenado
XSS★★
La biografía se guarda tal cual y se muestra sin escapar a cualquiera que visite el perfil:
$ curl -s -X POST --data-urlencode 'bio=' localhost:8585/mi-perfil
(redirige a /perfil/nova)$ curl -s localhost:8585/perfil/nova | grep -o "GO{[^}]*}" GO{lo_guardado_se_ejecuta_para_todos}
Flag: GO{lo_guardado_se_ejecuta_para_todos}
Lección: a diferencia del reflejado, aquí el script afecta a cualquiera que visite ese perfil, no solo a quien lo escribió.
Reto 3 — XSS basado en DOM
XSS★★
La página de bienvenida carga hash.js, que lee location.hash y lo inserta con innerHTML. Ese dato nunca pasa por el servidor, así que se detecta leyendo el propio script:
$ curl -s localhost:8585/static/hash.js | grep -o "GO{[^}]*}" GO{el_hash_tambien_es_una_entrada}
Flag: GO{el_hash_tambien_es_una_entrada}
Lección: abrir /bienvenida#<img src=x onerror=alert(1)> en un navegador real ejecutaría el script, sin que ninguna petición al servidor lo revele.
Reto 4 — CSRF
Peticiones falsificadas★★
Seguir a otro usuario es una petición GET sin ningún token que demuestre que se originó en la propia aplicación:
$ curl -s 'localhost:8585/seguir?id=admin' | grep -o "GO{[^}]*}" GO{ninguna_prueba_de_que_fuiste_tu}
Flag: GO{ninguna_prueba_de_que_fuiste_tu}
Lección: en un ataque real, esta misma URL se incrustaría en una imagen de un sitio externo (<img src="…/seguir?id=admin">); el navegador de la víctima la cargaría con su sesión ya iniciada.
Reto 5 — Clickjacking
Cabeceras de respuesta★★
El panel no envía ni X-Frame-Options ni una Content-Security-Policy con frame-ancestors:
$ curl -si localhost:8585/panel | grep -i 'x-frame\|content-security'
(sin resultados)Al comprobar cómo respondería si se cargara dentro de un iframe:
$ curl -s -H "Sec-Fetch-Dest: iframe" localhost:8585/panel | grep -o "GO{[^}]*}" GO{cualquiera_te_puede_enmarcar}
Flag: GO{cualquiera_te_puede_enmarcar}
Lección: sin esas cabeceras, cualquier sitio puede incrustar el panel en un iframe transparente y engañar a la víctima para que haga clic donde no cree que hace clic.
Reto 6 — Redirección abierta
Redirecciones★★
El parámetro next se usa tal cual como destino, sin comprobar que sea una ruta propia de la aplicación:
$ curl -si 'localhost:8585/ir?next=https://ejemplo-externo.test/phish' | grep -iE 'location|x-pista' Location: https://ejemplo-externo.test/phish X-Pista: GO{un_enlace_de_confianza_que_no_lo_es}
Flag: GO{un_enlace_de_confianza_que_no_lo_es}
Lección: un enlace que empieza con el dominio legítimo (pulsar-social.example/ir?next=...) puede acabar en cualquier otro sitio, lo que lo hace muy útil para el phishing.
Reto 7 — Cookie sin protección
Cookies★★
La cookie de sesión se establece sin la marca HttpOnly:
$ curl -si localhost:8585/ | grep -i set-cookie
Set-Cookie: sesion=dXN1YXJpbz1pbnZpdGFkbztHT3tsYV9jb29raWVfdmlhamFfc2luX3Byb3RlY2Npb259; Path=/$ echo dXN1YXJpbz1pbnZpdGFkbztHT3tsYV9jb29raWVfdmlhamFfc2luX3Byb3RlY2Npb259 | base64 -d usuario=invitado;GO{la_cookie_viaja_sin_proteccion}
Flag: GO{la_cookie_viaja_sin_proteccion}
Lección: sin HttpOnly, cualquier XSS en la misma página (como los retos 1 o 2) podría leer esta cookie con document.cookie y robarla.
Reto 8 — postMessage sin validar origen
Comunicación entre ventanas★★★
El widget de chat acepta mensajes de cualquier origen y los inserta directamente en la página:
$ curl -s localhost:8585/static/chat.js | grep -o "GO{[^}]*}" GO{cualquier_origen_puede_escribir}
Flag: GO{cualquier_origen_puede_escribir}
Lección: sin comprobar event.origin, cualquier página que incruste este widget (o que se comunique con una ventana que lo tenga abierto) puede inyectar HTML o JavaScript en él.
Resumen
| # | Vulnerabilidad | Dónde se detecta |
|---|---|---|
| 1 | XSS reflejado | Respuesta del servidor |
| 2 | XSS almacenado | Respuesta del servidor (persistente) |
| 3 | XSS basado en DOM | Código JavaScript del cliente |
| 4 | CSRF | Ausencia de token en una acción |
| 5 | Clickjacking | Cabeceras de respuesta ausentes |
| 6 | Redirección abierta | Cabecera Location |
| 7 | Cookie sin protección | Cabecera Set-Cookie |
| 8 | postMessage sin validar | Código JavaScript del cliente |