Los riesgos más comunes en apps web y cómo reducirlos con buenas prácticas.
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.
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:
Problema: Usuarios pueden acceder a recursos o datos que no deben (facturas de otros, configuración de admin, reportes confidenciales).
Ejemplos de vulnerabilidad:
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:
Problema: Datos sensibles (contraseñas, DNI, tarjetas de crédito) no están cifrados o se transmiten sin protección.
Ejemplos de vulnerabilidad:
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:
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: DENY");
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) => { ... });
Problema: La seguridad no se consideró desde el diseño. Faltan mecanismos como rate limiting, validación de entrada, recuperación ante fallos.
Ejemplos:
Mitigación:
// 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.");
}
Problema: Credenciales fáciles de adivinar, sesiones mal manejadas, MFA ausente.
Vulnerabilidades comunes:
Buenas prácticas:
// 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;
}
Problema: Dependencias vulnerables, actualizaciones falsas, datos modificados en tránsito.
Ejemplos:
Mitigación:
Problema: No verificar identidad real del usuario o permitir cambios sin confirmación.
Ejemplos:
Mitigación:
Problema: No hay registro de quién hizo qué, por lo que no puedes detectar ni investigar ataques.
Qué NO logguear:
Qué SÍ logguear:
// 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]));
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:
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);
}
Problema: Inyectar JavaScript malicioso en la página de otros usuarios (robar cookies, phishing, malware).
Tipos de XSS:
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:
// ✅ 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'");
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:
Impacto:
Solución implementada:
Resultado: 6 meses después, 0 incidentes de seguridad. Certificación SOC 2 obtenida (mejora reputación y atrae clientes empresariales).
Stack mínimo:
| Área | Checklist | Prioridad |
|---|---|---|
| Transporte |
|
Crítica |
| Autenticación |
|
Crítica |
| Datos |
|
Crítica |
| Input Validation |
|
Crítica |
| Control de Acceso |
|
Crítica |
| Dependencias |
|
Alta |
| Logging |
|
Alta |
| Testing |
|
Media |
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.