Introducción: por qué la seguridad de las aplicaciones JavaScript no es una opción, sino una necesidad
JavaScript (especialmente en la plataforma Node.js) se ha convertido en el lenguaje dominante para crear aplicaciones tanto del lado del cliente como del servidor. Sin embargo, con el aumento de la popularidad también crece el número de ataques. Según OWASP, la mayoría de las intrusiones ocurren debido a errores típicos que se pueden prevenir. En este artículo analizaremos el top 10 de vulnerabilidades de JavaScript (Node.js) con ejemplos de código reales y métodos de protección. El material será útil tanto para desarrolladores principiantes como experimentados que deseen fortalecer la seguridad de sus proyectos.
1. Inyecciones (SQL, NoSQL, Command Injection)
Las inyecciones siguen siendo la categoría de vulnerabilidades más peligrosa. En Node.js, MongoDB y PostgreSQL suelen verse afectados.
Ejemplo de código vulnerable (inyección NoSQL):
// Código vulnerableapp.post('/login', async (req, res) => { const { username, password } = req.body; const user = await db.collection('users').findOne({ username: username, password: password }); // Si se pasa { "username": { "$ne": null }, "password": { "$ne": null } }, se devolverá el primer usuario});Cómo corregirlo:
- Utilice ORM/ODM con parametrización (Mongoose, Sequelize).
- Valide y sanitice los datos de entrada (biblioteca
validatorojoi). - Para comandos, use
execFileen lugar deexec.
2. Cross-Site Scripting (XSS)
XSS permite a un atacante inyectar un script malicioso en una página que otros usuarios están viendo.
Ejemplo de código vulnerable (XSS Reflejado):
// Ruta Express vulnerableapp.get('/search', (req, res) => { const query = req.query.q; res.send(`<h1>Resultados de búsqueda para: ${query}</h1>`); // Si se pasa ?q=<script>alert('XSS')</script>, el script se ejecutará});Protección:
- Escape la salida: use
res.rendercon motores de plantillas (EJS, Pug) oencodeURIComponent. - Establezca el encabezado HTTP
Content-Security-Policy. - No use
innerHTMLen el cliente — apliquetextContent.
3. Autenticación insegura y gestión de sesiones
La autenticación débil es una de las principales causas de filtraciones de datos. A menudo, los desarrolladores hardcodean claves o usan contraseñas débiles.
Errores típicos:
- Almacenar contraseñas en texto plano (en lugar de bcrypt/argon2).
- Usar secretos JWT predecibles (
secret123). - Falta de limitación en el número de intentos de inicio de sesión.
Ejemplo de implementación correcta:
const bcrypt = require('bcrypt');const saltRounds = 12;
app.post('/register', async (req, res) => { const hash = await bcrypt.hash(req.body.password, saltRounds); // Guardamos el hash en la BD});
app.post('/login', async (req, res) => { const user = await db.getUser(req.body.username); const match = await bcrypt.compare(req.body.password, user.passwordHash); if (match) { // Creamos un JWT con corta duración const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' }); res.json({ token }); }});4. SSRF (Server-Side Request Forgery)
La vulnerabilidad SSRF permite a un atacante hacer que el servidor realice solicitudes a recursos internos (por ejemplo, a los metadatos del proveedor de la nube).
Ejemplo:
// Código vulnerable: carga de avatar por URLapp.post('/avatar', async (req, res) => { const { url } = req.body; const response = await fetch(url); // Se puede pasar http://169.254.169.254/latest/meta-data/ // Procesamiento de la respuesta});Protección:
- Cree una lista blanca de dominios permitidos.
- Blo