Seguridad Web 2024: Protege tu Aplicación de Ataques Reales
Seguridad Web 2024: La Guía Práctica contra Ataques Reales
El costo promedio de una violación de datos es $4.45 millones. El 83% de las brechas de seguridad se deben a vulnerabilidades web conocidas y prevenibles.
Si aún esperas que “la seguridad es responsabilidad del equipo de DevOps”, estás cometiendo el error que cometen el 60% de los desarrolladores.
OWASP Top 10: Las Vulnerabilidades Más Críticas
1️⃣ Inyección SQL (SQL Injection)
El Problema:
// ❌ VULNERABLE - Nunca hagas esto
const userId = req.query.id;
const query = `SELECT * FROM users WHERE id = ${userId}`;
db.query(query); // ¡Desastre!
Un atacante puede enviar:
?id=1; DROP TABLE users; --
Resultado: Tu tabla de usuarios desaparece.
La Solución: Prepared Statements
// ✓ SEGURO - Usar prepared statements
const userId = req.query.id;
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId]); // El motor separa datos de código
// Con TypeORM
const user = await User.findOne({ where: { id: userId } });
// Con Prisma
const user = await prisma.user.findUnique({
where: { id: parseInt(userId) },
});
Impacto Evitado:
- Pérdida de datos completa
- Exposición de información sensible
- Manipulación de registros
2️⃣ Cross-Site Scripting (XSS)
El Problema:
<!-- ❌ VULNERABLE -->
<h1><?php echo $_GET['name']; ?></h1>
<!-- Si alguien visita: ?name=<script>alert('Hacked!')</script> -->
<!-- El script se ejecuta en el navegador de todos los usuarios -->
Tipos de XSS:
Reflected XSS
URL maliciosa: https://example.com/?search=<img src=x onerror="alert('XSS')">
Stored XSS
// ❌ Comentario guardado sin sanitizar
const comment = req.body.text; // "<script>steal_cookies()</script>"
db.comments.insert(comment);
// Cuando otros ven el comentario, el script se ejecuta
La Solución: Sanitización y Context-Aware Encoding
// ✓ Opción 1: Librerías de sanitización
const DOMPurify = require('isomorphic-dompurify');
const clean = DOMPurify.sanitize(userInput);
// ✓ Opción 2: Template engines seguros
// En React (escape automático)
<div>{userInput}</div> // React escapa automáticamente
// En Vue
<div>{{ userInput }}</div> // Vue escapa por defecto
// ✓ Opción 3: Content Security Policy
// En servidor
res.setHeader('Content-Security-Policy', "script-src 'self'");
Impacto Evitado:
- Robo de cookies/sesiones
- Keylogging en navegador
- Phishing dentro de tu aplicación
- Difusión de malware
3️⃣ Autenticación Rota
El Problema:
// ❌ Problemas comunes
1. Contraseñas en texto plano
2. Sesiones sin expiración
3. Recuperación de contraseña débil
4. Falta de rate limiting
// ❌ Código inseguro
const login = (email, password) => {
// Sin encriptación
const user = db.users.findOne({ email, password });
// Sin verificación de intentos
return user;
};
La Solución: Autenticación Segura
// ✓ Encriptación de contraseñas
const bcrypt = require('bcrypt');
const register = async (email, password) => {
const hashed = await bcrypt.hash(password, 10);
return db.users.create({ email, passwordHash: hashed });
};
const login = async (email, password) => {
const user = db.users.findOne({ email });
if (!user) return null;
const isValid = await bcrypt.compare(password, user.passwordHash);
return isValid ? user : null;
};
// ✓ Rate limiting
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5, // 5 intentos
message: 'Demasiados intentos de login',
});
app.post('/login', loginLimiter, login);
// ✓ Sesiones seguras
app.use(session({
secret: process.env.SESSION_SECRET,
httpOnly: true, // No accesible desde JavaScript
secure: true, // Solo HTTPS
maxAge: 15 * 60 * 1000, // 15 minutos
sameSite: 'strict', // CSRF protection
}));
// ✓ Multi-factor authentication
const speakeasy = require('speakeasy');
const enable2FA = (user) => {
const secret = speakeasy.generateSecret({
name: `App (${user.email})`,
});
return secret; // Usuario escanea el QR
};
const verify2FA = (user, token) => {
return speakeasy.totp.verify({
secret: user.twoFactorSecret,
encoding: 'base32',
token,
});
};
Impacto Evitado:
- Acceso no autorizado
- Robo de identidad
- Compromiso de cuentas administrativas
4️⃣ Broken Access Control
El Problema:
// ❌ VULNERABLE - Confía en la UI
// Frontend oculta botón de admin, pero...
app.get('/api/users/:id', (req, res) => {
// Sin verificar permisos
res.json(db.users.findById(req.params.id));
});
// Un atacante puede:
// GET /api/users/1 → acceso normal
// GET /api/users/2 → acceso a otro usuario
// GET /api/users/admin → acceso a admin
La Solución: Control de Acceso en Backend
// ✓ Middleware de autorización
const authorize = (roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Not authorized' });
}
next();
};
};
// ✓ Uso
app.get('/api/users/:id', authorize(['admin']), (req, res) => {
res.json(db.users.findById(req.params.id));
});
app.delete('/api/users/:id', authorize(['admin']), (req, res) => {
db.users.deleteById(req.params.id);
res.json({ success: true });
});
// ✓ Verificación a nivel de datos
app.get('/api/profile', (req, res) => {
// Solo el usuario puede ver su propio perfil
const profile = db.users.findById(req.params.id);
if (profile.userId !== req.user.id && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
res.json(profile);
});
Impacto Evitado:
- Acceso a datos de otros usuarios
- Manipulación de registros administrativos
- Escalada de privilegios
5️⃣ Configuración de Seguridad Deficiente
El Problema:
// ❌ Errores comunes
1. Headers de seguridad faltantes
2. Librerías desactualizadas con vulnerabilidades
3. Debug mode activado en producción
4. Secretos en repositorio público
5. CORS demasiado permisivo
La Solución: Configuración Segura
// ✓ Express con helmet (headers de seguridad)
const helmet = require('helmet');
app.use(helmet());
// Expande manualmente
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "trusted-cdn.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
}));
// ✓ CORS seguro
const cors = require('cors');
app.use(cors({
origin: ['https://example.com', 'https://app.example.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
// ❌ Mal:
app.use(cors()); // Permite CUALQUIER origen
// ✓ Gestión de secretos
require('dotenv').config(); // Lee .env (NUNCA en git)
// En .env (gitignored)
DATABASE_URL=postgresql://user:pass@host/db
JWT_SECRET=super_secret_key_muy_segura
API_KEY=clave_api_produccion
// ✓ Nunca:
const secret = "hardcoded-secret"; // ❌
console.log(process.env.DATABASE_URL); // ❌ No imprimas secretos
Impacto Evitado:
- Exposición de secretos
- Acceso a través de vulnerabilidades conocidas
- Information disclosure
Protección Contra Ataques Específicos
CSRF (Cross-Site Request Forgery)
// ✓ Generar token CSRF
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: false });
app.get('/form', csrfProtection, (req, res) => {
res.send(`<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="${req.csrfToken()}">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>`);
});
app.post('/submit', csrfProtection, (req, res) => {
// Token verificado automáticamente
res.json({ success: true });
});
Rate Limiting y DDoS
// ✓ Limitar requests
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // 100 requests
message: 'Demasiadas requests, intenta después',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', apiLimiter);
// ✓ Store en Redis para múltiples servidores
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const client = redis.createClient();
const limiter = rateLimit({
store: new RedisStore({
client: client,
prefix: 'rl:', // rate limit
}),
windowMs: 15 * 60 * 1000,
max: 100,
});
Validación de Input
// ✓ Usar librerías de validación
const { body, validationResult } = require('express-validator');
app.post('/register', [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 12 }).trim(),
body('age').isInt({ min: 18, max: 120 }),
body('username').matches(/^[a-zA-Z0-9_]{3,20}$/),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Datos validados
register(req.body);
});
Checklist de Seguridad para Producción
- Todas las dependencias actualizadas
- Contraseñas con bcrypt/argon2
- Rate limiting en endpoints públicos
- Headers de seguridad (CSP, HSTS, etc.)
- CORS configurado restrictivamente
- Validación de input en backend
- Sanitización de output
- Logging de acciones críticas
- Secrets en variables de entorno
- HTTPS obligatorio
- JWT o sesiones seguras
- 2FA en cuentas administrativas
- Backup regular
- Monitoreo de seguridad
- Plan de respuesta a incidentes
Herramientas Esenciales
# Escaneo de vulnerabilidades
npm audit
npm audit fix
# Análisis estático de seguridad
npm install -g snyk
snyk test
# OWASP Dependency Check
npm install -g @snyk/cli
# Escaneo de secretos
npm install -g git-secrets
git secrets --scan
Conclusión
La seguridad no es un complemento, es arquitectura fundamental. El 95% de los ataques exitosos aprovechan vulnerabilidades conocidas y prevenibles.
Los desarrolladores modernos deben ser guardianes de la seguridad. No puedes delegar esta responsabilidad.
Acción inmediata:
- Audita tu aplicación actual
- Implementa los 5 cambios más críticos
- Configura monitoreo de vulnerabilidades
- Entrena a tu equipo en OWASP Top 10
Recursos: