Seguridad de aplicaciones JavaScript: top 10 vulnerabilidades
Las aplicaciones JavaScript modernas en Node.js procesan enormes volúmenes de datos sensibles, lo que las convierte en un objetivo atractivo para los atacantes. Según OWASP, la mayoría de las intrusiones ocurren debido a errores típicos que se pueden prevenir. En este artículo analizaremos las 10 vulnerabilidades más comunes, proporcionaremos ejemplos reales de código y mostraremos cómo proteger su aplicación.
1. XSS (Cross-Site Scripting)
El scripting entre sitios es una de las amenazas más antiguas, pero sigue siendo relevante. El atacante inyecta un script malicioso en una página web que se ejecuta en el navegador de la víctima.
// Código vulnerableapp.get('/search', (req, res) => { const query = req.query.q; res.send(`<h2>Resultados de búsqueda para: ${query}</h2>`);});
// Versión seguraconst escapeHtml = (str) => { return str.replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''');};
app.get('/search', (req, res) => { const query = escapeHtml(req.query.q || ''); res.send(`<h2>Resultados de búsqueda para: ${query}</h2>`);});2. SQL Injection
La inyección de código SQL ocurre cuando la entrada del usuario se concatena directamente en una consulta SQL. Esto puede provocar la fuga o eliminación de datos.
// Código vulnerableconst userId = req.body.id;const query = `SELECT * FROM users WHERE id = ${userId}`;
// Versión segura con consultas parametrizadasconst userId = req.body.id;const query = 'SELECT * FROM users WHERE id = ?';db.query(query, [userId], (err, results) => { // procesamiento de resultados});3. Deserialización insegura
Node.js usa frecuentemente JSON.parse(), pero si trabaja con otros formatos (por ejemplo, Buffer), un atacante puede inyectar datos maliciosos.
// Enfoque peligrosoconst data = JSON.parse(req.body.data); // puede contener __proto__
// Enfoque seguroconst safeParse = (json) => { const data = JSON.parse(json); // Eliminamos recursivamente propiedades peligrosas return JSON.parse(JSON.stringify(data));};4. CSRF (Cross-Site Request Forgery)
Ataque en el que el atacante obliga al usuario a realizar una acción no deseada en un sitio donde está autenticado.
// Configuración de protección con csurfconst csrf = require('csurf');const csrfProtection = csrf({ cookie: true });
app.post('/transfer', csrfProtection, (req, res) => { // procesamiento seguro de la transferencia});5. Autenticación insegura
Contraseñas débiles, falta de límite de intentos de inicio de sesión y almacenamiento de contraseñas en texto plano son problemas comunes.
// Hash correcto de contraseñasconst bcrypt = require('bcrypt');
async function createUser(password) { const salt = await bcrypt.genSalt(12); const hash = await bcrypt.hash(password, salt); // guardamos el hash en la BD}
async function checkPassword(plainPassword, hash) { return await bcrypt.compare(plainPassword, hash);}6. Fuga de datos confidenciales
Enviar el stack de errores al cliente o registrar contraseñas es un error frecuente de los desarrolladores.
// Incorrectoapp.use((err, req, res, next) => { res.status(500).json({ error: err.stack });});
// Correctoapp.use((err, req, res, next) => { console.error(err); // registramos en el servidor res.status(500).json({ error: 'Error interno del servidor' });});7. Configuración insegura
Uso de puertos estándar, modo de depuración habilitado en producción y políticas CORS débiles.
// Configuración segura de CORSconst cors = require('cors');cons