Skip to main content

Implications de sécurité du partage des ressources entre origines (CORS) dans Node.js

Écrit par
Headshot of Victor Ikechukwu

Victor Ikechukwu

blog feature cors

13 septembre 2023

0 minutes de lecture

Dans les applications web modernes, le partage des ressources entre origines (CORS) permet une communication sécurisée entre des applications hébergées sur des origines différentes. Les développeurs utilisent le CORS pour accéder aux services d’autres applications depuis les leurs. Cette approche évite de réécrire des fonctionnalités de zéro, accélère le développement et améliore l’expérience des développeurs.

Bien que le CORS soit utile, une mise en œuvre incorrecte peut exposer vos applications Node.js à des risques de sécurité, comme des violations de données et des accès non autorisés depuis des sites tiers. Des erreurs de configuration peuvent révéler des données sensibles à des origines non prévues ou permettre à des sites malveillants de contourner les protections de la politique de même origine (SOP).

Comprendre ces vulnérabilités et adopter les bonnes pratiques pour mettre en œuvre le CORS de manière sécurisée contribue à atténuer ces risques, tout en vous aidant à préserver les fonctionnalités de votre application.

Dans cet article, nous commencerons par présenter le CORS et certains de ses cas d’usage. Nous utiliserons ensuite un exemple de code pour mettre en œuvre le CORS dans une application Node.js. Après avoir examiné les risques potentiels pour la sécurité, nous passerons en revue les bonnes pratiques d’utilisation du CORS et testerons la sécurité de notre exemple. Pour suivre ce tutoriel, vous aurez simplement besoin d’une expérience de JavaScript et de Node.js.

Comprendre le CORS et ses cas d’usage

Les navigateurs web utilisent un mécanisme de sécurité appelé politique de même origine (SOP) pour réguler les interactions entre les applications web. La SOP empêche les applications hébergées sur une origine de lire les ressources d’une application hébergée sur une autre origine. 

Ce mécanisme empêche les sites malveillants de lire les données d’un autre site, mais peut aussi limiter des usages légitimes. Que se passe-t-il si vous souhaitez intégrer des données météo à votre application ? Ou intégrer une vidéo YouTube à une page web ? Ces ressources publiques devraient être accessibles à tous, mais la SOP les bloque.

Le CORS permet aux applications web de contourner les restrictions de la SOP, en autorisant la communication entre différents services web et les requêtes entre origines. Les applications peuvent utiliser la méthode HTTP OPTIONS pour envoyer des requêtes préliminaires vers la ressource de l’autre origine. Ces requêtes permettent aux navigateurs web de déterminer si le serveur de l’autre origine les autorise. Selon les informations obtenues, le navigateur ignore ou applique la SOP.

Les cas d’usage courants du CORS incluent le chargement de ressources depuis des réseaux de diffusion de contenu (CDN), les appels à des API sur plusieurs domaines et l’intégration de services tiers.

Toutefois, une mise en œuvre incorrecte du CORS dans votre application comporte des risques de sécurité. Votre application peut être exposée à une attaque de falsification de requête intersites (CSRF), qui incite les utilisateurs à effectuer des actions indésirables dans l’application. De plus, des configurations trop permissives peuvent autoriser l’accès non autorisé aux données utilisateur et aux informations sensibles entre différentes origines, ce qui risque d’entraîner des violations de données ou d’exposer des vulnérabilités que des acteurs malveillants pourraient exploiter.

Par exemple, vous pourriez être tenté d’utiliser le caractère générique « autoriser tout », représenté par un astérisque (*), comme valeur de l’en-tête HTTP Access-Control-Allow-Origin. Cette configuration CORS permet à n’importe quelle origine d’interagir avec votre application, ce qui l’expose à des vulnérabilités potentielles. Privilégiez plutôt des configurations précises et restrictives. Pour définir dynamiquement les origines autorisées, envisagez d’utiliser des variables d’environnement, une solution plus sûre.

Mettre en œuvre le CORS de manière sécurisée dans une application Node.js

Dans cette section, nous allons vous montrer comment mettre en œuvre le CORS de manière sécurisée dans une application Node.js. Nous utiliserons une application Node.js simple, basée sur Express.js. Elle servira d’API pour une librairie en ligne fictive. 

L’API comporte plusieurs points de terminaison pour fetch, add, update et delete des livres, mais cet exemple mettra uniquement en œuvre la récupération des livres. Pour simplifier, l’API utilisera un objet sampleBooksData comme système de stockage des données.

Prérequis

Pour suivre ce tutoriel, vous aurez besoin des éléments suivants :

  • Une expérience de JavaScript

  • Node.js (la version 18.16.1 est recommandée)

  • Un navigateur web moderne

Créer l’application Node.js

Pour créer l’application, commencez par créer un nouveau répertoire. Ensuite, dans ce répertoire, exécutez la commande ci-dessous pour initialiser une nouvelle application Node.js :

npm init -y

Exécutez ensuite la commande suivante pour installer les dépendances de ce projet :

npm i express cors

Enfin, créez un fichier JavaScript nommé index.js. Collez-y le code ci-dessous :

// 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 met à disposition des fonctions middleware pour traiter les requêtes et les réponses au sein de l’application. Ces fonctions peuvent intercepter et modifier les requêtes entrantes ou les réponses sortantes, et ainsi prendre en charge des fonctionnalités telles que la journalisation, l’authentification ou, dans notre cas, la configuration du CORS. L’exemple ci-dessus importe le module CORS à l’aide de const cors = require("cors");. Il applique ce module comme fonction middleware à la route /books à l’aide du code suivant :

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

Toutefois, l’implémentation actuelle du middleware cors sur la route /books ne précise pas explicitement quelles origines peuvent envoyer une requête GET. Le paramètre autorise donc toutes les origines à envoyer des requêtes à ce chemin. Pour des raisons de sécurité, cette approche est déconseillée, mais vous pouvez configurer des règles plus restrictives pour votre environnement de production.

Configurer le CORS

Vous pouvez mettre en œuvre le CORS de manière plus sécurisée avec le middleware cors en utilisant quatre configurations différentes.

Activer le CORS pour des origines spécifiques

Vous pouvez définir l’option origin dans vos paramètres CORS pour configurer les origines autorisées. Ce paramètre renforce la sécurité en limitant l’accès aux sources fiables et en empêchant les requêtes interorigines non autorisées. L’exemple ci-dessous n’autorise que les requêtes provenant de `https://example.com` :

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

Configurer les méthodes HTTP autorisées

Vous pouvez également définir les méthodes autorisées à l’aide de l’option methods. Cette approche limite les vecteurs d’attaque potentiels en n’autorisant que les actions nécessaires sur les points de terminaison de votre API. L’exemple ci-dessous n’autorise que les méthodes GET et POST :

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

Définir des en-têtes personnalisés et les en-têtes exposés

Vous pouvez également configurer l’en-tête CORS Access-Control-Allow-Headers avec l’option allowedHeaders, et Access-Control-Expose-Headers avec l’option exposedHeaders. Cette approche permet à l’application de traiter correctement les données sensibles tout en assurant une communication flexible entre les composants client et serveur. L’exemple ci-dessous n’autorise que les en-têtes Content-Type et Authorization, et n’expose que X-Custom-Header :

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

Configurer les requêtes préliminaires et la mise en cache

Enfin, vous pouvez activer la mise en cache des requêtes préliminaires (OPTIONS) en définissant sa durée avec le paramètre maxAge. Cette approche permet au client de mettre en cache les informations relatives à la politique CORS du serveur pendant une durée déterminée. Lors des requêtes suivantes, les clients peuvent réutiliser les données CORS en cache sans devoir les récupérer de nouveau auprès du serveur.

Une valeur maxAge adaptée réduit le temps de réponse et la charge réseau, optimisant les performances tout en garantissant que les politiques CORS restent à jour. L’exemple ci-dessous définit maxAge sur 86400 secondes (24 heures) :

// 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;

Maintenant que nous avons configuré nos options, nous pouvons utiliser notre code comme middleware sur toute route nécessitant l’activation du CORS :

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

Bien que le CORS contribue à sécuriser l’API, il s’agit toujours d’une dépendance tierce. Nous devons nous assurer qu’elle-même et toutes les autres dépendances ajoutées au projet sont sécurisées et ne présentent aucune vulnérabilité connue. Snyk Open Source, par exemple, aide à détecter les problèmes de sécurité dans les packages pendant le développement ou dans les pipelines d’intégration et de livraison continues (CI/CD).

Implications de sécurité du CORS et bonnes pratiques

Si nous ne configurons pas correctement le CORS, nous risquons de déclencher les vulnérabilités de sécurité mentionnées plus haut. Plus précisément, nous pourrions exposer des données sensibles à des origines non prévues, autoriser des sites malveillants à effectuer des actions non autorisées (attaques CSRF) et même permettre à des attaquants de contourner les protections de la SOP à l’aide de techniques telles que le cross-site scripting (XSS).

Pour éviter ces situations, suivez toujours les bonnes pratiques lors de la configuration du CORS. Vous pouvez également prendre d’autres mesures de sécurité, comme la mise en place d’en-têtes en dehors du CORS, que nous aborderons plus loin dans cette section.

Bonnes pratiques

Des bonnes pratiques telles que la restriction des origines autorisées, l’utilisation de cookies et de jetons sécurisés et la limitation des en-têtes exposés sont essentielles pour assurer la sécurité de vos applications et de leurs utilisateurs. Nous les détaillerons ci-dessous à l’aide d’exemples de code.

Limiter les origines autorisées à une liste d’autorisation

Pour éviter d’utiliser le caractère générique « autoriser tout » (*) dans l’objet corsOptions, précisez les origines dans une liste d’autorisation :

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'));
  }
};

Utiliser des cookies et des jetons sécurisés pour l’authentification

Utilisez le protocole HTTPS pour garantir la transmission sécurisée des cookies et des jetons de votre application. Utilisez également des bibliothèques d’authentification fiables, comme Passport.js, ou des solutions basées sur les jetons Web JSON (JWT) :

corsOptions.credentials = true;

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

Dans ce code, la valeur credentials to true permet aux requêtes interorigines d’inclure des identifiants.

Limiter les en-têtes exposés et les méthodes HTTP

Pour ne spécifier que les en-têtes essentiels, utilisez l’option exposedHeaders. Utilisez également l’option methods dans votre configuration pour limiter les méthodes pour lesquelles le CORS est activé :

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

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

Mettre en œuvre des en-têtes de sécurité en dehors du CORS

Vous pouvez mettre en œuvre des en-têtes de sécurité supplémentaires en dehors du CORS pour renforcer la sécurité de votre application. Les en-têtes de sécurité HTTP sont des en-têtes qui indiquent les aspects de sécurité d’une communication HTTP entre un client et un serveur.

Helmet.js fournit un ensemble d’en-têtes de sécurité avec des valeurs par défaut adaptées. Toutefois, une certaine configuration de la politique de sécurité du contenu (CSP) est nécessaire pour activer les fonctionnalités définies par la configuration CORS. Suivez les étapes ci-dessous pour la configurer.

Tout d’abord, exécutez la commande suivante dans la ligne de commande du répertoire de votre application Node.js pour installer Helmet :

npm i helmet

Ensuite, importez le package dans votre application à l’aide du code ci-dessous :

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

Configurez ensuite la CSP pour qu’elle corresponde à vos paramètres CORS, comme ci-dessous :

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

Pour finir, vérifiez votre travail. Il est facile d’oublier un paramètre ou de faire une faute de frappe qui compromet la sécurité de votre application, surtout lorsque celle-ci évolue. Snyk Code et les extensions pour développeurs de Snyk (comme l’extension Visual Studio Code) détectent les en-têtes de sécurité manquants ou mal configurés dans votre base de code. Le recours à ces outils tiers pour traiter ces problèmes de sécurité de manière proactive pendant le développement contribue à préserver la sécurité de votre application Node.js.

Tester les implémentations CORS et leur sécurité

Pour vous assurer que votre application est protégée contre les menaces potentielles, testez votre implémentation CORS et sa sécurité. Cette approche permet de détecter les vulnérabilités et les erreurs de configuration potentielles. Les tests vérifient également que les règles CORS fonctionnent comme prévu et n’exposent pas l’application à des risques de sécurité, tels que des violations de données ou des accès non autorisés depuis des sites tiers.

Il existe plusieurs façons de tester les implémentations CORS et leur sécurité. Nous allons présenter ci-dessous quelques méthodes courantes.

Outils de développement du navigateur

Les navigateurs web modernes proposent des outils de développement pour examiner et déboguer les applications web. Vous pouvez les utiliser pour tester le CORS en inspectant les requêtes et les réponses réseau. Simulez des requêtes interorigines pour vérifier que les règles CORS les bloquent ou les autorisent comme prévu.

Nous reviendrons sur cette méthode plus loin en l’appliquant à l’application Node.js d’exemple que nous avons créée précédemment.

Postman

Vous pouvez utiliser Postman, un outil populaire de développement et de test d’API, pour tester CORS. Il vous permet de créer des requêtes HTTP et de définir des en-têtes personnalisés afin de tester les configurations CORS. Postman peut également générer automatiquement des requêtes préliminaires pour tester la mise en cache de ces requêtes. Vous pouvez aussi automatiser des scénarios de test grâce aux fonctionnalités de script de Postman.

Scripts personnalisés

Vous pouvez créer des scripts personnalisés pour tester les configurations CORS. Par exemple, utilisez un langage de script comme Python ou JavaScript pour envoyer des requêtes HTTP et vérifier la présence des en-têtes CORS dans les réponses. Vous pouvez également simuler des requêtes cross-origin et vérifier que les règles CORS les bloquent ou les autorisent comme prévu.

Comment tester une configuration CORS à l’aide des outils de développement du navigateur

Vous pouvez tester les configurations CORS de notre application Node.js d’exemple à l’aide des outils de développement du navigateur. Suivez les étapes ci-dessous.

Commencez par démarrer l’application Node.js. Dans la ligne de commande du répertoire de l’application, exécutez la commande ci-dessous :

node index.js

Ensuite, dans le navigateur de votre choix, accédez à la page d’accueil de Google.

Page d’accueil Google en mode sombre, avec une barre de recherche vide, des boutons de recherche et des options de langue, dont le haoussa, l’igbo, l’Èdè Yorùbá et le pidgin nigérian

Ouvrez ensuite la console de développement. Dans un navigateur basé sur Chromium, utilisez Option+⌘+J (sur macOS) ou Shift+CTRL+ J (sur Windows/Linux). Vous pouvez aussi utiliser la commande correspondante dans le navigateur de votre choix.

Collez le code ci-dessous, puis appuyez sur Retour/Entrée :

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

Compte tenu de la configuration CORS actuelle de l’application d’exemple, vous obtiendrez probablement des erreurs indiquant que la politique CORS a bloqué l’origine (Google) qui tentait d’accéder à la ressource /books sur votre serveur, ainsi qu’une erreur interne du serveur.

Console Chrome DevTools affichant une requête fetch bloquée par une règle CORS et une erreur 500 Internal Server Error

L’erreur d’accès bloqué s’explique par le fait que l’origine ​​https://www.google.com ne figure pas parmi les origines autorisées à contourner la SOP. Les paramètres de l’option `origin` de l’objet corsOptions, que nous transmettons au middleware cors, n’autorisent pas cette origine à recevoir une réponse du serveur. Notez que l’affichage et le message d’erreur peuvent varier selon le navigateur, mais l’erreur indiquera toujours que la requête est bloquée.

Cette erreur confirme que nos règles de configuration CORS fonctionnent comme prévu. L’application n’est pas accessible depuis des origines non autorisées.

Pour autoriser la requête provenant de https://www.google.com, indiquez cette origine dans l’option origin de l’objet 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) => { /* ... */ })

Après avoir redémarré le serveur et relancé la commande fetch dans la console de développement, tout devrait fonctionner comme prévu. Si l’erreur persiste, essayez d’exécuter le script dans un autre navigateur. Pour que la commande fetch fonctionne dans Brave, vous devez désactiver Shields. Notez que la configuration CORS actuelle peut échouer avec Safari. Cela s’explique par l’application stricte de la politique de même origine (SOP) par Safari, qui restreint les requêtes provenant d’origines différentes.

Écran divisé montrant la page d’accueil Google et la console des outils de développement Chrome affichant une requête fetch qui renvoie un tableau de trois livres.

Nous avons autorisé https://www.google.com dans les paramètres CORS : nous obtenons donc une liste de livres au lieu d’erreurs.

Étapes suivantes

Bien que CORS apporte des fonctionnalités utiles à votre application, une mauvaise configuration peut l’exposer à des failles de sécurité. Compte tenu de ces risques, appliquez les bonnes pratiques dès que possible pour protéger vos applications Node.js contre les menaces potentielles.

Vous pouvez configurer le middleware cors avec des origines, des méthodes HTTP, des en-têtes et des requêtes préliminaires spécifiques afin de sécuriser les communications cross-origin. Des outils comme Snyk Code et les outils de développement du navigateur vous aident également à détecter de manière proactive les erreurs et les vulnérabilités pendant le développement. Cette approche contribue à sécuriser votre application avant sa mise en production et à protéger vos systèmes et vos utilisateurs.

Une fois vos paramètres CORS configurés conformément à ces bonnes pratiques, essayez Snyk Code pour tester votre application en temps réel et vérifier à nouveau vos paramètres de sécurité. Vous pouvez effectuer gratuitement 200 tests par mois.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.