Seguridad en Aplicaciones Web: Mejores Prácticas
Introducción
La seguridad en aplicaciones web es fundamental en el desarrollo moderno. Este artículo cubre las mejores prácticas para proteger tus aplicaciones contra las amenazas más comunes.
OWASP Top 10
1. Injection (Inyección)
Prevenir inyecciones SQL, NoSQL, OS y LDAP:
// ❌ Vulnerable
const query = `SELECT * FROM users WHERE id = ${userId}`;
// ✅ Seguro - Prepared Statements
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId]);
2. Broken Authentication
Implementar autenticación robusta:
// Configuración de sesiones seguras
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 horas
}
}));
Implementación de HTTPS
Configuración SSL/TLS
server {
listen 443 ssl http2;
server_name tudominio.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# Configuraciones de seguridad
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
}
Autenticación y Autorización
JSON Web Tokens (JWT)
const jwt = require('jsonwebtoken');
// Generar token
function generateToken(user) {
return jwt.sign(
{
id: user.id,
email: user.email
},
process.env.JWT_SECRET,
{
expiresIn: '1h',
issuer: 'tu-app.com',
audience: 'tu-app.com'
}
);
}
// Verificar token
function verifyToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token requerido' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Token inválido' });
}
}
Autenticación Multifactor (2FA)
const speakeasy = require('speakeasy');
// Generar secreto
const secret = speakeasy.generateSecret({
name: 'Tu App',
issuer: 'TuEmpresa'
});
// Verificar código
function verifyTOTP(token, secret) {
return speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 2
});
}
Validación y Sanitización
Validación del lado del servidor
const Joi = require('joi');
const userSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])')).required(),
name: Joi.string().min(2).max(50).required()
});
// Middleware de validación
function validateUser(req, res, next) {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
}
Sanitización de datos
const DOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const purify = DOMPurify(window);
function sanitizeHTML(dirty) {
return purify.sanitize(dirty);
}
Headers de Seguridad
Configuración de headers
const helmet = require('helmet');
app.use(helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
// X-Frame-Options
frameguard: { action: 'deny' },
// X-Content-Type-Options
noSniff: true,
// Referrer Policy
referrerPolicy: { policy: "same-origin" }
}));
Cifrado de Datos
Cifrado de contraseñas
const bcrypt = require('bcrypt');
// Hashear contraseña
async function hashPassword(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}
// Verificar contraseña
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
Cifrado de datos sensibles
const crypto = require('crypto');
const algorithm = 'aes-256-gcm';
const secretKey = crypto.scryptSync(process.env.ENCRYPTION_KEY, 'salt', 32);
function encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(algorithm, secretKey, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
};
}
Prevención de Ataques
CSRF Protection
const csrf = require('csurf');
app.use(csrf({
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
}
}));
// Middleware para agregar token a vistas
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
Rate Limiting
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5, // 5 intentos por IP
message: 'Demasiados intentos de login',
standardHeaders: true,
legacyHeaders: false,
});
app.post('/login', loginLimiter, loginHandler);
Monitoreo y Logging
Logging de seguridad
const winston = require('winston');
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/security.log' }),
new winston.transports.Console()
]
});
// Log de eventos de seguridad
function logSecurityEvent(event, details) {
securityLogger.warn('Security Event', {
event,
details,
timestamp: new Date().toISOString(),
ip: details.ip,
userAgent: details.userAgent
});
}
Testing de Seguridad
Pruebas automatizadas
const request = require('supertest');
describe('Security Tests', () => {
test('should reject SQL injection attempts', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.post('/search')
.send({ query: maliciousInput })
.expect(400);
expect(response.body.error).toContain('Invalid input');
});
test('should enforce rate limiting', async () => {
// Realizar múltiples requests
for (let i = 0; i < 6; i++) {
await request(app).post('/login').send({
email: 'test@test.com',
password: 'wrongpassword'
});
}
// El sexto debería ser rechazado
const response = await request(app)
.post('/login')
.send({ email: 'test@test.com', password: 'wrongpassword' })
.expect(429);
});
});
Checklist de Seguridad
Pre-deployment
- HTTPS configurado correctamente
- Headers de seguridad implementados
- Validación de entrada en todos los endpoints
- Autenticación y autorización robustas
- Contraseñas hasheadas con algoritmo seguro
- Rate limiting configurado
- CSRF protection habilitado
- Logging de seguridad implementado
- Dependencias actualizadas
- Secrets en variables de entorno
Monitoreo continuo
- Logs de seguridad revisados regularmente
- Vulnerabilities scans automatizados
- Penetration testing periódico
- Actualizaciones de seguridad aplicadas
- Backup y recovery procedures probados
Conclusión
La seguridad web es un proceso continuo que requiere atención constante. Implementar estas prácticas desde el inicio del desarrollo es crucial para proteger tanto tu aplicación como los datos de tus usuarios.
Recursos Adicionales
¿Necesitas ayuda implementando estas medidas de seguridad en tu aplicación? Contáctanos para una consultoría personalizada.