Artículos / Seguridad en Aplicaciones Web: Mejores Prácticas

Seguridad en Aplicaciones Web: Mejores Prácticas

Seguridad en Aplicaciones Web: Mejores Prácticas

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

Monitoreo continuo

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.

Volver a Artículos