Qué es XSS y cómo proteger tu aplicación
Autor: Redacción Domeny.ForSale Publicado
En resumen: XSS (Cross-Site Scripting) es una vulnerabilidad que permite que el navegador ejecute código no confiable en el contexto de la página visitada. Suele surgir cuando la aplicación trata los datos del usuario como parte de la página o del código, en lugar de tratarlos como texto. La protección consiste, ante todo, en mostrar los datos de forma segura, no solo en bloquear entradas sospechosas.
Cómo funciona XSS y qué puede hacer un atacante
El navegador interpreta HTML como la estructura de la página y JavaScript como instrucciones que debe ejecutar. Si la aplicación inserta contenido sin verificar en un lugar que se interpreta como código, un atacante puede alterar el funcionamiento de la página para otro usuario. Ese contenido puede proceder de un comentario, un parámetro de la dirección, un nombre de perfil o la respuesta de un servicio externo.
El código ejecutado mediante XSS actúa en el contexto del sitio atacado. Puede leer los datos accesibles de la página, modificar su contenido, mostrar un formulario de inicio de sesión falso o realizar operaciones con los permisos del usuario que ha iniciado sesión. Las consecuencias reales dependen de las funciones de la aplicación y de las medidas de seguridad aplicadas.
Esto no implica automáticamente tomar el control del servidor ni de todo el ordenador. El problema afecta principalmente a la confianza que el navegador deposita en el código ejecutado por ese sitio. El usuario puede abrir una dirección legítima y, aun así, encontrarse con una página que contiene contenido malicioso.
Las tres formas de XSS más habituales
Esta clasificación ayuda a identificar de dónde procede el contenido peligroso y en qué parte de la aplicación se procesa:
- Stored XSS, o XSS almacenado: el contenido se guarda, por ejemplo, en una base de datos de comentarios. El problema aparece cuando la aplicación lo muestra a otras personas de forma insegura. Guardarlo en la base de datos no constituye por sí solo una vulnerabilidad.
- Reflected XSS, o XSS reflejado: la aplicación toma datos de la petición, como el término de búsqueda incluido en la URL, y los incorpora a la respuesta sin la protección adecuada. El atacante puede intentar convencer a la víctima de que abra un enlace preparado para ello.
- DOM-based XSS, o XSS basado en DOM: el procesamiento inseguro se produce en el código JavaScript que se ejecuta en el navegador. DOM es el modelo de objetos del documento, es decir, la representación de la página que los scripts pueden leer y modificar.
Estas categorías no siempre son excluyentes. «Almacenado» y «reflejado» describen principalmente cómo llegan los datos, mientras que «DOM-based» indica dónde tiene lugar el procesamiento inseguro. El contenido guardado en una base de datos también puede dar lugar a DOM-based XSS.
Un ejemplo sencillo: texto tratado como HTML
Supongamos que una aplicación muestra el nombre del usuario. Ejecuta el siguiente ejemplo únicamente en tu propio entorno de pruebas o donde tengas autorización expresa:
const nombre = '<img src="x" onerror="alert(\'Prueba XSS\')">';
const elemento = document.querySelector('#saludo');
element.innerHTML = nombre;
La propiedad innerHTML indica al navegador que interprete el valor como HTML. Si la imagen no se carga, el manejador del error puede ejecutar alert, que muestra una ventana con un mensaje. Esto demuestra la ejecución de código, no el robo de datos; las medidas de seguridad adicionales del navegador o de la página pueden bloquearla.
Si el nombre debe mostrarse como texto sin formato, la solución adecuada es:
const nombre = '<img src="x" onerror="alert(\'Prueba XSS\')">';
const elemento = document.querySelector('#saludo');
element.textContent = nombre;
La propiedad textContent mostrará la cadena indicada sin crear una imagen ni ejecutar la instrucción que contiene. La diferencia es fundamental: los datos siguen siendo datos. No hace falta adivinar qué palabras o etiquetas podrían ser peligrosas.
Cómo proteger una aplicación frente a XSS
Adapta la protección al lugar donde se utilizan los datos. La codificación de salida consiste en representar los caracteres especiales de manera que no se interpreten como sintaxis. El texto en HTML, el valor de un atributo, una URL y un fragmento de JavaScript requieren reglas distintas. Sustituir únicamente los signos < y > no protege todas estas situaciones.
Cuando muestres texto sin formato, utiliza mecanismos seguros de plantillas e interfaces como textContent. La codificación automática del framework ayuda, pero se puede omitir mediante una función que inserte HTML sin procesar. Estas excepciones requieren una revisión específica.
Si los usuarios realmente necesitan formato HTML, utiliza una biblioteca de sanitización que reciba mantenimiento, como DOMPurify. La sanitización elimina los elementos y atributos no permitidos según unas reglas definidas. Un filtro propio basado en unas pocas expresiones regulares no ofrece una protección equivalente.
La validación, es decir, la comprobación de que los datos tienen un formato permitido, sigue siendo útil: un campo de edad debe aceptar un número, y la dirección de un enlace debe usar un esquema permitido. Sin embargo, no sustituye a la presentación segura de los datos. Tampoco basta con proteger solo el formulario, porque los datos pueden llegar a la aplicación por otras vías.
Las capas adicionales de protección limitan las consecuencias de los errores. CSP (Content Security Policy) permite definir reglas para la ejecución de scripts, pero su eficacia depende de la configuración. El atributo HttpOnly impide que JavaScript lea la cookie, pero no bloquea todas las acciones que el código malicioso podría realizar en la sesión activa.
Por dónde empezar a revisar tu sitio
Sigue el recorrido de los datos de un campo, desde su recepción hasta cada lugar donde se muestran. Un comentario puede ser seguro debajo de un artículo y resultar peligroso en el panel de moderación o en la vista previa de una notificación. Revisa también los usos de innerHTML y de las funciones que permiten omitir la codificación automática.
El siguiente paso es hacer una prueba en un entorno aislado: comprueba si el contenido introducido aparece como texto o modifica la estructura de la página. Tras corregir el problema, añade una prueba de regresión que detecte si esos datos vuelven a tratarse como HTML.
Consulta también: ¿Qué es 2FA?
