dev.ansango / blog

Proteger Node.js en producción - Las buenas maneras

· 5 min de lectura

Proteger Node.js en producción - Las buenas maneras

7e608c7b447729b41d6c1e36d7d70c72_MD5.jpeg

Nada de amigos con “privilegios”

Ejecutar Node.js o cualquier servidor web como usuario root representa un riesgo significativo de seguridad. Un solo exploit podría otorgar a los atacantes control total sobre el servidor.

Crear un usuario dedicado para su aplicación Node.js restringe el daño potencial.

adduser --disabled-login nodejsUser

Ejemplo de Dockerfile para una aplicación Node.js

FROM node:18-alpine
RUN addgroup adx && adduser -S -G adx adx
WORKDIR /usr/src/app/backend
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
USER adx
EXPOSE 5000
CMD ["npm", "start"]

Cambia a este usuario antes de iniciar su aplicación para asegurarte de que se ejecute con permisos limitados.

Actualiza todas tus librerías y dependencias de npm

Las dependencias en el ecosistema de Node.js pueden ser un arma de doble filo. Aunque aceleran significativamente el desarrollo, también pueden introducir vulnerabilidades.

Use npm audit para un escaneo rápido de vulnerabilidades y solucione problemas automáticamente con npm audit fix.

npm update && npm audit fix

Podríamos usar Snyk que escanea en busca de vulnerabilidades y proporcionando soluciones o alternativas.

npm install -g snyk
snyk auth
snyk test

Podemos automatizar este proceso en una pipeline CI/CD.

Personalización de Nombres de Cookies: Ocultando Detalles de la Tecnología

Los nombres de cookies predeterminados pueden revelar las tecnologías de tu aplicación, facilitando a los atacantes ajustar sus exploits.

Cambia los nombres de las cookies de sesión predeterminadas a algo único y no relacionado con la tecnología utilizada.

const express = require('express');
const session = require('express-session');
app.use(session({
  // establecer un nombre personalizado para la cookie de sesión
  name: 'siteSessionId',
  // una clave secreta segura para el cifrado de sesiones
  secret: 'complex_secret_key',
  // Configuraciones adicionales de sesión…
}));

Ponte el casco, usa Helmet para securizar tus cabeceras HTTP

Las cabeceras HTTP seguras son cruciales para proteger su aplicación de diversos tipos de ataques como XSS, clickjacking y otras inyecciones de sitios cruzados.

Helmet.js es un middleware que configura encabezados HTTP seguros automáticamente.

El middleware helmet() elimina automáticamente encabezados inseguros y añade nuevos, incluyendo X-XSS-Protection, X-Content-Type-Options, Strict-Transport-Security y X-Frame-Options. Super easy de aplicar:

const helmet = require('helmet');

app.use(helmet({
  // Configuración personalizada de helmet aquí
}));

Controla las requests

El control de las peticiones es esencial para proteger su aplicación contra ataques de fuerza bruta y DDoS, limitando la cantidad de solicitudes que un usuario puede hacer en un periodo de tiempo determinado.

Utiliza librerías como express-rate-limit para una configuración sencilla de limitación de tasas.

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 100, // Limita cada IP a 100 solicitudes por windowMs
});

app.use(limiter);

Algo más que passwords. Implementación de Políticas de Autenticación Fuertes

Los mecanismos de autenticación son frecuentemente objetivos de los atacantes. Implementar métodos de autenticación robustos es crucial para asegurar las cuentas de los usuarios.

Puedes usar librerías como bcrypt, que proporciona un método seguro para almacenar contraseñas. Establecer requisitos de complejidad de contraseñas y utilizar la autenticación multifactor (MFA) añade otra capa de seguridad.

Educa a tus usuarios sobre la importancia de contraseñas fuertes.

const bcrypt = require('bcrypt');
const saltRounds = 10;

// Hashing de una contraseña
bcrypt.hash('userPassword', saltRounds, function(err, hash) {
  // Almacenar hash en su base de datos de contraseñas.
});

Controla esos logs en producción y evitando la fuga de información

Los mensajes de error verbosos pueden proporcionar a los atacantes información sobre la arquitectura de su aplicación, facilitando ataques dirigidos.

Asegúrese de que los entornos de producción no expongan trazas detalladas a los usuarios. Reserva esos registros detallados para el server side manteniendo los mensajes orientados al usuario genéricos.

app.use((err, req, res, next) => {
  res.status(500).json({ error: "Internal Server Error" });
});

Con un ojo puesto

Monitorea tu aplicación. Integra herramientas de Monitoreo del Rendimiento de Aplicaciones para rastrear el comportamiento de la aplicación e identificar anomalías indicativas de brechas de seguridad.

const apmTool = require('apm-tool-of-choice');

apmTool.start({
  // Opciones de configuración
});

Solo HTTPS

HTTPS asegura que los datos entre su servidor y el usuario estén encriptados. Redirige todo el tráfico HTTP a HTTPS y asegúrate de que las cookies se configuren con el atributo Secure.

Puedes usar herramientas como como Let’s Encrypt para obtener certificados SSL/TLS gratuitos.

app.use((req, res, next) => {
  if (!req.secure) {
    return res.redirect(`https://${req.headers.host}${req.url}`);
  }
  next();
});

Securizar Nginx con Let’s Encrypt en Ubuntu

Validación de Entrada del Usuario: Protegiéndose contra Inyecciones

Validar la entrada del usuario es fundamental para prevenir ataques de inyección, como la inyección SQL, XSS, y más.

Puedes usar librerías clásicas como express-validator, u otras más modernas como zod. Pero sobre todos define reglas estrictas basadas en el formato esperado de los datos.

const { body, validationResult } = require('express-validator');

app.post('/register', [
  body('email').isEmail(),
  body('password').isLength({ min: 5 })
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  // Proceder con la lógica de registro
});

Puedes ver algunas librerías como:

Aprovechando Linters de Seguridad

Usa herramientas para detectar automáticamente riesgos de seguridad en tu código.

  1. Usa eslint: combinado con el eslint-plugin-security, ofrece un enfoque centrado en identificar riesgos de seguridad en el código Node.js.
  2. Ejecuta el linter para encontrar problemas de seguridad y corregirlos antes de que se conviertan en vulnerabilidades.
  3. Incorpora el linting en tus desarrollos para detectar y corregir problemas rápidamente.
npm install eslint eslint-plugin-security --save-dev
{
  "extends": ["eslint:recommended", "plugin:security/recommended"],
  "plugins": ["security"]
}
npx eslint .

Conclusiones

Proteger una aplicación Node.js en producción es un proceso continuo. La seguridad no es un estado, sino un proceso.

Mantén tus dependencias actualizadas, implementa políticas de autenticación robustas, controla las entradas de los usuarios y monitorea tu aplicación para detectar anomalías.

Con estas prácticas, puedes reducir significativamente la superficie de ataque de tu aplicación y protegerla contra una amplia gama de amenazas.