Skip to main content

Deberías usar encabezados HTTP Strict Transport Security (HSTS) en tu servidor Node.js

Escrito por

Raphael Mun

feature global security

29 de diciembre de 2022

0 minutos de lectura

En la mayoría de los sitios web y las aplicaciones, el uso de encabezados HTTP relacionados con la seguridad ya es una práctica estándar. Los sitios web usan encabezados en las solicitudes y respuestas HTTP para transmitir información sobre una página o los datos enviados mediante el protocolo HTTP. Por ejemplo, pueden incluir un encabezado Content-Encoding para indicar que el contenido es un archivo ZIP comprimido, o un encabezado Location para especificar una URL de redireccionamiento.

Entre los muchos encabezados de seguridad disponibles que los navegadores web modernos usan para proteger a los usuarios, uno de los más importantes es el encabezado HTTP Strict Transport Security (HSTS). El encabezado HSTS que proporciona un sitio web le indica al navegador que use el protocolo HTTPS en cada visita posterior.

Sin embargo, pese a su utilidad, su fácil implementación y la compatibilidad con prácticamente todos los navegadores, solo alrededor del 25 % de las respuestas HTTP móviles y el 28 % de las de escritorio incluyen encabezados HSTS.

Veamos a fondo los encabezados HSTS para descubrir cómo afectan la seguridad web y por qué deberíamos usarlos en Node.js. Después, aprenderemos a habilitar HSTS en un servidor Node.js.

¿Por qué habilitar HSTS?

Las conexiones HTTPS son seguras porque cifran y descifran los paquetes de datos transmitidos mediante certificados SSL/TLS, que pueden verificar autoridades de certificación (CA) reconocidas. Por lo tanto, solo la computadora del usuario y el servidor pueden leer los paquetes de datos transmitidos, sin importar quién intercepte el tráfico de red.

Las conexiones HTTP habituales no están cifradas, por lo que cualquiera que acceda a los datos puede leerlos. Entre esos destinatarios no previstos pueden estar los servidores que enrutan y reenvían los datos del usuario al servidor de destino, o un hacker que accede al tráfico de red a través de un router Wi-Fi público.

Considera estos hallazgos del informe Web Almanac 2022, que Snyk también ayudó a redactar.

Gráficos de Web Almanac 2022 que muestran que el 25 % de las solicitudes móviles tienen un encabezado HSTS y las tasas de uso de las directivas HSTS en computadoras y dispositivos móviles.

Actualmente, solo el 34 % de las respuestas a solicitudes móviles y el 37 % de las de escritorio incluyen la directiva includeSubdomain.

La mayoría restante sigue siendo más vulnerable a los ataques de intermediario (MITM), que pueden provocar desde el robo de tarjetas de crédito o de identidad hasta la exposición de las credenciales web de nuestra aplicación Node.js. Cabe destacar que los actores maliciosos no necesitan interceptar los datos justo cuando ingresamos nuestras credenciales. Las cookies del navegador suelen almacenar y enviar nuestros tokens de sesión o acceso, lo que permite que estos actores maliciosos los obtengan mediante el secuestro de sesión.

¿Cómo funciona HSTS?

Cuando la respuesta HTTP incluye un encabezado HSTS, los navegadores web saben que siempre deben usar una conexión HTTPS con el servidor y redirigen automáticamente a los usuarios que se conectaron primero mediante HTTP. A partir de ese momento, las conexiones con la aplicación o el sitio web permanecen cifradas y seguras, lo que permite que la aplicación use cookies seguras y ayuda a prevenir ataques de seguridad MITM.

El encabezado HSTS tiene tres parámetros diferentes. Veámoslos.

max - age = <expire-time>

Este parámetro obligatorio especifica durante cuánto tiempo, en segundos, el navegador debe recordar que tiene que conectarse al sitio mediante HTTPS. Una vez que vence este plazo, el navegador cargará el sitio normalmente en la siguiente visita. El tiempo de vencimiento se actualiza en el navegador del usuario cada vez que este recibe el encabezado HSTS. Por eso, el parámetro puede permanecer activo indefinidamente. También es posible deshabilitar HSTS de inmediato en ciertos sitios al establecer este valor en 0.

Al probar la configuración de HSTS, puedes establecer un plazo corto, como 300 segundos (cinco minutos). Luego, una vez que hayas verificado que el sitio funciona correctamente, puedes establecer un plazo más largo, como 31,536,000 segundos (un año).

includeSubDomains

El parámetro includeSubDomains es una marca opcional que le indica al navegador que habilite HSTS para el sitio web y todos sus subdominios. Como esta configuración se aplica de forma general, asegúrate de que todos tus subdominios sean compatibles con HTTPS antes de implementar el parámetro.

preload

La conexión inicial podría usar una conexión HTTP comprometida. Para responder a este riesgo, Google mantiene el parámetro no oficial preload. La mayoría de los principales navegadores son compatibles con este encabezado de seguridad, que reduce considerablemente el riesgo. Un sitio web puede agregar el parámetro preload al encabezado HSTS y luego registrar el dominio en el servicio de precarga. Así, los navegadores pueden consultar esta lista de referencia para determinar si sus conexiones iniciales pueden usar HTTPS. Ten en cuenta que debes establecer el parámetro max-age en al menos 31,536,000 segundos y habilitar el parámetro includeSubDomains para que funcione el parámetro preload.

Ten en cuenta que, una vez que el servicio de precarga agregue tu dominio, una solicitud para eliminarlo puede tardar varios meses en procesarse. Si quieres registrar tu dominio para la precarga, familiarízate con los requisitos de envío y las recomendaciones de implementación.

Refuerza la seguridad con HSTS

Veamos un ejemplo de código de un servidor Node.js que responde con un encabezado HSTS. Ten en cuenta que todos los ejemplos de código de este artículo son públicos y están disponibles en el siguiente repositorio de código abierto en GitHub.

Requisitos previos

Para ejecutar este proyecto, necesitarás lo siguiente:

  • Un nombre de dominio registrado

  • Un servidor con Node.js y los puertos de red 80 y 433 disponibles

  • Un registro DNS válido para el dominio que apunte al servidor

Obtén un certificado SSL/TLS

Antes de habilitar HTTPS, primero necesitaremos un certificado y una clave privada para el dominio. Si aún no tienes uno, puedes obtenerlo mediante Let's Encrypt siguiendo estos pasos:

Primero, instala certbot siguiendo estas instrucciones. Selecciona other en el menú desplegable Software y elige el sistema operativo de tu servidor en el menú desplegable System. Luego, sigue las instrucciones que aparecen.

Página web con instrucciones de Certbot que muestra opciones para un sitio web HTTP en “Otro” y CentOS 8.

Después, asegúrate de que los puertos 80 y 443 estén abiertos en el servidor y ejecuta el siguiente comando con el dominio de tu servidor:

certbot certonly --standalone -d your.domain.url

Es posible que tengas que anteponer sudo al comando y ejecutarlo como usuario con privilegios.

Salida de la terminal que muestra que Certbot obtuvo y guardó correctamente un certificado HTTPS de Let's Encrypt para snykdemo.instafluff.tv.

Ahora, ejecuta certbot certificates (con sudo, si es necesario) para obtener las rutas de los archivos del certificado y la clave privada.

Terminal que muestra el resultado de Certbot para el certificado de snykdemo.instafluff.tv, incluida su fecha de vencimiento y las rutas del certificado y la clave privada de Let's Encrypt.

Configura el código del servidor

Empieza por crear una carpeta de proyecto en el servidor con el comando: mkdir snyk-demo.

Después, entra en la carpeta con: cd snyk-demo.

Luego, inicializa la carpeta como un proyecto de Node.js con: npm init -y.

Terminal que muestra npm init creando un archivo package.json de Node.js para el proyecto snyk-demo.

Ahora instalaremos algunas dependencias para el servidor. Asegúrate de seguir las instrucciones correspondientes para Express o Fastify.

Express:

npm install express helmet

Fastify:

npm install fastify @fastify/helmet

Ahora podemos crear un servidor Node.js básico con Express que funcione con HTTP y HTTPS.

Crea un archivo index.js en esta carpeta y agrega los siguientes módulos de Node al inicio del código.

Express:

const fs = require( "fs" );
const http = require( "http" );
const https = require( "https" );
const express = require( "express" );
const helmet = require( "helmet" );

Fastify:

const fs = require( "fs" );
const helmet = require( "@fastify/helmet" );

Agrega el siguiente código para cargar los archivos del certificado y reemplaza el valor de certificatePath por la ruta del certificado de tu dominio. Si usaste certbot de Let's Encrypt, puedes ejecutar certbot certificates para obtener las rutas de los archivos.

Express:

// Replace the following with your domain certificate path
const certificatePath = `/etc/letsencrypt/live/snykdemo.instafluff.tv/;
const privateKey = fs.readFileSync( `${certificatePath}/privkey.pem, "utf8" );
const certificate = fs.readFileSync( `${certificatePath}/fullchain.pem, "utf8" );
// Add the following if your certificate includes a certificate chain
// const ca = fs.readFileSync( `${certificatePath}/chain.pem, "utf8" );

const credentials = {
    key: privateKey,
    cert: certificate,
    // ca: ca, // Add the ca if there is a certificate chain
};

Fastify:

// Replace the following with your own domain certificate path
const certificatePath = `/etc/letsencrypt/live/snykdemo.instafluff.tv/`;
const fastify = require( "fastify" )({
    https: {
        key: fs.readFileSync( `${certificatePath}/privkey.pem, "utf8" ),
        cert: fs.readFileSync( `${certificatePath}/fullchain.pem, "utf8" ),
        // // Add the following if your certificate includes a certificate chain
        // ca: fs.readFileSync( `${certificatePath}/chain.pem, "utf8" ),
    },
});
const fastifyHttp = require( "fastify" )();

Ahora es momento de inicializar la aplicación Express o Fastify y crear una ruta básica.

El siguiente código muestra los tres parámetros de HSTS, pero quizás quieras empezar usando solo el parámetro max-age.

Opción 1: Usa el middleware Helmet

La primera forma de configurar HSTS es mediante el middleware Helmet al inicializar la aplicación:

Express:

const app = express();
app.use( helmet() );
app.use( helmet.hsts( { maxAge: 300, includeSubDomains: true, preload: true } ) );

app.use( ( req, res ) => {
res.send( "Hello secure web!" );
});

Fastify:

const hsts = { maxAge: 300, includeSubDomains: true, preload: true };
fastify.register( helmet, { hsts } );
fastifyHttp.register( helmet, { hsts } );

Opción 2: Configura el encabezado manualmente

Como alternativa, podemos configurar manualmente el encabezado HSTS mediante un middleware que se aplique a todas las respuestas:

Express:

const app = express();

app.use( ( req, res ) => {
    if( req.secure ) {
         res.setHeader( "Strict-Transport-Security", "max-age=300; includeSubDomains; preload" );
    }
    res.send( "Hello secure web!" );
});

Fastify:

fastify.get( "/", ( req, res ) => {
    res.header( "Strict-Transport-Security", "max-age=300; includeSubDomains; preload" );
    res.send( "Hello secure web!" );
});
fastifyHttp.get( "/", ( req, res ) => {
    res.header( "Strict-Transport-Security", "max-age=300; includeSubDomains; preload" );
    res.send( "Redirect to HTTPS" );
});

Podemos completar el código creando los servidores HTTP y HTTPS con el siguiente código:

Express:

const httpServer = http.createServer( app );
httpServer.listen( 80, () => {
console.log( "HTTP server started." );
});

const httpsServer = https.createServer( credentials, app );
httpsServer.listen( 443, () => {
console.log( "HTTPS server started" );
});

Fastify:

fastifyHttp.listen( { port: 80, host: "0.0.0.0" }, ( err, address ) => {
    console.log( "HTTP server started" );
});

fastify.listen( { port: 443, host: "0.0.0.0" }, ( err, address ) => {
    console.log( "HTTPS server started" );
});

Como referencia, aquí tienes los enlaces a los repositorios del código de los proyectos Express y Fastify.

Por último, ejecuta el código del servidor con node index.js y abre tu dominio en un navegador web.

bitnami@ip-172-26-14-29:~/snyk-demo$ sudo node index.js
HTTP server started.
HTTPS server started

Abre DevTools en tu navegador e inspecciona los encabezados de respuesta en la pestaña Network. Verás que se especifica el encabezado HSTS.

Encabezados de red de Chrome DevTools que muestran el encabezado de respuesta Strict-Transport-Security con max-age=300, includeSubDomains y preload

¿Qué sigue?

Ahora que aprendiste por qué los encabezados HTTP Strict Transport Security son importantes para proteger las aplicaciones Node.js, cómo ayudan a prevenir los ataques MITM y cómo implementarlos en tu código Node.js, tienes las herramientas para proteger las aplicaciones web que ofreces a tus usuarios.

Hay otro punto fundamental que debes recordar. En un entorno de producción, no debes configurar HSTS ni servir SSL desde la aplicación Node.js. En su lugar, debes delegar estos procesos a servidores HTTP de archivos estáticos, como NGINX o Apache.

Leer más

blog feature snyk container custom base recommendations
Blog

Presentamos Evo Agentic AppSec: seguridad al ritmo del desarrollo con IA

El desarrollo impulsado por IA supera el ritmo de los flujos de trabajo de seguridad tradicionales. Agentic AppSec de Snyk usa agentes de seguridad para encontrar, corregir y verificar vulnerabilidades de forma continua, mientras las personas definen el estándar.

blog feature ai violet
Blog

VulnBump: ¿Pueden los agentes de IA corregir dependencias vulnerables sin romper la compilación?

VulnBump prueba agentes de IA en 307 actualizaciones reales de dependencias. Una corrección cuenta solo si elimina la vulnerabilidad y mantiene en verde la compilación y las pruebas.

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.