Qué es OWASP Top 10?

OWASP (Open Web Application Security Project) pública periodicamente una lista de las 10 vulnerabilidades más críticas en aplicaciones web. Es el estandar de referencia para seguridad web en la industria.

Esta guia cubre cada vulnerabilidad con ejemplos de código vulnerable y su versión segura.

A01: Broken Access Control

El control de acceso roto es la vulnerabilidad número uno. Ocurre cuando los usuarios pueden actuar fuera de sus permisos.

Ejemplos comunes

  • Acceder a cuentas de otros usuarios cambiando el ID en la URL
  • Elevar privilegios de usuario normal a admin
  • Manipular tokens o cookies para saltarse verificaciones

Código vulnerable

// VULNERABLE: no verifica que el usuario sea el dueno del recurso
app.get('/api/users/:id/profile', async (req, res) => {
  const profile = await db.getProfile(req.params.id);
  res.json(profile); // Cualquier usuario puede ver cualquier perfil
});

Código seguro

// SEGURO: verificar autorización
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  const requestedId = req.params.id;
  const authenticatedUserId: string = req.user.id;

  if (requestedId !== authenticatedUserId && !req.user.isAdmin) {
    return res.status(403).json({ error: 'No autorizado' });
  }

  const profile = await db.getProfile(requestedId);
  res.json(profile);
});

Prevencion

  • Denegar acceso por defecto
  • Implementar control de acceso en el servidor, nunca solo en el frontend
  • Registrar y alertar sobre fallas de control de acceso
  • Limitar rate de peticiones a APIs

A02: Cryptographic Failures

Fallas criptograficas incluyen transmitir datos sensibles en texto plano, usar algoritmos obsoletos o gestionar mal las claves.

Errores comunes

// VULNERABLE: hashear con MD5 o SHA1
import crypto from 'crypto';
const hash = crypto.createHash('md5').update(password).digest('hex');

// VULNERABLE: almacenar datos sensibles sin cifrar
localStorage.setItem('creditCard', '4532-1234-5678-9012');

// VULNERABLE: transmitir datos sensibles en la URL
fetch(`/api/reset-password?token=${token}&newPassword=${password}`);

Solucion correcta

  • Usa bcrypt, scrypt o Argon2 para passwords
  • Cifra datos sensibles en reposo con AES-256
  • Usa HTTPS para todo el trafico
  • No transmitas datos sensibles en URLs o query parameters
  • Genera claves y tokens con funciones criptograficamente seguras

A03: Injection

Las inyecciones ocurren cuando datos no confiables se envian a un interprete como parte de un comando o consulta.

Tipos de inyección

Tipo Vector Ejemplo
SQL Injection Consultas SQL ' OR 1=1 --
NoSQL Injection Consultas MongoDB {"$gt": ""}
Command Injection Comandos del SO ; rm -rf /
LDAP Injection Consultas LDAP `)(uid=))(
Template Injection Motores de plantillas {{constructor.constructor('return this')()}}

Prevencion universal

  1. Usar consultas parametrizadas siempre
  2. Validar y sanitizar todo input
  3. Usar ORMs que parametricen automaticamente
  4. Aplicar el principio de mínimo privilegio en la base de datos

A04: Insecure Design

El diseño inseguro se refiere a fallas fundamentales en la arquitectura de la aplicación que no se pueden solucionar con mejor implementación.

Ejemplo

Flujo de recuperacion de password INSEGURO:
1. Usuario pide reset
2. Se envia pregunta de seguridad (fácil de adivinar)
3. Se muestra la password en la pantalla

Flujo SEGURO:
1. Usuario pide reset con su email
2. Se genera un token criptografico con expiracion (30 min)
3. Se envia link único al email registrado
4. El token es de un solo uso y se invalida al usarse

Principios de diseño seguro

  • Modela las amenazas antes de escribir código
  • Aplica defense in depth (multiples capas de seguridad)
  • Separa responsabilidades y permisos
  • Falla de forma segura (deny by default)

A05: Security Misconfiguration

La configuración incorrecta es una de las vulnerabilidades más frecuentes y fáciles de explotar.

Checklist de configuración

// Verificar configuración de seguridad en Express
interface SecurityConfig {
  helmet: boolean;
  cors: {
    origin: string[];
    credentials: boolean;
  };
  rateLimit: {
    windowMs: number;
    max: number;
  };
  production: {
    stackTraces: boolean;
    debugMode: boolean;
    defaultCredentials: boolean;
  };
}

const secureConfig: SecurityConfig = {
  helmet: true,
  cors: {
    origin: ['https://tudominio.com'],  // NO usar '*'
    credentials: true
  },
  rateLimit: {
    windowMs: 15 * 60 * 1000,
    max: 100
  },
  production: {
    stackTraces: false,    // NUNCA en producción
    debugMode: false,      // NUNCA en producción
    defaultCredentials: false // Cambiar SIEMPRE
  }
};

A06: Vulnerable and Outdated Components

Usar dependencias con vulnerabilidades conocidas es como dejar la puerta abierta.

Estrategia de mitigacion

# Auditar dependencias regularmente
npm audit

# Auditar solo producción
npm audit --production

# Corregir automaticamente lo posible
npm audit fix

# Ver dependencias desactualizadas
npm outdated

# Herramientas automaticas
# Activar Dependabot o Renovate en tu repositorio

Politica recomendada

  • Auditar dependencias en cada build de CI
  • Actualizar dependencias con vulnerabilidades críticas en 24 horas
  • Actualizar dependencias con vulnerabilidades altas en 1 semana
  • Revisar dependencias transitivas (las que tus dependencias usan)

A07: Identification and Authentication Failures

Las fallas de autenticación permiten a los atacantes comprometer passwords, tokens o explotar fallas de implementación.

Requisitos minimos de password

interface PasswordValidation {
  isValid: boolean;
  errors: string[];
}

function validatePassword(password: string): PasswordValidation {
  const errors: string[] = [];

  if (password.length < 12) {
    errors.push('Minimo 12 caracteres');
  }
  if (password.length > 128) {
    errors.push('Maximo 128 caracteres');
  }
  if (!/[A-Z]/.test(password)) {
    errors.push('Al menos una mayuscula');
  }
  if (!/[a-z]/.test(password)) {
    errors.push('Al menos una minuscula');
  }
  if (!/[0-9]/.test(password)) {
    errors.push('Al menos un número');
  }
  if (!/[^A-Za-z0-9]/.test(password)) {
    errors.push('Al menos un caracter especial');
  }

  return { isValid: errors.length === 0, errors };
}

A08: Software and Data Integrity Failures

Ocurre cuando el código o la infraestructura no protegen contra violaciones de integridad: pipelines CI/CD inseguros, actualizaciones sin verificación, dependencias de CDN sin integrity checks.

Subresource Integrity (SRI)

<!-- INSEGURO: cargar script de CDN sin verificación -->
<script src="https://cdn.example.com/lib.js"></script>

<!-- SEGURO: con SRI, el navegador verifica el hash -->
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxAh6VgnSY"
  crossorigin="anonymous">
</script>

A09: Security Logging and Monitoring Failures

Sin logs adecuados, no puedes detectar ni responder a ataques.

Qué registrar

interface SecurityLog {
  timestamp: string;
  event: string;
  severity: 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL';
  userId: string | null;
  ip: string;
  details: Record<string, unknown>;
}

function logSecurityEvent(log: SecurityLog): void {
  // Enviar a sistema de logging centralizado
  console.log(JSON.stringify({
    ...log,
    timestamp: new Date().toISOString()
  }));
}

// Eventos críticos que DEBES registrar:
// - Intentos de login fallidos
// - Cambios de password/email
// - Fallas de control de acceso (403)
// - Errores de validación de input sospechosos
// - Cambios de permisos/roles

A10: Server-Side Request Forgery (SSRF)

SSRF ocurre cuando un atacante puede hacer que tu servidor envie peticiones a destinos arbitrarios.

Prevencion

// VULNERABLE: el usuario controla la URL
app.get('/api/fetch-url', async (req, res) => {
  const url = req.query.url as string;
  const response = await fetch(url); // Puede acceder a red interna!
  res.json(await response.json());
});

// SEGURO: validar y restringir URLs
function isUrlAllowed(url: string): boolean {
  try {
    const parsed = new URL(url);

    // Solo HTTPS
    if (parsed.protocol !== 'https:') return false;

    // Bloquear IPs internas
    const blockedPatterns = [
      /^localhost$/i,
      /^127\./,
      /^10\./,
      /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
      /^192\.168\./,
      /^0\./,
      /^169\.254\./  // Link-local
    ];

    if (blockedPatterns.some(p => p.test(parsed.hostname))) return false;

    // Lista blanca de dominios permitidos
    const allowedDomains = ['api.github.com', 'api.example.com'];
    return allowedDomains.includes(parsed.hostname);
  } catch {
    return false;
  }
}

Conclusion

El OWASP Top 10 no es solo una lista: es un marco de referencia para construir aplicaciones seguras. Cada vulnerabilidad tiene contramedidas claras y probadas.

La clave esta en integrar la seguridad desde el diseño, no como un parche posterior. Usa esta guia como checklist en cada proyecto y revisa periodicamente las actualizaciones de OWASP.