Skip to main content

Implicaciones de seguridad del uso compartido de recursos entre orígenes (CORS) en Node.js

Escrito por
Headshot of Victor Ikechukwu

Victor Ikechukwu

blog feature cors

13 de septiembre de 2023

0 minutos de lectura

En las aplicaciones web modernas, el uso compartido de recursos entre orígenes (CORS) permite la comunicación segura entre aplicaciones alojadas en distintos orígenes. Los desarrolladores usan CORS para acceder a los servicios de otras aplicaciones desde las suyas. Este enfoque evita tener que volver a crear funciones desde cero, acelera el desarrollo y mejora la experiencia de los desarrolladores.

Aunque CORS es útil, una implementación incorrecta puede exponer tus aplicaciones de Node.js a riesgos de seguridad, como filtraciones de datos y accesos no autorizados desde sitios web de terceros. Las configuraciones incorrectas pueden revelar datos confidenciales a orígenes no previstos o permitir que sitios web maliciosos evadan las protecciones de la política del mismo origen (SOP).

Comprender estas vulnerabilidades y adoptar prácticas recomendadas para implementar CORS de forma segura ayuda a mitigar estos riesgos y también te permite mantener la funcionalidad de tu aplicación.

En este artículo, primero veremos qué es CORS y algunos de sus casos de uso. Después, usaremos un ejemplo de código para implementar CORS en una aplicación de Node.js. Tras analizar los posibles problemas de seguridad, repasaremos las prácticas recomendadas para usar CORS y probaremos la seguridad de nuestro ejemplo. Para seguir el tutorial, solo necesitas algo de experiencia con JavaScript y Node.js.

CORS y sus casos de uso

Los navegadores web usan un mecanismo de seguridad llamado política del mismo origen (SOP) para regular la interacción entre aplicaciones web. La SOP impide que las aplicaciones alojadas en un origen lean los recursos de una aplicación alojada en un origen diferente. 

Este mecanismo impide que sitios maliciosos lean los datos de otros sitios, pero también puede restringir usos legítimos. ¿Qué pasa si quieres incluir datos meteorológicos en tu aplicación? ¿O insertar un video de YouTube en una página web? Todos deberían poder leer estos recursos públicos, pero la SOP los bloquea.

CORS permite que las aplicaciones web superen las limitaciones de la SOP, facilita la comunicación entre distintos servicios web y permite solicitudes entre orígenes. Las aplicaciones pueden usar el método HTTP OPTIONS para enviar solicitudes de comprobación previa al recurso del otro origen. Mediante estas solicitudes, los navegadores web determinan si el servidor del otro origen las permite. Con base en esa información, el navegador ignora o aplica la SOP.

Entre los casos de uso típicos de CORS se incluyen cargar recursos desde redes de distribución de contenido (CDN), solicitar API en varios dominios y facilitar la integración con terceros.

Sin embargo, implementar CORS de forma incorrecta en tu aplicación implica riesgos de seguridad. Podrías exponerla a un ataque de falsificación de solicitudes entre sitios (CSRF), que engaña a los usuarios para que realicen acciones no deseadas en la aplicación. Además, las configuraciones demasiado permisivas pueden permitir el acceso no autorizado a datos de usuarios e información confidencial entre orígenes, lo que podría provocar filtraciones de datos o exponer vulnerabilidades que los atacantes podrían aprovechar.

Por ejemplo, podrías sentirte tentado a usar el comodín permitir todo, representado por un asterisco (*), como valor del encabezado HTTP Access-Control-Allow-Origin. Esta configuración de CORS permite que cualquier origen interactúe con tu aplicación y la expone a posibles vulnerabilidades. En su lugar, elige configuraciones específicas y restrictivas. Para definir dinámicamente los orígenes permitidos, considera usar variables de entorno como alternativa más segura.

Implementar CORS de forma segura en una aplicación de Node.js

En esta sección, veremos cómo implementar CORS de forma segura en una aplicación de Node.js. Usaremos una aplicación sencilla de Node.js que utiliza Express.js como framework web. La aplicación funcionará como API para una librería en línea hipotética. 

Aunque la API tiene varios endpoints (para fetch, add, update y delete libros), este ejemplo solo implementará la función de obtener libros. Para simplificar, la API se conectará a un objeto sampleBooksData que sirve como sistema de almacenamiento de datos.

Requisitos previos

Para seguir este tutorial, necesitas lo siguiente:

  • Experiencia con JavaScript

  • Node.js (se recomienda la versión 18.16.1)

  • Un navegador web moderno

Crear la aplicación de Node.js

Para crear la aplicación, primero crea un directorio nuevo. Luego, ejecuta el siguiente comando en ese directorio para inicializar una aplicación de Node.js:

npm init -y

A continuación, ejecuta el siguiente comando para instalar las dependencias de este proyecto:

npm i express cors

Por último, crea un archivo JavaScript y asígnale el nombre index.js. Pega el siguiente código:

// Import required modules
const express = require("express");
const cors = require("cors");

// Initialize the Express application
const app = express();
app.use(express.json());

//Sample object that will serve as a database to store data
let sampleBooksData = {
  books: [
    {
      id: 1,
      title: "Harry Potter",
    },
    {
      id: 2,
      title: "The Da Vinci Code",
    },
    {
      id: 3,
      title: "Twilight",
    },
  ],
};

app.get("/books", cors(), (req, res, next) => {

  try {
    res.status(200).json(sampleBooksData);
  } catch (error) {
    console.error(`Error while reading from DB ${error}`);
    res.status(500).json({ message: "Internal Server Error" });
  }

});

// Start server on port defined by the variable PORT
let port = 3000;
app.listen(port, () =>
  console.log(`Server running at http://localhost:${port}`)
);

Express.js ofrece funciones de middleware para procesar solicitudes y respuestas dentro de la aplicación. Estas funciones pueden interceptar y modificar solicitudes entrantes o respuestas salientes, y permiten habilitar funciones como el registro, la autenticación o, en nuestro caso, la configuración de CORS. El ejemplo anterior importa el módulo CORS con const cors = require("cors");. Aplica el módulo como función de middleware a la ruta /books mediante el siguiente código:

app.get("/books", cors(), (req, res, next) => {
...
} 

Sin embargo, la implementación actual del middleware cors en la ruta /books no especifica explícitamente qué orígenes pueden hacer una solicitud GET. Por eso, la configuración permite que todos los orígenes hagan solicitudes a esa ruta. Por motivos de seguridad, no es una buena idea, pero puedes configurar reglas más restrictivas para tu entorno de producción.

Configurar CORS

Puedes implementar CORS de forma más segura con el middleware cors mediante cuatro configuraciones diferentes.

Habilitar CORS para orígenes específicos

Puedes establecer la opción origin en la configuración de CORS para definir los orígenes permitidos. Esto mejora la seguridad al restringir el acceso a fuentes confiables e impedir solicitudes no autorizadas entre orígenes. El siguiente ejemplo solo permite solicitudes desde `https://example.com`:

const corsOptions = {
  origin: 'https://example.com'
};
app.get('/books', cors(corsOptions), (req,res) => { /* ... */ });

Configurar los métodos HTTP permitidos

También puedes definir los métodos permitidos con la opción methods. Este enfoque limita los posibles vectores de ataque al permitir únicamente las acciones necesarias en los endpoints de tu API. El siguiente ejemplo solo permite las acciones GET y POST:

corsOptions.methods = ['GET', 'POST'];

Definir encabezados personalizados y encabezados expuestos

Otra opción es configurar el encabezado CORS Access-Control-Allow-Headers mediante la opción allowedHeaders y Access-Control-Expose-Headers mediante la opción exposedHeaders. Este enfoque permite que la aplicación gestione correctamente los datos confidenciales y mantenga una comunicación flexible entre los componentes del cliente y del servidor. El siguiente ejemplo solo permite los encabezados Content-Type y Authorization, y solo expone X-Custom-Header:

corsOptions.allowedHeaders = ['Content-Type', 'Authorization'];
corsOptions.exposedHeaders = ['X-Custom-Header'];

Configurar las solicitudes de comprobación previa y el almacenamiento en caché

Por último, puedes habilitar el almacenamiento en caché de las solicitudes de comprobación previa (OPTIONS) mediante la configuración de la duración de la caché con maxAge. Así, el cliente puede guardar en caché la información sobre la política CORS del servidor durante un tiempo determinado. En las solicitudes posteriores, los clientes pueden usar los datos de la política CORS guardados en caché sin volver a obtenerlos del servidor.

Establecer un valor adecuado para maxAge reduce el tiempo de respuesta y el tráfico de red, lo que optimiza el rendimiento y mantiene actualizadas las políticas de CORS. El siguiente ejemplo establece maxAge en 86400 segundos (24 horas):

// Cache duration in seconds.
// Set maxAge to a high value (e.g., 86400) for long-lived caches.
// Default is no-cache.
const DAY_IN_SECONDS=86400;
corsOptions.maxAge=DAY_IN_SECONDS;

Ahora que configuramos las opciones, podemos usar nuestro código como middleware en cualquier ruta que requiera CORS:

app.get('<route-of-your-choice>', cors(corsOptions), (req,res) => { /* ... */ });

Aunque CORS ayuda a proteger la API, sigue siendo una dependencia de terceros. Debemos asegurarnos de que esta y las demás dependencias que agreguemos al proyecto sean seguras y no tengan vulnerabilidades conocidas. Por ejemplo, Snyk Open Source ayuda a detectar problemas de seguridad en los paquetes durante el desarrollo o en los pipelines de integración y entrega continuas (CI/CD).

Implicaciones de seguridad de CORS y prácticas recomendadas

Si no configuramos CORS correctamente, corremos el riesgo de provocar las vulnerabilidades de seguridad mencionadas antes. En concreto, podríamos exponer datos confidenciales a orígenes no previstos, permitir que sitios web maliciosos realicen acciones no autorizadas (ataques CSRF) e incluso permitir que los atacantes evadan las protecciones de la SOP mediante técnicas como la secuencia de comandos entre sitios (XSS).

Para evitar estas situaciones, sigue siempre las prácticas recomendadas al implementar configuraciones de CORS. También puedes tomar otras medidas de seguridad, como implementar encabezados que no formen parte de CORS, tema que veremos más adelante en esta sección.

Prácticas recomendadas

Aplicar prácticas recomendadas como restringir los orígenes permitidos, usar cookies y tokens seguros y limitar los encabezados expuestos es fundamental para proteger tus aplicaciones y a sus usuarios. A continuación, veremos estas prácticas con ejemplos de código que te mostrarán cómo aplicarlas.

Restringir los orígenes permitidos mediante una lista de permitidos

Para evitar usar el comodín que permite todos los orígenes (*) en el objeto corsOptions, especifica los orígenes en una lista de permitidos:

const allowedOrigins = ['https://example.com', 'https://another-example.com'];
corsOptions.origin = (origin, callback) => {
  if (allowedOrigins.includes(origin)) {
    callback(null, true);
  } else {
    callback(new Error('Not allowed by CORS'));
  }
};

Usar cookies y tokens seguros para la autenticación

Usa el protocolo HTTPS para asegurarte de que tu aplicación transmita cookies y tokens de forma segura. También usa bibliotecas de autenticación confiables, como Passport.js, o soluciones de JSON Web Token (JWT):

corsOptions.credentials = true;

app.use(passport.initialize());
passport.use(new JwtStrategy(/* ... */));
app.get('/books', cors(corsOptions), passport.authenticate('jwt', { session: false }), (req,res) => { /* ... */ });

En este código, establecer el valor de credentials to true permite que las solicitudes entre orígenes incluyan credenciales.

Limitar los encabezados expuestos y los métodos HTTP

Para especificar solo los encabezados esenciales, usa la opción exposedHeaders. Además, usa la opción methods en tu configuración para restringir los métodos que habilitan CORS:

// Exposed Headers.
corsOptions.exposedHeaders = ['Content-Type', 'Authorization'];

// Permitted HTTP Methods
corsOptions.methods=['GET'];

Implementar encabezados de seguridad fuera de CORS

Puedes implementar otros encabezados de seguridad fuera de CORS para proteger aún más tu aplicación. Los encabezados de seguridad HTTP indican los aspectos de seguridad de la comunicación HTTP entre un cliente y un servidor.

Helmet.js ofrece un conjunto de encabezados de seguridad con valores predeterminados adecuados. Sin embargo, requiere cierta configuración de la política de seguridad del contenido (CSP) para habilitar la funcionalidad definida en la configuración de CORS. Sigue estos pasos para configurarla.

Primero, ejecuta el siguiente comando en la línea de comandos del directorio de tu aplicación de Node.js para instalar Helmet:

npm i helmet

A continuación, importa el paquete a tu aplicación con el siguiente código:

const helmet = require('helmet');
app.use(helmet());

Después, configura la CSP para que coincida con tu configuración de CORS, como se muestra a continuación:

const cspConfig = {
  directives: {
    defaultSrc: ["'self'", 'https://example.com'],
    // ...other CSP directives matching your app's needs.
  }
};
app.use(helmet.contentSecurityPolicy(cspConfig));

Por último, revisa tu trabajo. Es fácil pasar por alto una configuración o cometer un error tipográfico que comprometa la seguridad de tu aplicación, sobre todo a medida que crece. Snyk Code y las extensiones de herramientas para desarrolladores de Snyk (como la extensión de Visual Studio Code) detectan encabezados de seguridad faltantes o mal configurados en tu base de código. El uso de estas herramientas de terceros para abordar de forma proactiva los problemas de seguridad durante el desarrollo ayuda a mantener segura tu aplicación de Node.js.

Probar las implementaciones de CORS y su seguridad

Para asegurarte de que tu aplicación esté protegida frente a posibles amenazas, prueba la implementación de CORS y su seguridad. Esto ayuda a identificar posibles vulnerabilidades y configuraciones incorrectas. Las pruebas también verifican que las reglas de CORS funcionen según lo previsto y no expongan la aplicación a riesgos de seguridad, como filtraciones de datos o accesos no autorizados desde sitios web de terceros.

Hay varias formas de probar las implementaciones de CORS y su seguridad. A continuación, veremos algunos métodos populares.

Herramientas para desarrolladores del navegador

Los navegadores web modernos incluyen herramientas para desarrolladores que permiten inspeccionar y depurar aplicaciones web. Puedes usarlas para probar CORS examinando las solicitudes y respuestas de red. Simula solicitudes entre orígenes para verificar que las reglas de CORS las bloqueen o permitan según corresponda.

Más adelante ampliaremos este método y lo aplicaremos a la aplicación de Node.js de ejemplo que creamos antes.

Postman

Puedes usar Postman, la popular herramienta de desarrollo y pruebas de API, para probar CORS. Te permite crear solicitudes HTTP y especificar encabezados personalizados para probar las configuraciones de CORS. Postman también puede generar automáticamente solicitudes de verificación previa para probar el almacenamiento en caché de estas solicitudes. Además, puedes usar las funciones de scripting de Postman para automatizar escenarios de prueba.

Scripts personalizados

Puedes crear scripts personalizados para probar las configuraciones de CORS. Por ejemplo, usa un lenguaje de programación como Python o JavaScript para enviar solicitudes HTTP y verificar que las respuestas incluyan encabezados de CORS. También puedes simular solicitudes de origen cruzado y verificar que las reglas de CORS bloqueen o permitan las solicitudes según corresponda.

Cómo probar la configuración de CORS con las herramientas para desarrolladores del navegador

Puedes probar las configuraciones de CORS en nuestra aplicación de ejemplo de Node.js con las herramientas para desarrolladores del navegador. Sigue estos pasos.

Primero, inicia la aplicación de Node.js. En la línea de comandos del directorio de la aplicación, ejecuta el siguiente comando:

node index.js

Luego, ve a la página principal de Google en el navegador que prefieras.

Página de inicio de Google en modo oscuro con una barra de búsqueda vacía, botones de búsqueda y opciones de idioma como hausa, igbo, yoruba y pidgin nigeriano

A continuación, abre la consola para desarrolladores. Si usas un navegador basado en Chromium, usa el comando Option+⌘+J (en macOS) o Shift+CTRL+ J (en Windows/Linux). También puedes usar los comandos correspondientes en el navegador que prefieras.

Pega el código siguiente y presiona Return/Enter:

fetch('http://127.0.0.1:3000/books')
.then((response) => response.json())
.then((json) => console.log(json))

Según la configuración actual de CORS de la aplicación de ejemplo, lo más probable es que recibas errores que indiquen que la política de CORS bloqueó el acceso del origen (Google) al recurso /books en tu servidor y que se produjo un error interno del servidor.

Consola de Chrome DevTools que muestra una solicitud fetch bloqueada por una política CORS y un error interno del servidor 500

El error de acceso bloqueado se debe a que el origen ​​https://www.google.com no está entre los orígenes autorizados para superar la SOP. La configuración de la opción `origin` del objeto corsOptions que pasamos al middleware cors no permite que ese origen reciba una respuesta del servidor. Ten en cuenta que, aunque la vista y el mensaje de error pueden variar según el navegador, el error seguirá indicando que la solicitud está bloqueada.

Este error confirma que las reglas de implementación de CORS funcionan según lo previsto. No se puede acceder a la aplicación desde orígenes no autorizados.

Para aprobar la solicitud de https://www.google.com, especifícalo en la opción origin del objeto corsOptions:

const allowedOrigins = ['https://example.com', 'https://another-example.com', 'https://www.google.com'];
corsOptions.origin = (origin, callback) => {
  if (allowedOrigins.includes(origin)) {
    callback(null, true);
  } else {
    callback(new Error('Not allowed by CORS'));
  }
};

app.get('books/', cor(corsOptions), (req,res) => { /* ... */ })

Cuando reinicies el servidor y vuelvas a ejecutar el comando fetch en la consola para desarrolladores, debería funcionar según lo previsto. Si sigues recibiendo el error, es posible que debas ejecutar el script en otro navegador. Para que el comando fetch funcione en el navegador Brave, deberás desactivar Shields. Ten en cuenta que la configuración actual de CORS puede fallar al usar el navegador Safari. Esto se debe a que Safari aplica estrictamente la Política del mismo origen (SOP), que restringe las solicitudes de otros orígenes.

Pantalla dividida que muestra la página principal de Google y la consola de Chrome DevTools, donde se ve una solicitud fetch que devuelve un arreglo de tres libros.

Permitimos https://www.google.com en la configuración de CORS, así que obtenemos una lista de libros en lugar de errores.

Próximos pasos

Aunque CORS agrega funciones útiles a tu aplicación, una configuración incorrecta puede exponerla a vulnerabilidades de seguridad. Debido a estos riesgos, debes aplicar las prácticas recomendadas siempre que sea posible para proteger tus aplicaciones de Node.js contra posibles amenazas.

Puedes configurar el middleware cors con orígenes, métodos HTTP, encabezados y solicitudes de verificación previa específicos para lograr una comunicación segura entre orígenes. Herramientas como Snyk Code y las herramientas para desarrolladores del navegador también te ayudan a encontrar de forma proactiva errores y vulnerabilidades durante el desarrollo. Este enfoque ayuda a garantizar que tu aplicación sea segura al pasar a producción y mantiene protegidos tus sistemas y usuarios.

Una vez que hayas configurado los ajustes de CORS siguiendo estas prácticas recomendadas, prueba Snyk Code para analizar tu aplicación en tiempo real y verificar dos veces tus ajustes de seguridad. Puedes realizar 200 pruebas al mes gratis.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Leer más

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.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.