Skip to main content

L’importance de vérifier les signatures des webhooks

Écrit par

Marcelo Oliveira

blog feature playwright tests

29 juin 2023

0 minutes de lecture

Les webhooks sont une technique d’intégration par rappel qui permet d’envoyer et de recevoir des informations, comme des notifications d’événements, presque en temps réel. Ils peuvent être déclenchés par des événements d’application et transmettre des données via HTTP à une autre application ou à une API tierce. Vous pouvez configurer une URL de webhook et connecter des intervenants externes pour personnaliser, étendre ou modifier vos workflows.

Les webhooks peuvent être signés ou non. Toutefois, pour des raisons de sécurité, il est recommandé d’inclure une signature vérifiable afin que le destinataire puisse confirmer que la requête provient bien de la source de webhook attendue.

Voici quelques exemples d’utilisation des webhooks aujourd’hui :

  • Notifications sur une plateforme de gestion de versions basée sur Git lorsqu’un développeur a poussé du code vers le dépôt.

  • Notifications lorsqu’un utilisateur répond à une conversation sur une plateforme de messagerie.

  • Notifications d’un service de paiement confirmant l’autorisation d’un moyen de paiement pour un achat en magasin.

Selon un rapport de Verizon, les attaques contre la chaîne d’approvisionnement représentaient 62 % des incidents d’intrusion dans les systèmes en 2022. Tout au long du cycle de vie du développement logiciel, il est essentiel de sécuriser tous les composants et toutes les pratiques impliqués dans le développement et le déploiement. Si des vulnérabilités sont exploitées, elles peuvent compromettre les partenaires concernés et perturber l’ensemble de la chaîne d’approvisionnement logicielle.

Pour contrer ces menaces et sécuriser la chaîne d’approvisionnement, les fournisseurs imposent des signatures de webhook. Par exemple, avec CircleCI, vous pouvez utiliser des signatures de webhook pour valider un secret et vous assurer que seul CircleCI — et non un acteur malveillant — appelle votre webhook. Travis CI fournit également un en-tête HTTP de signature dans le cadre de son processus de vérification des webhooks.

Dans ce tutoriel, nous allons mettre en place un webhook GitHub en Node.js qui détecte les pushs de code vers un dépôt.

Prérequis

Pour suivre ce tutoriel, vous devez :

  • Télécharger et installer Node.js et npm. 

  • Installer ngrok, un outil de proxy inverse qui crée un tunnel sécurisé entre une URL publique et votre application Node.js locale.

  • Installer Postman, une plateforme qui vous permet de créer des requêtes HTTP et d’en examiner facilement les résultats.

Vérifier les signatures de webhook avec Node.js

Créez un dossier sur votre ordinateur, puis un fichier nommé webhook.js contenant le code suivant. Pensez à remplacer la valeur de la constante repo par le chemin de votre dépôt local :

const http = require('http');
const exec = require('child_process').exec;

const repo = "C:\\Users\\your-user-name\\Documents\\GitHub\\webhook-test";

http
  .createServer((req, res) => {
    req.on('data', chunk => {

      const body = JSON.parse(chunk);

      console.log(body);

      const isMain = body?.ref === 'refs/heads/main';

      if (isMain) {
        try {
            console.log('Push event detected. Pulling repository updates...');
            exec(`cd ${repo} && git fetch && git pull`);
            console.log('Git repository pulled successfully.');
        } catch (error) {
          console.log(error);
        }
      }
      res.end();
    });
  })
  .listen(8080);

Exécutez ensuite la commande suivante dans votre terminal :

node webhook.js

Voici ce que nous cherchons à réaliser avec le fichier webhook.js :

  1. La constante repo contient le chemin du dossier qui héberge le dépôt que nous voulons surveiller.

  2. La fonction createServer démarre un serveur HTTP sur notre machine, sur le port 8080. L’application de webhook est maintenant en cours d’exécution et prête à recevoir des requêtes à l’adresse http://localhost:8080.

  3. Une fois la requête entrante traitée, la constante body en contient l’essentiel. Une requête adressée à notre endpoint signifie que nous recevons une notification du webhook GitHub à l’URL de payload locale http://localhost:8080, nous informant qu’un utilisateur a poussé du code vers le dépôt que nous surveillons.

  4. Nous voulons nous assurer que le push a bien été effectué sur la branche main. Pour le vérifier, nous analysons les données JSON de la requête dans un objet body et utilisons la constante isMain pour indiquer si la référence correspond à la branche main.

  5. Si la requête est acceptée, nous exécutons une commande pour nous placer dans le dépôt local. Enfin, nous exécutons les commandes git fetch et git pull pour le mettre à jour.

Simuler une requête malveillante avec Postman

Nous concevons l’application Node.js pour qu’elle fonctionne avec GitHub. Toutefois, avant d’utiliser GitHub, simulons une requête malveillante avec Postman afin de montrer la vulnérabilité de notre application. Vous comprendrez ainsi l’importance des signatures.

Ouvrez l’application Postman et configurez une nouvelle requête HTTP POST vers notre URL de payload locale http://localhost:8080.

Dans l’onglet Body, ajoutez le code suivant :

{
    "ref": "refs/heads/main",
    "comment": "hi, I'm a malicious request..."
}
Requête Postman affichant la charge utile d’un webhook et une réponse 401 Unauthorized indiquant que la signature du webhook ne correspond pas au hachage de la charge utile.

Cliquez sur Send. Ouvrez le terminal où Node.js est en cours d’exécution et observez le résultat :

Sortie du terminal montrant la détection d’un événement Git push, la récupération des mises à jour du dépôt et la réussite de cette opération.

Comme vous pouvez le constater, l’application Node.js accepte une requête provenant d’une source autre que GitHub, ce qui est dangereux. Nous devons intégrer des signatures de webhook à notre code pour vérifier la légitimité de l’expéditeur.

Configurer ngrok

Dans cet exemple, l’application Node.js s’exécute en local. Cependant, GitHub ne peut pas accéder à l’endpoint http://localhost:8080 sur votre machine pour envoyer une requête de webhook. Nous pourrions résoudre ce problème en publiant l’application Node.js sur un serveur public. Mais il est plus facile d’exécuter et de déboguer Node.js sur la machine de développement que de le déployer sur un serveur Web ou dans le cloud. Nous allons donc utiliser ngrok. ngrok est un outil de proxy inverse qui crée un tunnel sécurisé entre une URL publique aléatoire et votre endpoint de webhook local.

Ouvrez un terminal sur votre machine et exécutez la commande ngrok suivante pour exposer le port local 8080 :

ngrok http 8080

ngrok confirme que le port local 8080 est exposé via un tunnel sécurisé associé à une URL publique aléatoire :

Forwarding https://<<YOUR-RANDOM-SUBDOMAIN>>.sa.ngrok.io -> http://localhost:8080

Nous allons ensuite utiliser le sous-domaine ngrok public ci-dessus pour configurer un webhook GitHub.

Configurer un webhook GitHub

Rendez-vous sur la page GitHub de création d’un dépôt et ajoutez un nouveau dépôt pour configurer le webhook :

Formulaire de création d’un dépôt affichant le titre « Créer un nouveau dépôt », un propriétaire masqué et le nom de dépôt « webhook-test ».

Cliquez sur Settings dans la barre supérieure. Sélectionnez ensuite l’option Webhooks dans la liste de gauche, puis cliquez sur Add webhook :

Page des paramètres d’un dépôt GitHub, avec « Webhooks » sélectionné dans la barre latérale et des flèches pointant vers le titre « Webhooks » et le bouton « Add webhook ».

Le formulaire Add webhook s’ouvre. Nous allons y configurer un webhook déclenché lorsqu’un développeur pousse du code vers le dépôt.

Formulaire de configuration d’un webhook GitHub affichant une URL de charge utile, le type de contenu JSON, un champ de secret, des options de déclenchement par événement et une case à cocher « Actif ».

Remplissez le formulaire comme suit :

  • Dans le champ Payload URL, saisissez l’adresse ngrok correspondant à l’endpoint de votre application Node.js locale.

  • Dans le champ Content type, sélectionnez « application/json » pour indiquer le type de contenu que GitHub enverra à l’endpoint.

  • Dans le champ Secret, saisissez « some-webhook-secret ». En production, vous devrez utiliser des mots de passe ou des phrases secrètes plus robustes pour garantir la sécurité.

  • Sous Which events would you like to trigger this webhook, sélectionnez Just the push event. Le webhook GitHub sera ainsi configuré pour l’événement de push vers un dépôt, que vous pourrez déclencher pour tester la vérification.

Remarque : Il est toujours conseillé d’activer la vérification SSL (Secure Socket Layer) dans la configuration du webhook. Elle permet aux organisations de s’assurer que les données transmises entre les systèmes sont sécurisées et ne peuvent pas être interceptées par des personnes non autorisées.

Une fois le formulaire rempli, cliquez sur Add webhook.

Formulaire GitHub d’ajout d’un webhook, avec des flèches mettant en évidence l’URL de la charge utile, le type de contenu JSON, le secret et la sélection de l’événement push.

Sécuriser le webhook

Nous devons ajouter le module crypto de Node.js pour traiter les données chiffrées. Ajoutez cette ligne au début du fichier webhook.js :

const crypto = require('crypto');

Déclarez ensuite une constante contenant la même chaîne secrète que celle configurée dans votre webhook GitHub :

const secret = 'some-webhook-secret';

Remarque : Il est fortement déconseillé d’inscrire vos secrets en dur dans le code. Cette méthode est présentée à des fins de démonstration uniquement.

Au début de la fonction req.on, nous déclarons une constante contenant la valeur de la signature SHA256 calculée à l’aide de HMAC. Obtenez la signature HMAC-SHA256 en exécutant une fonction de hachage cryptographique SHA256 sur la clé secrète partagée et la valeur transmise par la requête GitHub (représentée ici par le paramètre chunk) :

    req.on('data', chunk => {

      const hashAlgorithm = 'sha256'
      const signature = Buffer.from(req.headers['x-hub-signature-256'] || '', 'utf8')
      const hmac = crypto.createHmac(hashAlgorithm, secret)
      const digest = Buffer.from(hashAlgorithm + '=' + hmac.update(chunk).digest('hex'), 'utf8')

Nous déclarons ensuite la constante isAllowed dans l’événement req.on pour indiquer si l’en-tête x-hub-signature-256 correspond à la signature calculée. Utilisez la fonction crypto.timingSafeEqual, qui compare deux variables sans révéler d’informations de timing susceptibles d’aider un attaquant à deviner l’une des valeurs :

      const isAllowed = (signature.length === digest.length && crypto.timingSafeEqual(digest, signature));
      if (!isAllowed) {
        const msg = 'Webhook signature does not match the hash of the payload';
        console.log(msg);
        res.writeHead(401)
        res.end(msg)
        return;
      }

Dans le code ci-dessus, une correspondance signifie que nous pouvons être certains que la requête provient de GitHub et non d’un acteur malveillant. Si la requête ne provient pas de GitHub, le serveur renvoie une réponse HTTP 401 Unauthorized.

Voici quelques bonnes pratiques appliquées dans ce code, que vous devriez suivre lors du développement de vos webhooks :

  • Pour calculer les signatures de webhook à l’aide d’opérations sur des chaînes, traitez toujours le payload au format UTF-8, car il peut contenir des caractères Unicode. De plus, si vous savez à quoi vos données doivent ressembler, vous pouvez mettre en place une méthode de validation pour vérifier que les données reçues sont conformes aux attentes.

  • Certains fournisseurs de webhooks, comme GitHub, peuvent inclure un en-tête x-hub-signature dans leurs requêtes. Cet en-tête x-hub-signature est généré avec SHA-1, un algorithme de hachage considéré comme faible, mais il est conservé pour assurer la rétrocompatibilité. Dans la mesure du possible, utilisez l’en-tête x-hub-signature-256, généré avec l’algorithme robuste SHA-256.

  • Commencez la signature de hachage par "sha256=", en utilisant le secret du corps du payload de la requête.

  • Au lieu d’utiliser un opérateur d’égalité simple ("==" ou "==="), utilisez une méthode de comparaison sécurisée qui réduit le risque d’attaque par mesure du temps. Tous les langages et toutes les plateformes modernes proposent au moins une méthode de ce type. Dans ce projet Node.js, nous utilisons la fonction crypto.timingSafeEqual.

Vous trouverez le code complet de webhook.js ici.

Pour en savoir plus, consultez la documentation GitHub sur la sécurisation des webhooks.

Tester l’endpoint de webhook sécurisé

Comme vous avez modifié le code, vous devez redémarrer votre serveur Node local. Dans votre console, arrêtez le serveur Node avec Ctrl+C (ou Command+C sur Mac), puis exécutez de nouveau la commande node webhook.js.

Retournez ensuite dans Postman et renvoyez la requête. L’application Node.js répond maintenant avec le code HTTP 401 Unauthorized :

Requête Postman affichant une charge utile JSON malveillante et une réponse 401 Unauthorized indiquant que la signature du webhook ne correspond pas à l’empreinte de la charge utile

Ouvrez GitHub et ajoutez un fichier README.md si ce n’est pas déjà fait, ou ouvrez-le s’il existe. Ajoutez ou modifiez ensuite le texte comme indiqué ci-dessous :

Éditeur README.md de GitHub affichant un dépôt nommé webhook-test avec le texte « My WebHook Test » et « This is a webhook test. »

Enfin, cliquez sur Commit changes pour pousser les modifications vers la branche main du dépôt :

Boîte de dialogue GitHub « Commit changes » affichant un message de mise à jour de README.md et l’option permettant de valider directement dans la branche main, mise en évidence par une flèche violette.

Retournez maintenant dans le terminal Node.js pour vérifier que le dépôt a bien été récupéré et mis à jour :

Sortie du terminal montrant un événement push d’un webhook GitHub, la mise à jour du dépôt et la réussite de la commande Git pull.

Conclusion

Les webhooks permettent aux outils de communiquer et de transmettre des données en temps réel lorsque des événements se produisent. Vous pouvez les intégrer à vos systèmes pour créer des workflows collaboratifs flexibles et étendus, impossibles à mettre en place avec des API aux paramètres prédéfinis.

Comme ils sont ouverts par conception, les webhooks sont exposés aux menaces de sécurité. Il est donc essentiel de prendre les mesures nécessaires pour prévenir les attaques contre la chaîne d’approvisionnement.

Les signatures de webhook sont la solution recommandée : les fournisseurs peuvent configurer des secrets et générer des signatures à l’aide d’algorithmes de hachage robustes. Elles constituent un moyen simple et sûr de garantir que les échanges de données par webhook ont lieu uniquement entre des partenaires vérifiés et de confiance.

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 feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les capacités des processus de sécurité traditionnels. Agentic AppSec de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser la build ?

VulnBump évalue des agents IA sur 307 mises à niveau réelles de dépendances. Une correction n’est validée que si la vulnérabilité disparaît et que la build et les tests restent au vert.

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.