← Todas las guías
ExplotaciónLaboratorio + defensaIntermedio32 min

Ataques XSS: del dato no confiable al navegador

Distingue XSS reflejado, almacenado y basado en DOM; sigue una fuente hasta un sink inseguro, demuestra el fallo en una página local, observa BeEF con un navegador propio y corrige la causa con sinks seguros, codificación, sanitización y defensas en profundidad.

1 · De dato no confiable a código ejecutable

Un Cross-Site Scripting (XSS) aparece cuando una aplicación lleva datos no confiables hasta un punto donde el navegador los interpreta como HTML o JavaScript. El valor puede proceder de un formulario, una URL, una cabecera, una base de datos o un mensaje entre ventanas. El problema no es que exista una entrada, sino que llegue sin la protección adecuada a un sink capaz de crear código, comoinnerHTML.

El navegador no distingue ese código del escrito por la aplicación y lo ejecuta bajo el mismo origen. Por eso el impacto puede incluir modificación de la interfaz, lectura de datos accesibles a JavaScript o peticiones a la API con la sesión activa. La cadena que debes buscar es fuente → transformaciones → sink; encontrar una cadena no demuestra por sí solo todo el impacto posible.

2 · Reflejado, almacenado y basado en DOM

Reflejado

La petición contiene el dato y el servidor lo devuelve de forma insegura en esa misma respuesta. Suele requerir que la persona abra una URL preparada.

Almacenado

El dato queda guardado —por ejemplo en un comentario o perfil— y se sirve después. Puede alcanzar a varias personas sin que cada una reciba un enlace distinto.

Basado en DOM

El JavaScript del cliente lee una fuente controlable y la entrega a un sink inseguro. El valor ejecutado puede no aparecer en la respuesta HTTP.

«Reflejado» y «almacenado» describen normalmente cómo llega o persiste el dato en el servidor. «Basado en DOM» describe dónde se produce la transformación peligrosa: en el cliente. En una aplicación real las categorías pueden solaparse; para corregir el fallo importa más reconstruir el flujo exacto que acertar la etiqueta.

3 · Laboratorio local: reconocer una fuente y un sink

Crea un archivo llamado xss-lab.html con el contenido siguiente. La entrada del formulario es la fuente y innerHTML es el sink inseguro. No contiene cuentas, cookies ni comunicación de red.

xss-lab.html · versión vulnerable
<!doctype html><meta charset="utf-8"><title>Laboratorio XSS local</title><label>Dato <input id="value"></label><button id="show">Mostrar</button><output id="result"></output><script> const input = document.querySelector("#value"); const result = document.querySelector("#result"); document.querySelector("#show").addEventListener("click", () => { result.innerHTML = input.value; });</script>

Desde la carpeta que contiene el archivo, sirve la página únicamente en la interfaz local:

Servidor temporal · Linux, macOS o Windows con Python 3
python3 -m http.server 8080 --bind 127.0.0.1# En Windows, si python3 no existe, prueba: py -m http.server 8080 --bind 127.0.0.1

Abre http://127.0.0.1:8080/xss-lab.html, pega el marcador siguiente y pulsa «Mostrar». La pestaña cambiará su título a XSS: es una prueba visible y no envía información fuera del equipo.

Marcador inocuo para este laboratorio
<img src=x onerror='document.title=String.fromCharCode(88,83,83)'>

El elemento img no puede cargar x, dispara su manejador de error y cambia el título. Esto también explica por qué borrar únicamente la palabra script no corrige XSS: el navegador dispone de varios contextos ejecutables. H1rd y PayloadsAllTheThings catalogan muchas variantes; úsalas para entender parsers y contextos en laboratorios autorizados, no como una lista para probar indiscriminadamente.

4 · Demostrar impacto sin recolectar datos

Una prueba responsable separa lo observado de lo inferido. El cambio de título demuestra que un dato controlable alcanzó un sink ejecutable en ese origen. No demuestra robo de sesión, persistencia, acceso a otras cuentas ni ejecución fuera del navegador. Documenta la fuente, el sink, la URL local, el navegador y el resultado antes de corregir.

Registro mínimo de evidencia
Observado: el título cambió de «Laboratorio XSS local» a «XSS»Causa: input.value alcanzó result.innerHTML sin codificación ni sanitizaciónNo probado: acceso a cookies, cuentas, credenciales o sistemas externos

En una aplicación con sesión, un XSS puede actuar desde el origen legítimo: modificar el DOM, leer datos que JavaScript pueda ver o invocar funciones permitidas al usuario. HttpOnly evita que JavaScript lea directamente una cookie, pero no elimina el XSS ni impide por sí solo peticiones autenticadas desde la página. Por eso los atributos de cookie reducen impacto; la corrección raíz sigue estando en el flujo de datos.

5 · BeEF en un navegador propio y aislado

BeEF es un framework de evaluación centrado en el navegador. Su hook.js establece comunicación con el servidor BeEF mientras la página puede alcanzarlo. El ejercicio siguiente no explota un sitio: incluye el hook deliberadamente en una segunda página local y comprueba que tu propio navegador aparece en el panel.

Instalación y verificación · Kali Linux
sudo apt updatesudo apt install -y beef-xsscommand -v beef-xss-start

sudo actualiza el índice e instala un paquete del sistema. El último comando debe imprimir la ruta del ejecutable. Inícialo y define una contraseña distinta de beef cuando Kali la solicite;beef-xss sin el sufijo -start está obsoleto.

Terminal
sudo beef-xss-start

Lee la salida del terminal: debe mostrar la URL del panel y la del hook. Con la configuración predeterminada son http://127.0.0.1:3000/ui/panel y http://127.0.0.1:3000/hook.js. Si tu salida usa otro puerto o ruta, utiliza esos valores. No expongas el servicio a Internet para este ejercicio.

Crea beef-lab.html en la misma carpeta del laboratorio:

beef-lab.html
<!doctype html><meta charset="utf-8"><title>BeEF · laboratorio local</title><h1>Navegador propio de laboratorio</h1><script src="http://127.0.0.1:3000/hook.js"></script>

Si detuviste el servidor Python, vuelve a iniciarlo con el comando de la sección 3. Abrehttp://127.0.0.1:8080/beef-lab.html y entra al panel en otra pestaña. La aparición de tu navegador en Online Browsers, junto con los metadatos básicos que muestra la interfaz, completa la práctica.

Al terminar, cierra las dos páginas de laboratorio, pulsa Ctrl+C en el servidor Python y detén BeEF. Si lo instalaste solo para esta práctica, puedes retirar el paquete; elimina además únicamente los dos archivos HTML que creaste.

Parada y limpieza opcional · Kali Linux
sudo beef-xss-stopsudo apt remove beef-xss

6 · Corregir la causa y limitar el impacto

En xss-lab.html, sustituye result.innerHTML = input.value por la asignación siguiente. Recarga, repite el marcador y comprueba que ahora aparece como texto y el título no cambia.

Sink seguro para texto
result.textContent = input.value;
  1. Renderiza texto como texto. Prefiere textContent, createTextNode y el autoescapado del framework. Audita escapes como dangerouslySetInnerHTML.
  2. Codifica según el contexto. HTML, atributos, URL, CSS y JavaScript tienen parsers distintos. Evita colocar datos no confiables en manejadores de eventos, código inline o nombres dinámicos de etiquetas.
  3. Sanitiza solo cuando necesitas aceptar HTML. Usa una biblioteca mantenida como DOMPurify, con una política de etiquetas y atributos permitidos, y actualízala. No modifiques después la cadena ya sanitizada.
  4. Añade capas de contención. Una CSP estricta con nonce o hash y Trusted Types puede reducir rutas de ejecución en navegadores compatibles. Son defensa en profundidad, no sustitutos del sink seguro.
  5. Limita la sesión. Marca las cookies sensibles como HttpOnly, Secure y con un SameSite apropiado; aplica autorización servidor-side en cada acción y evita secretos duraderos en localStorage.

No basta validar la entrada

Una expresión regular global desconoce el contexto de salida y el parser del navegador. Valida formato y codifica o sanitiza al renderizar.

No basta un WAF

Puede frenar cadenas conocidas, pero no corrige sinks inseguros ni ve necesariamente un flujo que ocurre solo en el DOM.

No basta CSP

Una política permisiva o mal configurada conserva vías de ejecución. Primero elimina el flujo vulnerable y después endurece la política.

Checklist antes de cerrar el laboratorio

  • Trabajé únicamente con los dos archivos locales y un navegador que controlo
  • Identifiqué la fuente input.value y el sink inseguro innerHTML
  • Registré el cambio de título como evidencia sin recolectar cookies, credenciales ni datos personales
  • Reemplacé innerHTML por textContent y confirmé que el marcador se muestra como texto
  • BeEF escuchó solo en el entorno local y no ejecuté ningún módulo de ataque
  • Cerré las pestañas, detuve Python y BeEF, y retiré los archivos o paquetes que ya no necesito

Glosario

XSS

Ejecución de contenido activo no confiable en el contexto de un sitio web.

Fuente

Punto por el que entra un dato no confiable, como una URL, un formulario o un mensaje.

Sink

API o contexto capaz de interpretar un dato como HTML, JavaScript o una URL ejecutable.

Sanitización

Transformación que permite solo una estructura y unos elementos considerados seguros.

CSP

Política del navegador que restringe desde dónde puede cargarse o ejecutarse contenido.

BeEF

Framework de laboratorio para estudiar el impacto de controlar JavaScript en un navegador propio.

Ver más guías
Ataques XSS: del dato no confiable al navegador | Cubix Academia