Volver al blog
Seguridad Web 5 Feb 2026 · 25 min de lectura

OWASP Top 10 2025: Riesgos Críticos para Aplicaciones Web

H

Hakobjan Dev Team

Ingenieros de Seguridad y Software

El Top 10 de OWASP es la lista más autorizada de riesgos de seguridad para aplicaciones web. La edición 2025 trae cambios importantes: broken access control permanece en #1, pero la SSRF y los riesgos de supply chain de software han aumentado significativamente. En este artículo completo cubrimos las diez categorías con ejemplos prácticos y soluciones.



A01: Broken Access Control

Broken Access Control ha sido la vulnerabilidad más común durante años. Se reduce a no hacer cumplir que los usuarios solo puedan hacer lo que están autorizados. Piensa en IDOR, escalación de privilegios y evasión de controles de acceso.

La solución comienza con un framework de autorización robusto: usa RBAC/ABAC, valida cada solicitud del lado del servidor y nunca confíes solo en verificaciones del lado del cliente. Deny by default: si un usuario no tiene permiso explícito, se deniega el acceso.

javascript
// Ejemplo: vulnerabilidad IDOR
// MAL - usuario puede consultar cualquier orden
app.get('/api/orders/:id', auth, async (req, res) => {
  const order = await Order.findById(req.params.id);
  res.json(order); // ¡Sin verificación!
});

// BIEN - agregar verificación de autorización
app.get('/api/orders/:id', auth, async (req, res) => {
  const order = await Order.findOne({
    _id: req.params.id,
    userId: req.user.id
  });
  if (!order) return res.status(404).json({ error: 'No encontrado' });
  res.json(order);
});

A02: Cryptographic Failures

Cryptographic Failures (anteriormente "Sensitive Data Exposure") trata sobre el fallo al proteger datos en tránsito y en reposo. Piensa en contraseñas almacenadas en texto plano, uso de algoritmos hash obsoletos como MD5 o SHA1, falta de TLS o claves de cifrado hardcoded en el código fuente.

Usa siempre algoritmos fuertes: bcrypt o Argon2 para contraseñas, AES-256-GCM para cifrado, TLS 1.3 para datos en tránsito. Nunca almacenes datos sensibles innecesariamente y clasifica tus datos.

javascript
// MAL - almacenar contraseña con MD5
const hash = crypto.createHash('md5').update(password).digest('hex');

// BIEN - usar bcrypt con salt rounds
const bcrypt = require('bcrypt');
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);

// Verificación
const isValid = await bcrypt.compare(inputPassword, storedHash);

A03: Injection

Los ataques de inyección ocurren cuando datos no confiables se envían a un intérprete como parte de un comando o consulta. SQL injection es el más conocido, pero NoSQL injection, OS command injection y LDAP injection también entran en esta categoría.

Siempre usa consultas parametrizadas o prepared statements. Usa ORMs que apliquen escaping automáticamente. Valida y sanitiza toda la entrada del lado del servidor.

javascript
// MAL - vulnerabilidad de inyección SQL
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;
db.query(query);

// BIEN - consulta parametrizada
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [req.params.id]);

// Ejemplo ORM (Sequelize)
const user = await User.findOne({
  where: { id: req.params.id }
});

A04: Insecure Design

Insecure Design es un concepto amplio que apunta a defectos fundamentales de diseño. La diferencia con los bugs de implementación es importante: una arquitectura insegura perfectamente implementada sigue siendo insegura. Piensa en un flujo de reset de contraseña usando preguntas de seguridad, o una app de e-commerce sin rate limiting en códigos de cupón.

La solución: usa threat modeling temprano en el proceso de desarrollo. Implementa patrones de diseño seguros como defensa en profundidad, menor privilegio y valores predeterminados seguros.

javascript
// MAL - sin rate limiting en reset de contraseña
app.post('/api/reset-password', async (req, res) => {
  const user = await User.findByEmail(req.body.email);
  if (user) sendResetEmail(user); // ¡Sin límite!
  res.json({ message: 'Revisa tu email' });
});

// BIEN - rate limiting + bloqueo de cuenta
const rateLimit = require('express-rate-limit');
const resetLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 3,
  message: 'Demasiadas solicitudes, intente más tarde'
});
app.post('/api/reset-password', resetLimiter, async (req, res) => {
  // ... implementación segura
});

A05: Security Misconfiguration

Security Misconfiguration es el problema más extendido. Abarca la falta de hardening de seguridad, funcionalidades innecesarias habilitadas, cuentas y contraseñas por defecto, mensajes de error demasiado informativos y headers HTTP de seguridad mal configurados.

Automatiza tu configuración de servidor con Infrastructure as Code. Elimina funcionalidades, puertos y servicios innecesarios. Implementa headers de seguridad (CSP, HSTS, X-Frame-Options).

yaml
# Configuración de headers de seguridad Nginx
server {
  # Strict Transport Security
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

  # Content Security Policy
  add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;

  # Prevenir clickjacking
  add_header X-Frame-Options "SAMEORIGIN" always;

  # Prevenir MIME type sniffing
  add_header X-Content-Type-Options "nosniff" always;

  # Ocultar versión del servidor
  server_tokens off;
}

A06: Vulnerable & Outdated Components

Las aplicaciones modernas contienen decenas a cientos de dependencias. Cada una puede contener vulnerabilidades. En 2024 se publicaron más de 29.000 nuevos CVEs. Rastrearlos sin herramientas es imposible.

Usa herramientas SCA como Snyk, npm audit o Dependabot. Elimina dependencias no utilizadas. Fija tus versiones y actualiza regularmente. Monitorea nuevos CVEs que afecten tu stack y ten una estrategia de parches lista.

bash
# Verificar vulnerabilidades en tu proyecto
npm audit

# Corregir automáticamente paquetes vulnerables
npm audit fix

# Escaneo Snyk para análisis más profundo
npx snyk test

# Mostrar paquetes desactualizados
npm outdated

# Config Dependabot (.github/dependabot.yml)
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"

A07: Identification & Authentication Failures

Authentication Failures incluyen ataques de fuerza bruta, credential stuffing, políticas de contraseñas débiles, falta de autenticación multifactor (MFA) y gestión pobre de sesiones. Session fixation, session hijacking y no invalidar sesiones al cerrar sesión son errores comunes.

Implementa MFA donde sea posible, usa políticas de contraseñas fuertes y frameworks de gestión de sesiones probados. Limita intentos de login e implementa mecanismos de bloqueo de cuenta.

javascript
// Login con protección contra fuerza bruta
const loginAttempts = new Map();

app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  const attempts = loginAttempts.get(email) || { count: 0, lockUntil: 0 };

  if (attempts.lockUntil > Date.now()) {
    return res.status(429).json({ error: 'Cuenta bloqueada temporalmente' });
  }

  const user = await User.findByEmail(email);
  const valid = user && await bcrypt.compare(password, user.password);

  if (!valid) {
    attempts.count++;
    if (attempts.count >= 5) {
      attempts.lockUntil = Date.now() + 15 * 60 * 1000;
    }
    loginAttempts.set(email, attempts);
    return res.status(401).json({ error: 'Credenciales inválidas' });
  }

  loginAttempts.delete(email);
  const token = generateJWT(user);
  res.json({ token });
});

A08: Software & Data Integrity Failures

Esta categoría se enfoca en suposiciones sobre actualizaciones de software, datos críticos y pipelines CI/CD sin verificación de integridad. Piensa en el ataque SolarWinds: los atacantes comprometieron el sistema de build y agregaron malware a actualizaciones legítimas instaladas por miles de organizaciones.

Usa Subresource Integrity (SRI) para scripts externos. Firma digitalmente los releases. Asegura tu pipeline CI/CD con code signing, pipeline-as-code y entornos de build separados.

javascript
// Subresource Integrity (SRI) para scripts externos
<script
  src="https://cdn.example.com/lib.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxAh6VgnSY"
  crossorigin="anonymous">
</script>

// Verificación de lock file en pipeline CI
// Siempre commitear package-lock.json o yarn.lock
// Usar npm ci en vez de npm install en CI
npm ci --ignore-scripts

// Verificar integridad de paquetes
npm audit signatures

A09: Security Logging & Monitoring Failures

Sin logging y monitoreo adecuados, los ataques solo se descubren cuando es demasiado tarde. Los estudios muestran que el tiempo promedio para detectar una brecha es de más de 200 días. Muchas organizaciones no registran suficientes eventos o no monitorean activamente sus logs.

Registra todos los eventos de autenticación, fallos de control de acceso y errores del lado del servidor. Centraliza logs con herramientas como ELK Stack o Grafana Loki. Configura alertas para patrones sospechosos.

javascript
// Logging de seguridad estructurado con Winston
const winston = require('winston');

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

// Registrar intentos de login fallidos
app.post('/api/login', async (req, res) => {
  const { email } = req.body;
  const success = await authenticate(email, req.body.password);

  securityLogger.info({
    event: success ? 'LOGIN_SUCCESS' : 'LOGIN_FAILURE',
    email,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    timestamp: new Date().toISOString()
  });
});

A10: Server-Side Request Forgery (SSRF)

Los ataques SSRF engañan al servidor para que haga requests a ubicaciones no intencionadas. En entornos cloud esto es especialmente peligroso: los atacantes pueden acceder al endpoint de metadata (ej. http://169.254.169.254 en AWS) para robar credenciales. La brecha de Capital One de 2019 fue un ataque SSRF que filtró 100 millones de registros.

Valida y sanitiza todas las URLs proporcionadas por el usuario. Usa una allowlist de dominios permitidos. Bloquea requests a rangos IP privados y endpoints de metadata cloud.

javascript
// MAL - URL del usuario sin validación
app.post('/api/fetch-url', async (req, res) => {
  const response = await fetch(req.body.url); // ¡SSRF!
  res.json(await response.json());
});

// BIEN - validación de URL con allowlist
const allowedDomains = ['api.example.com', 'cdn.example.com'];

function isAllowedUrl(urlString) {
  try {
    const url = new URL(urlString);
    if (!['http:', 'https:'].includes(url.protocol)) return false;
    if (!allowedDomains.includes(url.hostname)) return false;
    const ip = url.hostname;
    if (ip.startsWith('10.') || ip.startsWith('192.168.') ||
        ip.startsWith('169.254.') || ip === 'localhost') return false;
    return true;
  } catch { return false; }
}

app.post('/api/fetch-url', async (req, res) => {
  if (!isAllowedUrl(req.body.url)) {
    return res.status(400).json({ error: 'URL no permitida' });
  }
  const response = await fetch(req.body.url);
  res.json(await response.json());
});

Estrategia de Seguridad Práctica

Usa el Top 10 de OWASP como punto de partida para tu hoja de ruta de seguridad, no como destino final. Comienza con un modelo de amenazas para tu aplicación: ¿qué datos son más valiosos? ¿Quiénes son los atacantes potenciales? ¿Qué vectores de ataque son más probables?

Integra pruebas de seguridad en tu pipeline CI/CD con herramientas como OWASP ZAP (DAST), Semgrep (SAST) y Snyk (SCA). Automatiza lo que puedas y programa pruebas de penetración regulares para lo que no puedas automatizar.

Consejo Pro

Ejecuta un escaneo de seguridad automatizado después de cada despliegue. Herramientas como OWASP ZAP pueden ejecutarse como un paso en tu pipeline CI/CD y reportar automáticamente vulnerabilidades antes de que el código llegue a producción.

Compartir este artículo

Todos los artículos