Editar en GitHub

Mejores prácticas de producción: Seguridad

El término “production” se refiere a la etapa en el ciclo de vida del software cuando una aplicación o API está generalmente disponible para sus usuarios finales o consumidores. En contraste, en la etapa de “development”, aún estás escribiendo y probando código activamente, y la aplicación no está abierta a acceso externo. Los entornos de sistema correspondientes se conocen como entornos de production y development, respectivamente.

Los entornos de desarrollo y producción suelen configurarse de manera diferente y tienen requisitos muy distintos. Lo que está bien en desarrollo puede no ser aceptable en producción. Por ejemplo, en un entorno de desarrollo puede que quieras logging verboso de errores para depuración, mientras que el mismo comportamiento puede convertirse en un problema de seguridad en un entorno de producción. Y en desarrollo, no necesitas preocuparte por la escalabilidad, confiabilidad y rendimiento, mientras que esas preocupaciones se vuelven críticas en producción.

Nota

Si crees que has descubierto una vulnerabilidad de seguridad en Express, consulta Security Policies and Procedures.

Las mejores prácticas de seguridad para aplicaciones Express en producción incluyen:

No uses versiones deprecadas o vulnerables de Express

Express 2.x y 3.x ya no reciben mantenimiento. Los problemas de seguridad y rendimiento en estas versiones no serán corregidos. ¡No las uses! Si no has migrado a la versión 4, sigue la guía de migración o considera las Opciones de Soporte Comercial.

También asegúrate de no estar usando ninguna de las versiones vulnerables de Express listadas en la página de actualizaciones de seguridad. Si lo estás, actualiza a uno de los releases estables, preferiblemente el último.

Usa TLS

Si tu app maneja o transmite datos sensibles, usa Transport Layer Security (TLS) para asegurar la conexión y los datos. Esta tecnología encripta los datos antes de que se envíen del cliente al servidor, previniendo así algunos hacks comunes (y fáciles). Aunque las peticiones Ajax y POST puedan no ser visiblemente obvias y parecer “ocultas” en los navegadores, su tráfico de red es vulnerable a packet sniffing y man-in-the-middle attacks.

Puede que estés familiarizado con la encriptación Secure Socket Layer (SSL). TLS es simplemente la siguiente progresión de SSL. En otras palabras, si estabas usando SSL antes, considera actualizar a TLS. En general, recomendamos Nginx para manejar TLS. Para una buena referencia sobre cómo configurar TLS en Nginx (y otros servidores), consulta Recommended Server Configurations (TLSRef).

Además, una herramienta útil para obtener un certificado TLS gratuito es Let’s Encrypt, una autoridad certificadora (CA) gratuita, automatizada y abierta proporcionada por el Internet Security Research Group (ISRG).

No confíes en la entrada del usuario

Para aplicaciones web, uno de los requisitos de seguridad más críticos es la validación y manejo adecuado de la entrada del usuario. Esto se presenta en muchas formas y no las cubriremos todas aquí. En última instancia, la responsabilidad de validar y manejar correctamente los tipos de entrada de usuario que tu aplicación acepta es tuya.

Previene redirecciones abiertas

Un ejemplo de entrada del usuario potencialmente peligrosa es una redirección abierta, donde una aplicación acepta una URL como entrada del usuario (a menudo en la query de la URL, por ejemplo ?url=https://example.com) y usa res.redirect para establecer el encabezado location y retornar un estado 3xx.

Una aplicación debe validar que soporta la redirección a la URL entrante para evitar enviar a los usuarios a enlaces maliciosos como sitios web de phishing, entre otros riesgos.

Aquí hay un ejemplo de verificación de URLs antes de usar res.redirect o res.location:

app.use((req, res) => {
try {
if (new Url(req.query.url).host !== 'example.com') {
return res.status(400).end(`Unsupported redirect to host: ${req.query.url}`);
}
} catch (e) {
return res.status(400).end(`Invalid url: ${req.query.url}`);
}
res.redirect(req.query.url);
});

Usa Helmet

Helmet puede ayudar a proteger tu app de algunas vulnerabilidades web conocidas estableciendo los encabezados HTTP apropiadamente.

Helmet es una función de middleware que establece encabezados HTTP de respuesta relacionados con la seguridad. Helmet establece los siguientes encabezados por defecto:

  • Content-Security-Policy: Una lista de permitidos poderosa de lo que puede suceder en tu página, lo que mitiga muchos ataques
  • Cross-Origin-Opener-Policy: Ayuda a aislar tu página por proceso
  • Cross-Origin-Resource-Policy: Bloquea a otros de cargar tus recursos cross-origin
  • Origin-Agent-Cluster: Cambia el aislamiento de procesos para que sea basado en origen
  • Referrer-Policy: Controla el encabezado Referer
  • Strict-Transport-Security: Indica a los navegadores que prefieran HTTPS
  • X-Content-Type-Options: Evita el MIME sniffing
  • X-DNS-Prefetch-Control: Controla el prefetching de DNS
  • X-Download-Options: Fuerza que las descargas se guarden (solo Internet Explorer)
  • X-Frame-Options: Encabezado heredado que mitiga ataques de Clickjacking
  • X-Permitted-Cross-Domain-Policies: Controla el comportamiento cross-domain para productos de Adobe, como Acrobat
  • X-Powered-By: Información sobre el servidor web. Eliminado porque podría usarse en ataques simples
  • X-XSS-Protection: Encabezado heredado que intenta mitigar ataques XSS, pero empeora las cosas, así que Helmet lo deshabilita

Cada encabezado puede configurarse o deshabilitarse. Para leer más al respecto, visita su sitio web de documentación.

Instala Helmet como cualquier otro módulo:

Ventana de terminal
npm install helmet

Luego para usarlo en tu código:

index.cjs
// ...
const helmet = require('helmet');
app.use(helmet());
// ...

Reduce el fingerprinting

Puede ayudar a proporcionar una capa extra de seguridad reducir la capacidad de los atacantes para determinar el software que usa un servidor, conocido como “fingerprinting”. Aunque no es un problema de seguridad en sí mismo, reducir la capacidad de fingerprintear una aplicación mejora su postura de seguridad general. El software del servidor puede ser fingerprinteado por peculiaridades en cómo responde a peticiones específicas, por ejemplo en los encabezados de respuesta HTTP.

Por defecto, Express envía el encabezado de respuesta X-Powered-By que puedes deshabilitar usando el método app.disable():

app.disable('x-powered-by');

Nota

Deshabilitar el encabezado X-Powered-By no previene que un atacante sofisticado determine que una app está ejecutando Express. Puede desalentar un exploit casual, pero hay otras formas de determinar que una app está ejecutando Express.

Express también envía sus propios mensajes formateados de “404 Not Found” y mensajes de respuesta de error formateados. Estos pueden cambiarse añadiendo tu propio manejador de no encontrado y escribiendo tu propio manejador de errores:

// last app.use calls right before app.listen():
// custom 404
app.use((req, res, next) => {
res.status(404).send("Sorry can't find that!");
});
// custom error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});

Usa cookies de forma segura

Para asegurar que las cookies no expongan tu app a exploits, no uses el nombre predeterminado de la cookie de sesión y establece las opciones de seguridad de cookies apropiadamente.

Hay dos módulos principales de middleware para cookies de sesión:

  • express-session que reemplaza el middleware express.session integrado en Express 3.x.
  • cookie-session que reemplaza el middleware express.cookieSession integrado en Express 3.x.

La principal diferencia entre estos dos módulos es cómo guardan los datos de la cookie de sesión. El middleware express-session almacena los datos de sesión en el servidor; solo guarda el ID de sesión en la cookie en sí, no los datos de sesión. Por defecto, usa almacenamiento en memoria y no está diseñado para un entorno de producción. En producción, necesitarás configurar un session-store escalable; consulta la lista de session stores compatibles.

En contraste, el middleware cookie-session implementa almacenamiento respaldado por cookies: serializa toda la sesión en la cookie, en lugar de solo una clave de sesión. Úsalo solo cuando los datos de sesión sean relativamente pequeños y fácilmente codificables como valores primitivos (en lugar de objetos). Aunque se supone que los navegadores soportan al menos 4096 bytes por cookie, para asegurar que no excedes el límite, no excedas un tamaño de 4093 bytes por dominio. Además, ten en cuenta que los datos de la cookie serán visibles para el cliente, así que si hay alguna razón para mantenerlos seguros u ocultos, entonces express-session puede ser una mejor opción.

Usar el nombre predeterminado de la cookie de sesión puede exponer tu app a ataques. El problema de seguridad que plantea es similar a X-Powered-By: un atacante potencial puede usarlo para fingerprintear el servidor y dirigir los ataques en consecuencia.

Para evitar este problema, usa nombres de cookie genéricos; por ejemplo usando el middleware express-session:

index.cjs
const session = require('express-session');
app.set('trust proxy', 1); // trust first proxy
app.use(
session({
secret: 's3Cur3',
name: 'sessionId',
})
);

Establece las siguientes opciones de cookie para mejorar la seguridad:

  • secure - Asegura que el navegador solo envíe la cookie por HTTPS.
  • httpOnly - Asegura que la cookie se envíe solo por HTTP(S), no por JavaScript del cliente, ayudando a proteger contra ataques de cross-site scripting.
  • domain - indica el dominio de la cookie; úsalo para compararlo con el dominio del servidor en el que se está solicitando la URL. Si coinciden, entonces verifica el atributo path a continuación.
  • path - indica la ruta de la cookie; úsalo para compararlo con la ruta de la petición. Si este y el dominio coinciden, entonces envía la cookie en la petición.
  • expires - úsalo para establecer la fecha de expiración para cookies persistentes.

Aquí hay un ejemplo usando el middleware cookie-session:

index.cjs
const session = require('cookie-session');
const express = require('express');
const app = express();
const expiryDate = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
app.use(
session({
name: 'session',
keys: ['key1', 'key2'],
cookie: {
secure: true,
httpOnly: true,
domain: 'example.com',
path: 'foo/bar',
expires: expiryDate,
},
})
);

Previene ataques de fuerza bruta contra la autorización

Asegúrate de que los endpoints de login estén protegidos para hacer los datos privados más seguros.

Una técnica simple y poderosa es bloquear los intentos de autorización usando dos métricas:

  1. El número de intentos fallidos consecutivos por el mismo nombre de usuario y dirección IP.
  2. El número de intentos fallidos desde una dirección IP durante un período largo de tiempo. Por ejemplo, bloquea una dirección IP si hace 100 intentos fallidos en un día.

El paquete rate-limiter-flexible proporciona herramientas para hacer esta técnica fácil y rápida. Puedes encontrar un ejemplo de protección contra fuerza bruta en la documentación

Asegúrate de que tus dependencias sean seguras

Usar npm para gestionar las dependencias de tu aplicación es poderoso y conveniente. Pero los paquetes que usas pueden contener vulnerabilidades de seguridad críticas que también podrían afectar a tu aplicación. La seguridad de tu app es tan fuerte como el “eslabón más débil” de tus dependencias.

Desde npm@6, npm revisa automáticamente cada solicitud de instalación. Además, puedes usar npm audit para analizar tu árbol de dependencias.

Ventana de terminal
$ npm audit

Si quieres estar más seguro, considera Snyk.

Snyk ofrece tanto una herramienta de línea de comandos como una integración con GitHub que verifica tu aplicación contra la base de datos de vulnerabilidades de código abierto de Snyk en busca de vulnerabilidades conocidas en tus dependencias. Instala el CLI de la siguiente manera:

Ventana de terminal
$ npm install -g snyk
$ cd your-app

Usa este comando para probar tu aplicación en busca de vulnerabilidades:

Ventana de terminal
$ snyk test

Evita otras vulnerabilidades conocidas

Mantente atento a los avisos de GitHub Advisory Database o Snyk que puedan afectar a Express u otros módulos que tu app usa. En general, estas bases de datos son excelentes recursos de conocimiento y herramientas sobre la seguridad de Node.

Finalmente, las apps de Express—como cualquier otra app web—pueden ser vulnerables a una variedad de ataques basados en web. Familiarízate con las vulnerabilidades web conocidas y toma precauciones para evitarlas.

Consideraciones adicionales

Aquí hay algunas recomendaciones adicionales de la excelente Node.js Security Checklist. Consulta esa publicación de blog para todos los detalles sobre estas recomendaciones:

  • Siempre filtra y sanitiza la entrada del usuario para proteger contra ataques de cross-site scripting (XSS) e inyección de comandos.
  • Defiéndete contra ataques de SQL injection usando consultas parametrizadas o prepared statements.
  • Usa la herramienta de código abierto sqlmap para detectar vulnerabilidades de SQL injection en tu app.
  • Usa las herramientas nmap y sslyze para probar la configuración de tus cifrados SSL, llaves y renegociación, así como la validez de tu certificado.
  • Usa safe-regex para asegurar que tus expresiones regulares no sean susceptibles a ataques de denegación de servicio por expresiones regulares.