Ilustración de las 10 principales vulnerabilidades OWASP 2025 con escudo de defensa y alertas de seguridad

Top 10 vulnerabilidades OWASP 2025 y cómo mitigarlas en producción

Guía práctica para proteger aplicaciones web contra las amenazas más críticas: desde inyección SQL hasta vulnerabilidades de IA

Introducción

El OWASP Top 10 se ha consolidado como el estándar de facto para medir riesgos de seguridad en aplicaciones web, y la versión 2025 refleja una evolución dramática del panorama de amenazas: nuevas categorías emergen para abordar vulnerabilidades en inteligencia artificial, supply chain attacks sofisticados y configuraciones inseguras en arquitecturas serverless y containerizadas que no existían hace 5 años.

Según datos de OWASP Foundation y Verizon DBIR 2025, más del 90% de aplicaciones web contienen al menos una vulnerabilidad del Top 10, y el 43% de brechas de datos exitosas explotan directamente estas debilidades conocidas. Lo más preocupante: el tiempo promedio entre descubrimiento de vulnerabilidad y explotación activa en ataques se redujo de 30 días en 2020 a solo 7 días en 2025, dejando ventanas mínimas para remediar antes de que atacantes weaponizen exploits.

Esta guía desglosa las 10 vulnerabilidades más críticas de 2025 con enfoque práctico de producción: qué es cada vulnerabilidad, por qué importa, ejemplos reales de explotación, código vulnerable vs. código seguro, herramientas de detección automatizada, estrategias de mitigación implementables esta semana, y checklist de validación para DevSecOps. No es teoría académica; es arsenal operativo para desarrolladores, arquitectos de seguridad y equipos de operaciones que necesitan proteger aplicaciones en producción ahora.

OWASP Top 10 – 2025: Resumen Ejecutivo

#VulnerabilidadCambio vs 2021ImpactoPrevalencia
A01Broken Access Control↔️ Mantiene #1Crítico94% apps
A02Cryptographic Failures↔️ (antes Sensitive Data)Alto60% apps
A03Injection↓ De #1 a #3Crítico40% apps
A04Insecure Design↔️ Nueva categoríaAlto55% apps
A05Security Misconfiguration↔️Alto85% apps
A06Vulnerable Components↔️Crítico70% apps
A07Authentication Failures↔️Crítico45% apps
A08Software & Data Integrity🆕 Nueva (supply chain)Alto30% apps
A09Logging & Monitoring Failures↔️Medio75% apps
A10Server-Side Request Forgery🆕 SSRF entra al Top 10Alto25% apps

Nuevas tendencias 2025:

  • AI/ML Vulnerabilities: Prompt injection, model poisoning, data poisoning (categoría emergente, aún no en Top 10 oficial)
  • API-specific risks: Broken Object Level Authorization (BOLA), Mass Assignment
  • Cloud misconfigurations: S3 buckets públicos, IAM roles sobre-privilegiados
Ilustración de las 10 principales vulnerabilidades OWASP 2025 con escudo de defensa y alertas de seguridad

A01: Broken Access Control

Qué es: Fallas que permiten a usuarios acceder a recursos/funcionalidades sin autorización apropiada; atacante puede ver, modificar o eliminar datos de otros usuarios.

Por qué es #1: 94% de aplicaciones testadas tienen algún tipo de falla de control de acceso; es la vulnerabilidad más prevalente y explotada.

Ejemplos de explotación:

1. Insecure Direct Object Reference (IDOR)

javascript// ❌ VULNERABLE: No valida que usuario tenga permiso para ver documento
app.get('/api/documents/:id', async (req, res) => {
  const document = await db.documents.findById(req.params.id);
  res.json(document); // Cualquier usuario autenticado puede ver cualquier documento
});

// ✅ SEGURO: Valida ownership
app.get('/api/documents/:id', authMiddleware, async (req, res) => {
  const document = await db.documents.findById(req.params.id);
  
  if (!document) {
    return res.status(404).json({ error: 'Documento no encontrado' });
  }
  
  // Verificar que usuario actual es owner o tiene permiso
  if (document.userId !== req.user.id && !req.user.hasRole('admin')) {
    return res.status(403).json({ error: 'Acceso denegado' });
  }
  
  res.json(document);
});

2. Path Traversal

python# ❌ VULNERABLE: Permite acceder a archivos fuera del directorio permitido
@app.route('/download')
def download():
    filename = request.args.get('file')
    return send_file(f'/uploads/{filename}')  # ../../../etc/passwd

# ✅ SEGURO: Valida y sanitiza path
from werkzeug.utils import secure_filename
import os

@app.route('/download')
def download():
    filename = request.args.get('file')
    safe_filename = secure_filename(filename)
    
    # Construir path completo y validar que está dentro de directorio permitido
    base_dir = os.path.abspath('/uploads')
    file_path = os.path.abspath(os.path.join(base_dir, safe_filename))
    
    if not file_path.startswith(base_dir):
        return jsonify({'error': 'Acceso denegado'}), 403
    
    if not os.path.exists(file_path):
        return jsonify({'error': 'Archivo no encontrado'}), 404
    
    return send_file(file_path)

3. Missing Function Level Access Control

javascript// ❌ VULNERABLE: Endpoint admin sin validación de rol
app.delete('/api/users/:id', authMiddleware, async (req, res) => {
  await db.users.delete(req.params.id);
  res.json({ success: true });
});

// ✅ SEGURO: Valida rol antes de permitir acción
const requireRole = (role) => {
  return (req, res, next) => {
    if (!req.user.roles.includes(role)) {
      return res.status(403).json({ error: 'Permisos insuficientes' });
    }
    next();
  };
};

app.delete('/api/users/:id', authMiddleware, requireRole('admin'), async (req, res) => {
  await db.users.delete(req.params.id);
  res.json({ success: true });
});

Mitigaciones:

  1. Deny by default: todo acceso denegado por defecto; whitelist explícita
  2. Centralized access control: middleware/decorators reutilizables
  3. Validar ownership: cada request verifica que usuario tiene permiso sobre recurso
  4. Rate limiting: limita intentos de enumeración
  5. Logging: audita accesos a recursos sensibles

Herramientas de detección:

  • Burp Suite: escáner de IDOR y path traversal
  • OWASP ZAP: active scan para access control
  • Postman/Newman: tests automatizados de autorización

A02: Cryptographic Failures

Qué es: Fallas en protección de datos sensibles en tránsito y reposo; incluye uso de algoritmos débiles, llaves hardcodeadas, transmisión en claro.

Impacto: Exposición de PII, credenciales, datos financieros, secretos comerciales.

Ejemplos de explotación:

1. Datos sensibles sin cifrar

python# ❌ VULNERABLE: Passwords en texto plano
user = User(
    username="ana.garcia",
    password="MyP@ssw0rd123"  # Almacenado tal cual en DB
)

# ✅ SEGURO: Hash con bcrypt + salt
import bcrypt

password_hash = bcrypt.hashpw(
    "MyP@ssw0rd123".encode('utf-8'),
    bcrypt.gensalt(rounds=12)  # Cost factor 12 (2^12 iteraciones)
)

user = User(
    username="ana.garcia",
    password_hash=password_hash
)

2. Algoritmos criptográficos débiles

javascript// ❌ VULNERABLE: MD5/SHA1 son criptográficamente rotos
const crypto = require('crypto');
const hash = crypto.createHash('md5').update(password).digest('hex');

// ✅ SEGURO: Usar algoritmos modernos
const argon2 = require('argon2');
const hash = await argon2.hash(password, {
  type: argon2.argon2id,  // Resistente a GPU/ASIC attacks
  memoryCost: 65536,      // 64 MB
  timeCost: 3,            // 3 iteraciones
  parallelism: 4          // 4 threads
});

3. Transmisión sin TLS

text# ❌ VULNERABLE: HTTP sin cifrado
server {
  listen 80;
  server_name app.example.com;
  location / {
    proxy_pass http://backend:3000;
  }
}

# ✅ SEGURO: Forzar HTTPS con TLS 1.3
server {
  listen 80;
  server_name app.example.com;
  return 301 https://$server_name$request_uri;  # Redirect a HTTPS
}

server {
  listen 443 ssl http2;
  server_name app.example.com;
  
  ssl_certificate /etc/ssl/certs/app.crt;
  ssl_certificate_key /etc/ssl/private/app.key;
  
  ssl_protocols TLSv1.3 TLSv1.2;
  ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
  ssl_prefer_server_ciphers off;
  
  # HSTS: forzar HTTPS en cliente
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
  
  location / {
    proxy_pass http://backend:3000;
  }
}

4. Secretos hardcodeados en código

python# ❌ VULNERABLE: API key en código fuente
DATABASE_URL = "postgresql://user:P@ssw0rd123@db.example.com/prod"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

# ✅ SEGURO: Variables de entorno + secrets manager
import os
from aws_secretsmanager import get_secret

DATABASE_URL = os.environ.get('DATABASE_URL')
AWS_CREDENTIALS = get_secret('prod/aws/credentials')

Mitigaciones:

  1. Clasificar datos: identifica qué datos son sensibles (PII, PCI, PHI)
  2. Cifrado en reposo: AES-256 para databases, discos, backups
  3. Cifrado en tránsito: TLS 1.3 obligatorio, HTTP Strict Transport Security
  4. Key management: AWS KMS, Azure Key Vault, HashiCorp Vault; rotación automática
  5. No almacenar si no es necesario: minimiza retención de datos sensibles

Herramientas:

  • TruffleHog: detecta secretos en código/git history
  • GitGuardian: escaneo continuo de repos
  • SSL Labs: test configuración TLS
  • Qualys SSL Scanner: auditoría de cifrado

A03: Injection

Qué es: Inyección de código malicioso (SQL, NoSQL, OS commands, LDAP) cuando entrada no confiable se ejecuta sin validación.

Por qué bajó de #1: Frameworks modernos (ORMs, prepared statements) han reducido prevalencia, pero sigue siendo crítico.

Ejemplos de explotación:

1. SQL Injection

javascript// ❌ VULNERABLE: Concatenación de strings
app.get('/users/search', async (req, res) => {
  const name = req.query.name;
  const query = `SELECT * FROM users WHERE name = '${name}'`;
  // Input: ' OR '1'='1' -- 
  // Query resultante: SELECT * FROM users WHERE name = '' OR '1'='1' --'
  const results = await db.query(query);
  res.json(results);
});

// ✅ SEGURO: Prepared statements / Parameterized queries
app.get('/users/search', async (req, res) => {
  const name = req.query.name;
  const query = 'SELECT * FROM users WHERE name = $1';
  const results = await db.query(query, [name]);  // Parámetro escapado automáticamente
  res.json(results);
});

// ✅ SEGURO: ORM (Sequelize, TypeORM, Prisma)
const results = await User.findAll({
  where: { name: req.query.name }  // ORM previene injection
});

2. NoSQL Injection (MongoDB)

javascript// ❌ VULNERABLE: Query injection en MongoDB
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  const user = await User.findOne({ username, password });
  // Input: {"username": {"$ne": null}, "password": {"$ne": null}}
  // Bypasses authentication
});

// ✅ SEGURO: Validar tipos y sanitizar
const { username, password } = req.body;

// Validar que son strings
if (typeof username !== 'string' || typeof password !== 'string') {
  return res.status(400).json({ error: 'Datos inválidos' });
}

const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
  return res.status(401).json({ error: 'Credenciales inválidas' });
}

3. Command Injection

python# ❌ VULNERABLE: Ejecutar comandos OS con input del usuario
import os
@app.route('/ping')
def ping():
    host = request.args.get('host')
    result = os.system(f'ping -c 4 {host}')  # host: 8.8.8.8; rm -rf /
    return result

# ✅ SEGURO: Whitelist + subprocess con argumentos separados
import subprocess
import re

@app.route('/ping')
def ping():
    host = request.args.get('host')
    
    # Validar formato IP o hostname
    if not re.match(r'^[\w\.-]+$', host):
        return jsonify({'error': 'Host inválido'}), 400
    
    # subprocess con argumentos separados (no shell=True)
    try:
        result = subprocess.run(
            ['ping', '-c', '4', host],
            capture_output=True,
            timeout=10,
            check=True
        )
        return jsonify({'output': result.stdout.decode()})
    except subprocess.TimeoutExpired:
        return jsonify({'error': 'Timeout'}), 408

Mitigaciones:

  1. Parameterized queries: usar siempre prepared statements
  2. ORMs: prefiere ORMs modernos que previenen injection por defecto
  3. Input validation: whitelist de caracteres permitidos
  4. Escape output: si debes construir queries dinámicamente, escapa apropiadamente
  5. Least privilege: cuentas de DB con permisos mínimos (no usar root/admin)
  6. WAF: Web Application Firewall con reglas anti-injection

Herramientas:

  • SQLMap: testing de SQL injection
  • NoSQLMap: NoSQL injection scanner
  • Burp Suite: active scan para injection
  • Semgrep: análisis estático de código para detectar patterns vulnerables

A04: Insecure Design

Qué es: Fallas de diseño arquitectónico que no pueden resolverse solo con implementación correcta; ausencia de controles de seguridad desde diseño.

Nueva categoría 2021: Enfatiza “shift-left” security; diseñar con seguridad desde inicio.

Ejemplos:

1. Falta de rate limiting

javascript// ❌ VULNERABLE: Sin protección contra brute force
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  const user = await User.findOne({ username });
  
  if (user && await bcrypt.compare(password, user.passwordHash)) {
    return res.json({ token: generateToken(user) });
  }
  
  res.status(401).json({ error: 'Credenciales inválidas' });
});

// ✅ SEGURO: Rate limiting + account lockout
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutos
  max: 5,  // 5 intentos
  message: 'Demasiados intentos de login, intenta en 15 minutos'
});

app.post('/login', loginLimiter, async (req, res) => {
  const { username, password } = req.body;
  const user = await User.findOne({ username });
  
  // Account lockout después de N intentos fallidos
  if (user && user.failedAttempts >= 5) {
    return res.status(423).json({ 
      error: 'Cuenta bloqueada por múltiples intentos fallidos' 
    });
  }
  
  if (user && await bcrypt.compare(password, user.passwordHash)) {
    await User.updateOne({ _id: user._id }, { failedAttempts: 0 });
    return res.json({ token: generateToken(user) });
  }
  
  if (user) {
    await User.updateOne(
      { _id: user._id },
      { $inc: { failedAttempts: 1 } }
    );
  }
  
  res.status(401).json({ error: 'Credenciales inválidas' });
});

2. Business logic flaws

python# ❌ VULNERABLE: Puede comprar con saldo negativo
@app.route('/purchase', methods=['POST'])
def purchase():
    user = get_current_user()
    amount = request.json.get('amount')
    
    user.balance -= amount  # No valida que balance >= amount
    db.session.commit()
    return jsonify({'success': True})

# ✅ SEGURO: Validar lógica de negocio
@app.route('/purchase', methods=['POST'])
def purchase():
    user = get_current_user()
    amount = request.json.get('amount')
    
    # Validaciones de negocio
    if amount <= 0:
        return jsonify({'error': 'Monto inválido'}), 400
    
    if user.balance < amount:
        return jsonify({'error': 'Saldo insuficiente'}), 402
    
    # Transacción atómica
    try:
        with db.session.begin_nested():
            user.balance -= amount
            transaction = Transaction(
                user_id=user.id,
                amount=amount,
                timestamp=datetime.now()
            )
            db.session.add(transaction)
            db.session.commit()
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': 'Error procesando compra'}), 500
    
    return jsonify({'success': True, 'new_balance': user.balance})

Mitigaciones:

  1. Threat modeling: STRIDE, PASTA durante fase de diseño
  2. Security requirements: definir requisitos de seguridad antes de codificar
  3. Secure design patterns: usar patrones probados (circuit breaker, rate limiter)
  4. Abuse cases: documentar cómo atacantes podrían abusar del sistema
  5. Defense in depth: múltiples capas de control

A05: Security Misconfiguration

Qué es: Configuraciones inseguras en cualquier capa del stack: servidores web, bases de datos, frameworks, cloud services.

Prevalencia: 85% de aplicaciones tienen alguna misconfiguration.

Ejemplos:

1. Headers de seguridad faltantes

text# ❌ VULNERABLE: Sin security headers
server {
  listen 443 ssl;
  server_name app.example.com;
  
  location / {
    proxy_pass http://backend:3000;
  }
}

# ✅ SEGURO: Headers de seguridad completos
server {
  listen 443 ssl http2;
  server_name app.example.com;
  
  # Prevenir clickjacking
  add_header X-Frame-Options "DENY" always;
  
  # XSS protection
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-XSS-Protection "1; mode=block" always;
  
  # Content Security Policy
  add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
  
  # HSTS
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
  
  # Permissions Policy
  add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
  
  location / {
    proxy_pass http://backend:3000;
  }
}

2. Información sensible en mensajes de error

javascript// ❌ VULNERABLE: Stack traces en producción
app.use((err, req, res, next) => {
  res.status(500).json({
    error: err.message,
    stack: err.stack  // Expone estructura interna
  });
});

// ✅ SEGURO: Mensajes genéricos en producción
app.use((err, req, res, next) => {
  // Log completo internamente
  console.error('Error:', err);
  
  // Respuesta genérica al cliente
  res.status(500).json({
    error: process.env.NODE_ENV === 'production' 
      ? 'Error interno del servidor' 
      : err.message  // Stack trace solo en dev
  });
});

3. Configuración por defecto insegura

text# ❌ VULNERABLE: MongoDB sin autenticación
# docker-compose.yml
services:
  mongodb:
    image: mongo:7.0
    ports:
      - "27017:27017"  # Expuesto a internet sin auth

# ✅ SEGURO: Autenticación + red interna
services:
  mongodb:
    image: mongo:7.0
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
    networks:
      - backend  # Solo accesible desde containers de backend
    # No exponer puerto públicamente
  
  api:
    image: myapi:latest
    environment:
      MONGODB_URI: mongodb://admin:${MONGO_PASSWORD}@mongodb:27017/prod?authSource=admin
    networks:
      - backend
      - frontend

networks:
  backend:
    internal: true  # Red interna sin acceso a internet
  frontend:

Mitigaciones:

  1. Hardening guides: seguir CIS Benchmarks, NIST guidelines
  2. Configuration management: Ansible, Terraform con configs versionadas
  3. Security headers: implementar todos los headers recomendados
  4. Disable features no usadas: servicios, puertos, endpoints innecesarios
  5. Auditorías regulares: scanners de configuración (Prowler, ScoutSuite)

Herramientas:

  • Mozilla Observatory: analiza security headers
  • Security Headers: checklist de headers HTTP
  • Docker Bench: audita configuración de Docker
  • AWS Config / Azure Policy: auditoría continua de cloud

A06: Vulnerable and Outdated Components

Qué es: Uso de bibliotecas, frameworks, dependencias con vulnerabilidades conocidas (CVEs).

Prevalencia: 70% de aplicaciones usan componentes con vulnerabilidades conocidas.

Impacto real:

  • Equifax breach 2017: Apache Struts vulnerable (CVE-2017-5638) → 147M registros expuestos
  • Log4Shell 2021: Log4j vulnerable (CVE-2021-44228) → millones de sistemas comprometidos

Ejemplos:

json// ❌ VULNERABLE: package.json con versiones antiguas
{
  "dependencies": {
    "express": "4.16.0",  // Versión 2018 con vulnerabilidades conocidas
    "lodash": "4.17.11",  // Prototype pollution
    "axios": "0.18.0"     // SSRF vulnerability
  }
}

// ✅ SEGURO: Versiones actualizadas
{
  "dependencies": {
    "express": "^4.19.2",
    "lodash": "^4.17.21",
    "axios": "^1.6.8"
  }
}

Mitigaciones:

  1. Inventario de componentes: Software Bill of Materials (SBOM)
  2. Vulnerability scanning: Dependabot, Snyk, WhiteSource
  3. Update regularly: políticas de actualización (ej: mensual para minor, inmediato para critical)
  4. Monitor advisories: suscribirse a GitHub Security Advisories, NVD
  5. Pruning: eliminar dependencias no usadas

Herramientas:

bash# npm audit
npm audit
npm audit fix

# Snyk
snyk test
snyk monitor

# OWASP Dependency-Check
dependency-check --project myapp --scan ./

# Trivy (para containers)
trivy image myapp:latest

Automation en CI/CD:

text# GitHub Actions
name: Security Scan
on: [push, pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      - name: Fail on high severity
        run: snyk test --severity-threshold=high

A07: Identification and Authentication Failures

Qué es: Fallas en verificación de identidad, gestión de sesiones, autenticación; permite credential stuffing, brute force, session hijacking.

Ejemplos:

1. Passwords débiles permitidos

python# ❌ VULNERABLE: Sin requisitos de complejidad
def register(username, password):
    if len(password) < 6:
        raise ValueError("Password muy corta")
    user = User(username=username, password=hash(password))
    db.save(user)

# ✅ SEGURO: Validación robusta
import re
from zxcvbn import zxcvbn  # Password strength estimator

def validate_password(password):
    errors = []
    
    if len(password) < 12:
        errors.append("Mínimo 12 caracteres")
    
    if not re.search(r'[A-Z]', password):
        errors.append("Debe contener mayúsculas")
    
    if not re.search(r'[a-z]', password):
        errors.append("Debe contener minúsculas")
    
    if not re.search(r'\d', password):
        errors.append("Debe contener números")
    
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        errors.append("Debe contener caracteres especiales")
    
    # Validar contra diccionario y patrones comunes
    strength = zxcvbn(password)
    if strength['score'] < 3:  # 0-4, donde 4 es máximo
        errors.append("Password demasiado común o predecible")
    
    return errors

def register(username, password):
    errors = validate_password(password)
    if errors:
        raise ValueError(f"Password inválida: {', '.join(errors)}")
    
    password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))
    user = User(username=username, password_hash=password_hash)
    db.save(user)

2. Session fixation

javascript// ❌ VULNERABLE: Reutilizar session ID después de login
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body.username, req.body.password);
  if (user) {
    req.session.userId = user.id;  // Mantiene mismo session ID
    res.json({ success: true });
  }
});

// ✅ SEGURO: Regenerar session ID después de login
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body.username, req.body.password);
  if (user) {
    req.session.regenerate((err) => {  // Nuevo session ID
      if (err) {
        return res.status(500).json({ error: 'Error de sesión' });
      }
      req.session.userId = user.id;
      res.json({ success: true });
    });
  }
});

3. Ausencia de MFA

javascript// ✅ SEGURO: Implementar TOTP MFA
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// Setup MFA
app.post('/mfa/setup', authMiddleware, async (req, res) => {
  const secret = speakeasy.generateSecret({
    name: `MyApp (${req.user.email})`
  });
  
  await User.updateOne(
    { _id: req.user.id },
    { mfaSecret: secret.base32, mfaEnabled: false }
  );
  
  const qrCode = await QRCode.toDataURL(secret.otpauth_url);
  res.json({ qrCode, secret: secret.base32 });
});

// Verify y activar MFA
app.post('/mfa/verify', authMiddleware, async (req, res) => {
  const { token } = req.body;
  const user = await User.findById(req.user.id);
  
  const verified = speakeasy.totp.verify({
    secret: user.mfaSecret,
    encoding: 'base32',
    token: token
  });
  
  if (verified) {
    await User.updateOne({ _id: user.id }, { mfaEnabled: true });
    res.json({ success: true });
  } else {
    res.status(401).json({ error: 'Código inválido' });
  }
});

// Login con MFA
app.post('/login', async (req, res) => {
  const { username, password, mfaToken } = req.body;
  const user = await User.findOne({ username });
  
  if (!user || !await bcrypt.compare(password, user.passwordHash)) {
    return res.status(401).json({ error: 'Credenciales inválidas' });
  }
  
  if (user.mfaEnabled) {
    if (!mfaToken) {
      return res.status(403).json({ error: 'MFA token requerido' });
    }
    
    const mfaValid = speakeasy.totp.verify({
      secret: user.mfaSecret,
      encoding: 'base32',
      token: mfaToken
    });
    
    if (!mfaValid) {
      return res.status(401).json({ error: 'MFA token inválido' });
    }
  }
  
  const token = generateJWT(user);
  res.json({ token });
});

Mitigaciones:

  1. MFA obligatorio: especialmente para cuentas admin/privilegiadas
  2. Password policies: longitud mínima 12, complejidad, no reutilización
  3. Account lockout: bloqueo temporal después de N intentos fallidos
  4. Session management: timeouts, regeneración de IDs, secure cookies
  5. OAuth/OIDC: delegar autenticación a providers confiables

A08: Software and Data Integrity Failures

Qué es: Nueva categoría enfocada en supply chain attacks; código/datos sin verificación de integridad pueden ser comprometidos.

Ejemplos:

1. Dependencias sin verificación de integridad

xml<!-- ❌ VULNERABLE: CDN sin SRI -->
<script src="https://cdn.example.com/jquery-3.6.0.min.js"></script>

<!-- ✅ SEGURO: Subresource Integrity (SRI) -->
<script 
  src="https://cdn.example.com/jquery-3.6.0.min.js"
  integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
  crossorigin="anonymous">
</script>

2. CI/CD pipeline sin verificación

text# ❌ VULNERABLE: Pipeline sin signing
steps:
  - name: Build
    run: npm run build
  - name: Deploy
    run: aws s3 sync ./dist s3://my-bucket

# ✅ SEGURO: Verificar artifacts con firma digital
steps:
  - name: Build
    run: npm run build
  
  - name: Sign artifact
    run: |
      cosign sign-blob --key cosign.key dist/bundle.js > bundle.js.sig
  
  - name: Verify signature
    run: |
      cosign verify-blob --key cosign.pub --signature bundle.js.sig dist/bundle.js
  
  - name: Deploy
    run: aws s3 sync ./dist s3://my-bucket

3. Deserialization insegura

python# ❌ VULNERABLE: pickle permite arbitrary code execution
import pickle

def load_user_data(data):
    return pickle.loads(data)  # RCE si data es maliciosa

# ✅ SEGURO: Usar JSON o validar estructura
import json
from jsonschema import validate

user_schema = {
    "type": "object",
    "properties": {
        "username": {"type": "string"},
        "email": {"type": "string", "format": "email"}
    },
    "required": ["username", "email"]
}

def load_user_data(data):
    try:
        user = json.loads(data)
        validate(instance=user, schema=user_schema)
        return user
    except (json.JSONDecodeError, ValidationError) as e:
        raise ValueError(f"Datos inválidos: {e}")

Mitigaciones:

  1. Software signing: firmar releases con GPG/code signing certificates
  2. SRI para dependencies: Subresource Integrity en CDNs
  3. SBOM: Software Bill of Materials para auditoría de supply chain
  4. Verificar checksums: validar integridad de packages descargados
  5. Secure pipelines: protect CI/CD con secrets management, branch protection

A09: Security Logging and Monitoring Failures

Qué es: Insuficiente logging, monitoreo y alertas; imposibilita detectar brechas en tiempo útil.

Impacto: Tiempo promedio de detección 287 días sin logging adecuado.

Qué loggear:

javascript// ✅ SEGURO: Logging comprehensivo
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'security.log' })
  ]
});

// Login attempts
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  
  logger.info('Login attempt', {
    username,
    ip: req.ip,
    userAgent: req.get('user-agent'),
    timestamp: new Date().toISOString()
  });
  
  const user = await authenticate(username, password);
  
  if (user) {
    logger.info('Login successful', { username, userId: user.id });
    return res.json({ token: generateToken(user) });
  } else {
    logger.warn('Login failed', { username, ip: req.ip });
    return res.status(401).json({ error: 'Invalid credentials' });
  }
});

// Access to sensitive resources
app.get('/api/customers/:id', authMiddleware, async (req, res) => {
  logger.info('Customer data access', {
    userId: req.user.id,
    customerId: req.params.id,
    action: 'READ',
    timestamp: new Date().toISOString()
  });
  
  // ... fetch customer data
});

// Failed authorization
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    logger.warn('Unauthorized access attempt', {
      userId: req.user?.id || 'anonymous',
      path: req.path,
      method: req.method,
      ip: req.ip
    });
  }
  
  next(err);
});

Mitigaciones:

  1. Log security events: logins, authorization failures, input validation errors
  2. Centralized logging: ELK, Splunk, Datadog, CloudWatch
  3. Alerting: notificaciones automáticas para eventos críticos
  4. Log retention: mantener logs suficiente tiempo para forensics (90+ días)
  5. Protect logs: inmutables, cifrados, acceso restringido

A10: Server-Side Request Forgery (SSRF)

Qué es: Aplicación hace requests HTTP a URLs controladas por atacante; puede acceder a servicios internos, metadata endpoints.

Nueva en Top 10: Prevalencia aumentó con arquitecturas serverless y microservicios.

Ejemplo de explotación:

python# ❌ VULNERABLE: Fetch URL sin validación
@app.route('/fetch')
def fetch_url():
    url = request.args.get('url')
    response = requests.get(url)  # SSRF: puede acceder metadata AWS
    return response.text

# Exploit: /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# ✅ SEGURO: Whitelist + validación
import urllib.parse
import ipaddress

ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']

def is_safe_url(url):
    try:
        parsed = urllib.parse.urlparse(url)
        
        # Solo HTTPS
        if parsed.scheme != 'https':
            return False
        
        # Whitelist de dominios
        if parsed.hostname not in ALLOWED_DOMAINS:
            return False
        
        # Resolver DNS y verificar que no es IP privada
        import socket
        ip = socket.gethostbyname(parsed.hostname)
        ip_obj = ipaddress.ip_address(ip)
        
        if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
            return False
        
        return True
    except Exception:
        return False

@app.route('/fetch')
def fetch_url():
    url = request.args.get('url')
    
    if not is_safe_url(url):
        return jsonify({'error': 'URL no permitida'}), 403
    
    try:
        response = requests.get(url, timeout=5)
        return response.text
    except requests.RequestException as e:
        return jsonify({'error': 'Error fetching URL'}), 500

Mitigaciones:

  1. Whitelist: permitir solo dominios específicos
  2. Validar esquema: solo https://
  3. Bloquear IPs privadas: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.169.254
  4. Network segmentation: servicios internos en red aislada
  5. Disable redirects: prevenir bypass vía redirecciones

Checklist de Implementación

Semana 1: Quick Wins

  •  Habilitar security headers (CSP, HSTS, X-Frame-Options)
  •  Implementar rate limiting en login/API endpoints
  •  Configurar MFA para cuentas admin
  •  Scan de dependencias con npm audit/Snyk

Semana 2: Access Control

  •  Auditar endpoints sin autorización
  •  Implementar RBAC o ABAC
  •  Validar ownership en cada request a recursos
  •  Rate limiting y account lockout

Semana 3: Injection Prevention

  •  Migrar queries a prepared statements/ORM
  •  Validación de inputs con whitelisting
  •  Escape de outputs
  •  WAF con reglas anti-injection

Semana 4: Monitoring & Logging

  •  Centralized logging (ELK, Splunk)
  •  Alertas para eventos críticos
  •  SIEM para correlación
  •  Incident response playbooks

Herramientas de Seguridad Esenciales

SAST (Static Application Security Testing):

  • SonarQube
  • Semgrep
  • Checkmarx
  • Fortify

DAST (Dynamic Application Security Testing):

  • OWASP ZAP
  • Burp Suite
  • Acunetix
  • Netsparker

SCA (Software Composition Analysis):

  • Snyk
  • WhiteSource
  • Black Duck
  • Dependabot

Runtime Protection:

  • WAF: ModSecurity, Cloudflare WAF, AWS WAF
  • RASP: Contrast Security, Sqreen

Conclusión: Seguridad como Proceso Continuo

Las vulnerabilidades del OWASP Top 10 no son bugs ocasionales sino patrones sistemáticos que persisten porque equipos priorizan features sobre seguridad, carecen de training adecuado o implementan seguridad como afterthought. La versión 2025 del Top 10 refleja evolución de amenazas —supply chain, SSRF, insecure design— pero los fundamentos siguen siendo los mismos: validar inputs, proteger outputs, implementar least privilege, loggear todo y asumir que serás atacado.

No necesitas implementar todo simultáneamente; prioriza basándote en riesgo real. Empieza con quick wins de alto impacto (security headers, MFA, dependency scanning), continúa con access control robusto y prevención de injection, y finalmente implementa monitoring comprehensivo. Usa este artículo como checklist operativo, no como documento para archivar.

La seguridad no es estado final; es práctica continua de identificar, priorizar y remediar vulnerabilidades antes de que atacantes las exploten. Implementa scanning automatizado en CI/CD, capacita a tu equipo en secure coding, realiza code reviews con enfoque en seguridad y mide progreso con métricas concretas.

El OWASP Top 10 es tu mapa; tu responsabilidad es ejecutar el recorrido. Empieza hoy.

Deja un comentario