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.
<!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:
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.1Abre 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.
<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.
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 externosEn 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.
sudo apt updatesudo apt install -y beef-xsscommand -v beef-xss-startsudo 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.
sudo beef-xss-startLee 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:
<!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.
sudo beef-xss-stopsudo apt remove beef-xss6 · 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.
result.textContent = input.value;- Renderiza texto como texto. Prefiere
textContent,createTextNodey el autoescapado del framework. Audita escapes comodangerouslySetInnerHTML. - 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.
- 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.
- 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.
- Limita la sesión. Marca las cookies sensibles como
HttpOnly,Securey con unSameSiteapropiado; aplica autorización servidor-side en cada acción y evita secretos duraderos enlocalStorage.
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.