Blog

Seguridad web: OWASP Top 10 (resumen)

Los riesgos más comunes en apps web y cómo reducirlos con buenas prácticas.

Seguridad web

El OWASP Top 10 es la lista de riesgos más críticos en aplicaciones web, actualizada cada 3-4 años por la Open Worldwide Application Security Project. Esta guía se enfoca en la versión 2021, que prioriza vulnerabilidades por frecuencia e impacto. No necesitas ser un banco para ser objetivo: basta con tener usuarios, formularios, pagos o un panel administrativo.

¿Por qué el OWASP Top 10 es importante?

Según datos de plataformas de reporte de vulnerabilidades (2024), el 81% de los ataques web se dirigen a vulnerabilidades dentro del OWASP Top 10. Una brecha de seguridad puede costar:

  • $ 4.24M en promedio (por breach según IBM Security 2023).
  • Pérdida de confianza del cliente y multas regulatorias (GDPR: hasta €20M o 4% de ingresos).
  • Tiempo de recuperación: 280 días en promedio.

OWASP Top 10 2021: Detalles y Mitigación

1. Broken Access Control (Control de Acceso Roto)

Problema: Usuarios pueden acceder a recursos o datos que no deben (facturas de otros, configuración de admin, reportes confidenciales).

Ejemplos de vulnerabilidad:

  • Cambiar /usuarios/1/perfil a /usuarios/2/perfil y ver datos de otro usuario.
  • IDs predecibles: /order/1001, /order/1002 sin validar permisos.
  • Roles duros en frontend (usuario ve botón "eliminar" pero no tiene permiso en backend).
  • Cookies de sesión sin expiración o sin validación de origen.

Mitigación (PHP):

// ❌ Inseguro
$user_id = $_GET['id'];
$result = mysqli_query($conn, "SELECT * FROM users WHERE id = $user_id");

// ✅ Seguro
$user_id = $_GET['id'];
if ($_SESSION['user_id'] === (int)$user_id || hasAdminRole($_SESSION['user_id'])) {
    $stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
} else {
    http_response_code(403);
    exit("Acceso denegado");
}

Checklist:

  • Implementar matriz de roles y permisos (RBAC).
  • Validar permisos en backend (nunca confíes en frontend).
  • Usar UUIDs en lugar de IDs secuenciales.
  • Configurar CORS correctamente (no permitir cualquier origen).

2. Cryptographic Failures (Fallos Criptográficos)

Problema: Datos sensibles (contraseñas, DNI, tarjetas de crédito) no están cifrados o se transmiten sin protección.

Ejemplos de vulnerabilidad:

  • Guardar contraseñas en texto plano en BD.
  • HTTP en lugar de HTTPS (datos en tránsito sin cifrar).
  • Usar algoritmos débiles (MD5, SHA1) para hashing.
  • Clave de cifrado hardcodeada o débil.
  • Backups sin cifrar en servidores públicos.

Mitigación (PHP):

// Hashing seguro de contraseñas
$password = $_POST['password'];
$hashed = password_hash($password, PASSWORD_BCRYPT); // Usa bcrypt
mysqli_query($conn, "UPDATE users SET password = ? WHERE id = ?");

// Cifrado de datos sensibles
$plaintext = "4532111111111111"; // Número de tarjeta
$key = bin2hex(random_bytes(32)); // Generar clave aleatoria
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
$encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $key, false, $iv);

// Guardar $encrypted en BD (nunca la clave)

Configurar HTTPS:

  • Obtener certificado SSL/TLS (Let's Encrypt es gratuito).
  • Redirigir HTTP → HTTPS obligatoriamente.
  • Usar headers de seguridad:
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: DENY");

3. Injection (Inyecciones SQL, NoSQL, etc.)

Problema: Entrada de usuario se interpreta como código, permitiendo manipular queries de BD.

Ejemplo SQL Injection clásico:

// ❌ Vulnerable
$email = $_POST['email'];
$result = mysqli_query($conn, "SELECT * FROM users WHERE email = '$email'");

// Atacante ingresa: ' OR '1'='1
// Query resultante: SELECT * FROM users WHERE email = '' OR '1'='1'
// Resultado: acceso a TODOS los usuarios

Mitigación: Prepared Statements

// ✅ Seguro - PHP/MySQLi
$email = $_POST['email'];
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();

// ✅ Seguro - PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);

// ✅ Seguro - Node.js/MySQL
const query = 'SELECT * FROM users WHERE email = ?';
connection.query(query, [email], (error, results) => { ... });

4. Insecure Design (Diseño Inseguro)

Problema: La seguridad no se consideró desde el diseño. Faltan mecanismos como rate limiting, validación de entrada, recuperación ante fallos.

Ejemplos:

  • Formulario de login sin rate limiting: atacante intenta 1000 contraseñas/segundo.
  • Recuperación de contraseña con preguntas débiles ("¿cuál es tu película favorita?").
  • Carrito de compras modifica precios en frontend: total_price = 1000 → 10 sin validar en backend.
  • Reset de contraseña con token predecible.

Mitigación:

  • Rate Limiting: Máx 5 intentos de login fallidos en 15 minutos.
  • Validación Backend: Nunca confíes en valores del cliente (precio, descuento, cantidad).
  • Recuperación segura: Usar tokens criptográficos con expiración.
  • CAPTCHA después de fallos: Prevenir ataques automatizados.
// Rate limiting simple
$ip = $_SERVER['REMOTE_ADDR'];
$key = "login_attempts_$ip";
$attempts = $redis->incr($key);
if ($attempts === 1) $redis->expire($key, 900); // 15 minutos

if ($attempts > 5) {
    http_response_code(429);
    exit("Demasiados intentos. Intenta más tarde.");
}

5. Broken Authentication (Autenticación Débil)

Problema: Credenciales fáciles de adivinar, sesiones mal manejadas, MFA ausente.

Vulnerabilidades comunes:

  • Contraseña por defecto no cambiada (admin/admin).
  • Sin MFA (Multi-Factor Authentication).
  • Tokens de sesión predecibles o sin expiración.
  • Mensajes de error reveladores: "Usuario no existe" vs "Credenciales inválidas".

Buenas prácticas:

  • Exigir contraseñas fuertes: mín 12 caracteres, mayúscula, número, símbolo.
  • Implementar MFA (Google Authenticator, SMS, Authy).
  • Usar JWT o sesiones con expiración (15-30 min inactividad).
  • Regenerar session ID después de login.
// Configuración segura de sesión
session_set_cookie_params([
    'lifetime' => 1800, // 30 minutos
    'path' => '/',
    'domain' => 'tudominio.com',
    'secure' => true,  // HTTPS only
    'httponly' => true, // No accesible desde JS
    'samesite' => 'Strict'
]);
session_start();

// Regenerar tras login
if ($loginExitoso) {
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user->id;
}

6. Software and Data Integrity Failures (Fallos de Integridad)

Problema: Dependencias vulnerables, actualizaciones falsas, datos modificados en tránsito.

Ejemplos:

  • Librería jQuery 3.2.1 con vulnerabilidad XSS (usar 3.6+).
  • npm install instala paquete malicioso con nombre similar (typosquatting).
  • Update sin verificar firma digital (malware disfrazado).

Mitigación:

  • Usar composer audit (PHP) o npm audit (Node.js).
  • Fijar versiones en composer.json (no usar ^3.0, especificar 3.6.1).
  • Monitorear CVE databases (cvedetails.com, snyk.io).
  • Implementar CI/CD que valide dependencias antes de deploy.

7. Identification and Authentication Failures (Validación de Identificación)

Problema: No verificar identidad real del usuario o permitir cambios sin confirmación.

Ejemplos:

  • Cambiar correo de cuenta sin verificación por email actual.
  • Reset de contraseña sin validar respuesta de seguridad o código enviado.
  • Admin creando usuarios sin verificar dominio corporativo (@empresa.com).

Mitigación:

  • Enviar correo de confirmación para cambios críticos.
  • Usar OTP (One-Time Password) de 6 dígitos.
  • Logs de auditoría: quién cambió qué, cuándo, desde dónde.

8. Security Logging and Monitoring Failures (Falta de Logs)

Problema: No hay registro de quién hizo qué, por lo que no puedes detectar ni investigar ataques.

Qué NO logguear:

  • Contraseñas, tokens, tarjetas de crédito.

Qué SÍ logguear:

  • Login/logout fallidos (IP, timestamp, usuario).
  • Cambios en datos críticos (eliminación de usuario, cambio de rol).
  • Accesos a datos sensibles (reportes financieros).
  • Excepciones y errores del sistema.
// Ejemplo de log de auditoría
function logAudit($action, $entity, $entity_id, $user_id, $details = null) {
    $stmt = $conn->prepare(
        "INSERT INTO audit_logs (action, entity, entity_id, user_id, details, ip, timestamp) 
         VALUES (?, ?, ?, ?, ?, ?, NOW())"
    );
    $ip = $_SERVER['REMOTE_ADDR'];
    $stmt->bind_param("ssisss", $action, $entity, $entity_id, $user_id, $details, $ip);
    $stmt->execute();
}

// Uso
logAudit('DELETE', 'users', $deleted_user_id, $_SESSION['user_id'], 
    json_encode(['email' => $user->email, 'role' => $user->role]));

9. Server-Side Request Forgery (SSRF)

Problema: Tu servidor hace requests a direcciones que el atacante controla o a recursos internos.

Ejemplo:

// ❌ Vulnerable
$url = $_GET['url'];
$content = file_get_contents($url); // Atacante envía http://localhost/admin
// O: http://192.168.1.1 (router interno)
// O: http://169.254.169.254 (metadata service en AWS)

Mitigación:

  • Validar y whitelist URLs permitidas.
  • Bloquear direcciones internas (127.0.0.1, 192.168.*, 10.*).
  • Usar cURL con timeouts y SSL verification.
function fetchURL($url) {
    // Validar formato URL
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return false;
    }
    
    // Bloquear IPs privadas
    $host = parse_url($url, PHP_URL_HOST);
    $ip = gethostbyname($host);
    
    $private_ips = ['127.0.0.1', '0.0.0.0'];
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        return false;
    }
    
    // Fetch seguro
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    return curl_exec($ch);
}

10. Cross-Site Scripting (XSS)

Problema: Inyectar JavaScript malicioso en la página de otros usuarios (robar cookies, phishing, malware).

Tipos de XSS:

  • Stored (Persistente): Guardar script en BD y ejecutar para todos.
  • Reflected: URL con script: example.com/?name=<script>alert('xss')</script>
  • DOM-Based: JavaScript en cliente modifica DOM de forma insegura.

Ejemplo vulnerable:

// ❌ Vulnerable - Stored XSS
$comment = $_POST['comment'];
mysqli_query($conn, "INSERT INTO comments (text) VALUES ('$comment')");

// Mostrar (más tarde):
$result = mysqli_query($conn, "SELECT text FROM comments");
while ($row = mysqli_fetch_assoc($result)) {
    echo $row['text']; // Si contiene alert('hacked')
}

Mitigación:

  • Escapar HTML: htmlspecialchars() en PHP, .textContent en JS.
  • Content Security Policy (CSP) header.
  • No usar eval(), innerHTML sin validación, innerHTML dinámico.
// ✅ Seguro - Escapar salida
$result = mysqli_query($conn, "SELECT text FROM comments");
while ($row = mysqli_fetch_assoc($result)) {
    echo htmlspecialchars($row['text'], ENT_QUOTES, 'UTF-8');
}

// Header CSP
header("Content-Security-Policy: default-src 'self'; script-src 'self' cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'");

Caso de Estudio: PYME de Confecciones

Situación: Plataforma de ventas online con acceso a reportes de inventario y pedidos. Generaba $150K/mes pero sufrió una brecha de seguridad.

Vulnerabilidades encontradas:

  1. SQL Injection en búsqueda: /productos?nombre=pepas' OR '1'='1 mostraba todos los productos.
  2. Broken Access Control: /api/pedidos/123 modificable a 124, 125... acceso a pedidos de otros clientes.
  3. Sin HTTPS: Credenciales en tránsito sin cifrar.
  4. Sin logs: No sabían quién había borrado datos.

Impacto:

  • 30,000 emails de clientes robados.
  • Sitio down por 8 días (pérdida: $40K).
  • Multa por GDPR: $12K.
  • Costo de remediación: $15K.

Solución implementada:

  • Prepared statements para todas las queries.
  • RBAC: admin, gerente, vendedor, cliente con permisos específicos.
  • HTTPS + Let's Encrypt.
  • Logs en ELK Stack (Elasticsearch, Logstash, Kibana).
  • Rate limiting en login y API.
  • Auditoría de seguridad cada 6 meses.

Resultado: 6 meses después, 0 incidentes de seguridad. Certificación SOC 2 obtenida (mejora reputación y atrae clientes empresariales).

Arquitectura de Seguridad Recomendada para PYMES

Stack mínimo:

  • Frontend: Framework moderno (React, Vue, Angular) con CSP headers.
  • Backend: API con autenticación JWT/OAuth 2.0, rate limiting, validación estricta.
  • BD: Cifrada en reposo, backups automáticos (idealmente en 2 ubicaciones).
  • Infraestructura: WAF (Web Application Firewall), SSL/TLS, DDoS protection.
  • Monitoreo: ELK Stack, Datadog o similar para alertas en tiempo real.

Checklist Completo de Seguridad Web

Área Checklist Prioridad
Transporte
  • HTTPS obligatorio (TLS 1.2+)
  • HSTS header (includeSubDomains)
  • Certificado válido y renovado
Crítica
Autenticación
  • Contraseñas hasheadas (bcrypt/argon2)
  • MFA implementado
  • Sesión con expiración + regeneración
  • Rate limiting en login
Crítica
Datos
  • Datos sensibles cifrados
  • Backups encriptados
  • Ley de retención de datos (GDPR)
  • Anonimización de PII en logs
Crítica
Input Validation
  • Prepared statements (SQL)
  • Escape de HTML (XSS)
  • Whitelist de caracteres
  • Limites de longitud
Crítica
Control de Acceso
  • RBAC implementado
  • Validar permisos en backend
  • UUIDs en lugar de IDs secuenciales
  • Auditoría de accesos
Crítica
Dependencias
  • Audit regular (npm/composer)
  • Versiones fijas en lock files
  • Monitorear CVE databases
  • CI/CD con validación
Alta
Logging
  • Logs centralizados
  • Retención mínimo 90 días
  • Alertas en eventos críticos
  • Auditoría de cambios
Alta
Testing
  • Pruebas SAST (análisis estático)
  • DAST (penetration testing)
  • Auditoría de seguridad anual
  • Red team exercise
Media

Herramientas Recomendadas

  • SAST (Análisis estático): SonarQube, Checkmarx, Semgrep.
  • Dependencias: Snyk, Dependabot, WhiteSource.
  • Testing: OWASP ZAP, Burp Suite Community, Nuclei.
  • Logging: ELK Stack, Datadog, Splunk.
  • Monitoreo: Prometheus + Grafana, New Relic.

Plan de Acción para tu PYME

Fase 1 (Mes 1): Auditoría de seguridad (identificar top 5 riesgos).

Fase 2 (Meses 2-3): Implementar fixes críticos (SQL injection, XSS, HTTPS).

Fase 3 (Meses 4-6): MFA, logging centralizado, testing.

Fase 4 (Continuo): Monitoreo, actualizaciones de dependencias, red team.

La implementación de estos controles requiere inversión en herramientas y recursos, pero es fundamental para proteger tu negocio contra amenazas en línea.