La vulnerabilidad de Cross-Site Scripting (XSS) permite a los atacantes inyectar scripts maliciosos en páginas web vistas por otros usuarios. El núcleo del problema radica en que la aplicación web trata la entrada del usuario como código ejecutable en lugar de texto seguro.
Principios fundamentales de XSS
Los frameworks modernos como Vue y React implementan mecanismos de escape automático. Vue escapa el contenido HTML de forma predeterminada en las plantillas, pero el uso de v-html requiere precaución. React escapa todos los cadenas antes de la renderización, sin embargo, dangerouslySetInnerHTML omite esta protección.
El punto crítico es que el navegador no puede distinguir entre código legítimo y scripts inyectados. La vulnerabilidad se materializa en el momento de la salida de datos al HTML, no en la entrada.
<!-- Código vulnerable: entrada del usuario concatenada directamente -->
<div>Bienvenido, <%= usuarioInput %></div>
<!-- Entrada maliciosa: -->
<script>alert('XSS!')</script>
Clasificación de ataques XSS
| Tipo | XSS Persistente | XSS Reflejado | XSS basado en DOM |
|---|---|---|---|
| Característica | El script se almacena en el servidor | El script está en la URL | Se ejecuta compeltamente en el cliente |
| Payload típico | <img src=x onerror=alert(1)> |
';alert(1);// |
http://ejemplo.com#<img src=x onerror=alert(1)> |
Ejemplos prácticos por tipo
XSS persistente
// Almacenamiento sin validación (Node.js)
app.post('/comentarios', (req, res) => {
bd.guardarComentario(req.body.texto);
});
// Renderización vulnerable
app.get('/comentarios', (req, res) => {
const comentarios = bd.obtenerComentarios();
res.send(comentarios.map(c => `<div>${c.texto}</div>`));
});
XSS basado en DOM y mutaciones (mXSS)
El mXSS ocurre cuando datos de usuario sufren múltiples renderizaciones DOM, causando distorsiones sintácticas que el navegador repara, potencialmente convirtiendo contenido "seguro" en código ejecutable.
<div id="contenedor-1"></div>
<div id="contenedor-2"></div>
<script>
const entrada = '<scr<script>ipt>alert("mXSS")</script>';
const contenedor1 = document.getElementById('contenedor-1');
contenedor1.innerHTML = entrada; // El navegador repara la sintaxis
const contenidoDistorsionado = contenedor1.innerHTML;
document.getElementById('contenedor-2').innerHTML = contenidoDistorsionado;
</script>
XSS en analizadores UBB
Los sistemas UBB que no filtran protocolos peligrosos son vulnerables:
function analizarUBB(contenido) {
return contenido.replace(/\[img\](.*?)\[\/img\]/gi,
'<img src="$1" alt="imagen">');
}
// Entrada maliciosa: [img]javascript:alert(1)[/img]
Prácticas seguras en frameworks
Vue.js
// Evitar v-html con contenido no confiable
<div v-html="contenidoSanitizado"></div>
<script>
import DOMPurify from 'dompurify';
export default {
computed: {
contenidoSanitizado() {
return DOMPurify.sanitize(this.contenidoCrudo);
}
}
}
</script>
React
// Usar renderizado seguro por defecto
function ComponenteSeguro({ texto }) {
return <div>{texto}</div>;
}
// Para contenido HTML, sanitizar primero
import DOMPurify from 'dompurify';
function HTMLSanitizado({ html }) {
const limpio = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: limpio }} />;
}
Técnicas de detección
Pruebas manuales con payloads comunes
const payloadsPrueba = [
'<script>alert(1)</script>',
'<img src=x onerror=alert(1)>',
'javascript:alert(1)',
'\'";alert(1)//',
'%26%23x3c;script%26%23x3e;'
];
Automatización con herramientas
Utilizar herramientas como Burp Suite para escaneo activo o XSS Hunter para captura automáticca de vulnerabiliaddes.
Defensa contra XSS
Política de Seguridad de Contenido (CSP)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://cdn.confiable.com;
style-src 'self' 'unsafe-inline';">
Sanitización en el servidor
function escaparHTML(cadena) {
const mapa = {'&':'&','<':'<','>':'>','"':'"'};
return cadena.replace(/[<>"&]/g, m => mapa[m]);
}
Configuración segura de cookies
// Java - Spring Security
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setSameSite("Strict");
// Node.js - Express
res.cookie('session', valor, {
secure: true,
httpOnly: true,
sameSite: 'strict'
});
Verificación de defensas
- Comprobar encabezados HTTP para CSP
- Verificar atributos HttpOnly en cookies
- Probar que las entradas se escapen al renderizarse