Aller au contenu

Note d'ingénierie

Faille Node.js Brotli (CVE-2026-111497) : comprendre et corriger

Faille Node.js critique CVE-2026-111497 dans le décodeur Brotli : exécution de code à distance, accessible sans authentification sur Express 5 et NestJS. Qui est concerné, comment vérifier sa version, mettre à jour Node.js et se protéger en attendant.

Illustration de l'article « Faille Node.js Brotli (CVE-2026-111497) : comprendre et corriger »
Par Alexandre Kaczor9 min de lecture
  • Sécurité
  • Node.js
  • Ops
  • NestJS

Le 21 octobre 2026, le projet Node.js a publié Node.js 22.23.4, 24.22.1 et 26.12.1. Ces versions corrigent dix failles, dont une particulièrement sérieuse : CVE-2026-111497, une écriture hors limites dans le décodeur Brotli qui, selon l'avis officiel, permet l'exécution de code à distance. Le même jour, l'équipe PHP publiait ses correctifs contre PHP2Shell. Deux piles parmi les plus répandues du web ont donc dû être corrigées la même semaine.

Node.js fait tourner une grande partie des API modernes : Express, NestJS, Fastify, les serveurs Next.js, les outils internes, les bots. Si votre application ou votre site s'appuie sur Node.js, cet article explique ce que corrige cette mise à jour, comment savoir si vous êtes exposé et comment corriger sans rien casser.

Ce que corrige la mise à jour d'octobre 2026

L'avis de sécurité liste dix vulnérabilités, réparties sur les trois branches maintenues (22, 24 et 26). Les quatre plus graves, notées « élevée » :

  • CVE-2026-111497, décodeur Brotli. Une écriture hors limites quand Node.js décompresse des données Brotli fournies par un tiers. C'est celle qui permet l'exécution de code, et c'est celle de cet article.
  • CVE-2026-106069 et CVE-2026-105483, vérification TLS. Dans certains cas, Node.js accepte un certificat qui ne correspond pas au serveur contacté : nom d'hôte interprété comme une adresse IP, ou adresse écrite avec des chiffres « pleine chasse ». Une application qui appelle un service externe peut alors parler à un imposteur.
  • CVE-2026-105482, résolution DNS. dns.resolve tronque un nom qui contient un caractère nul : la requête porte sur un autre nom que celui que le code croit vérifier.

Le reste est de gravité moyenne ou faible : connexions keep-alive, aléa de crypto.randomUUID() figé dans les instantanés de démarrage et les exécutables autonomes, modèle de permissions. La version embarque aussi des mises à jour d'OpenSSL, c-ares, SQLite et Undici, et retire la confiance à l'autorité de certification intermédiaire eMudhra, compromise.

La faille Brotli, sans jargon

Brotli est un format de compression, comme gzip, mis au point par Google. Les navigateurs et les serveurs l'utilisent pour alléger les pages et les réponses d'API. Node.js intègre un décodeur Brotli, et c'est ce décodeur qui contient l'erreur : des données compressées construites pour l'occasion lui font écrire en dehors de la zone mémoire prévue. Une écriture mémoire contrôlée par un attaquant, c'est la matière première d'une prise de contrôle du processus.

La question décisive est donc : votre application décompresse-t-elle des données Brotli qui viennent de l'extérieur ? Souvent oui, sans que personne l'ait décidé.

  • Express 5, et donc NestJS 11 et suivants. Le lecteur de corps de requête d'Express 5 (body-parser 2) décompresse automatiquement les requêtes envoyées avec l'en-tête Content-Encoding: br. Il suffit d'un express.json(), la configuration par défaut, pour qu'une requête anonyme atteigne le décodeur avant toute authentification. Express 4 ne décompressait que gzip et deflate.
  • fetch() et Undici. Le fetch intégré à Node.js décompresse les réponses Brotli. Une application qui appelle des URL fournies par l'utilisateur (aperçu de lien, webhook, import) décompresse ce que renvoie un serveur choisi par l'attaquant.
  • Code applicatif. Tout appel à zlib.brotliDecompress ou createBrotliDecompress sur un fichier ou un flux reçu de l'extérieur.

J'ai vérifié sur mes propres API NestJS avant de corriger : une requête anonyme, sans jeton, arrivait bien jusqu'au décodeur. C'est le scénario le plus défavorable, une porte ouverte sur Internet devant laquelle il n'y a rien à franchir.

Qui est concerné

  • Node.js 22, 24 et 26 : corrigés dans 22.23.4, 24.22.1 et 26.12.1. Toute version antérieure de ces branches est vulnérable.
  • Node.js 20, 25 et plus ancien : branches en fin de vie, aucune version corrigée. L'avis précise qu'elles sont touchées elles aussi. Node.js 20 n'est plus maintenu depuis avril 2026 : s'il tourne encore chez vous, c'est la migration vers 22 qu'il faut faire.
  • Applications « clés en main » : un outil en Node.js installé il y a deux ans (CMS headless, outil de supervision, bot) embarque sa propre version. Il faut une nouvelle version de l'outil ou de son image Docker.

Vérifier sa version de Node.js, au bon endroit

Même piège qu'avec PHP : la version de votre poste ou du serveur n'est pas forcément celle qui sert l'application. En production, Node.js tourne le plus souvent dans un conteneur qui embarque la sienne.

# Version installée sur la machine
node -v

# Version réellement servie, conteneur par conteneur
docker ps --format '{{.Names}}' | while read c; do
  printf '%s  ' "$c"; docker exec "$c" node -v 2>/dev/null || echo '(pas de Node)'
done

# Process manager (PM2) : version utilisée par chaque processus
pm2 ls

Pour savoir si une API accepte les corps compressés en Brotli, un test inoffensif suffit : envoyer un petit JSON valide, compressé, à une route publique. Une réponse 415 Unsupported Media Type (« unsupported content encoding ») signifie que le serveur refuse ; toute autre réponse signifie qu'il a décompressé.

printf '{}' | brotli -c | curl -s -o /dev/null -w '%{http_code}\n' \
  -X POST https://api.exemple.fr/une-route-publique \
  -H 'Content-Type: application/json' -H 'Content-Encoding: br' --data-binary @-

Ne lancez ce test que sur vos propres serveurs.

Corriger : mettre à jour Node.js

Les versions correctives ne changent que des correctifs de sécurité dans la même branche : passer de 22.20 à 22.23.4 ne demande aucune modification de code. Il n'y a pas d'autre correction complète.

  • Docker avec une étiquette flottante (node:22-alpine, node:22-bookworm-slim). L'image de base a été republiée, mais votre machine de build garde l'ancienne en cache. Récupérez-la, puis reconstruisez l'application depuis le commit déjà en production, pour ne livrer que le correctif et pas le travail en cours.
docker pull node:22-alpine
docker build --pull --no-cache -t mon-api:node-22.23.4 .
docker run --rm mon-api:node-22.23.4 node -v      # doit afficher v22.23.4
  • Docker avec une image épinglée (node:22.20.0-alpine@sha256:…). C'est la bonne pratique, et ici elle demande une action explicite : remplacer la version et l'empreinte dans chaque Dockerfile par celles de 22.23.4, puis reconstruire. Gardez la même distribution (Alpine reste Alpine, Debian reste Debian) pour ne pas changer d'autres bibliothèques au passage.
  • Serveur sans conteneur. Avec les paquets NodeSource : sudo apt update && sudo apt install --only-upgrade nodejs. Avec nvm : nvm install 22.23.4, puis pointez le service dessus. Dans les deux cas, redémarrez les processus : un processus lancé avant la mise à jour garde l'ancien binaire en mémoire.
  • CI et poste de développement. Moins urgent mais à faire : la version de actions/setup-node, le fichier .nvmrc et le champ engines doivent suivre, sinon vous testez sur une version et livrez sur une autre.

Après le redéploiement, vérifiez la version dans chaque conteneur en service, puis testez les parcours qui comptent : connexion, inscription, paiement, envoi de fichier. J'ai corrigé 37 conteneurs sur trois serveurs de cette façon, application par application, en comparant après chaque bascule les codes de réponse d'une liste d'URL de référence avec ceux d'avant. Aucune différence, aucune coupure.

Si vous ne pouvez pas mettre à jour tout de suite

Ces mesures ferment le chemin d'attaque le plus exposé, les corps de requête compressés. Elles ne corrigent ni fetch, ni les failles TLS. Elles font gagner des heures, pas des semaines.

  • Refuser les corps Brotli au niveau du proxy. Les clients légitimes (navigateurs, applications mobiles) n'envoient presque jamais de corps de requête compressés. Avec nginx : if ($http_content_encoding ~* br) { return 415; } dans le bloc server. Avec Caddy : un matcher @br header Content-Encoding *br* suivi de respond @br 415.
  • Refuser la décompression dans l'application. L'option inflate: false d'Express (express.json({ inflate: false })) rejette tout corps compressé avec une erreur 415. Sur NestJS, désactivez le lecteur par défaut (bodyParser: false dans NestFactory.create) et branchez le vôtre avec cette option.
  • Ne pas exposer le serveur Node.js directement. Un proxy ou un pare-feu applicatif devant l'application permet d'appliquer ces règles en un seul endroit, à condition que le pare-feu du serveur refuse les connexions qui le contournent.

Ce que cette faille dit de votre maintenance

La leçon est la même que pour PHP2Shell, et elle tient en une phrase : le jour où une faille de ce niveau sort, ce qui compte, c'est le nombre d'heures qu'il vous faut pour être à jour partout.

  • Savoir ce qui tourne. Quelle version de Node.js, dans quel conteneur, sur quel serveur. Sur trois serveurs, la commande ci-dessus prend une minute. Sans inventaire, on oublie l'outil de supervision installé il y a un an, qui tourne encore sur une branche abandonnée.
  • Épingler les versions, et savoir les déplacer. Une image épinglée par empreinte garantit qu'on livre ce qu'on a testé. Il faut seulement que changer cette empreinte soit une opération banale, documentée, faite en dix minutes.
  • Pouvoir redéployer le commit en production. Corriger le moteur ne doit pas obliger à livrer en même temps trois semaines de fonctionnalités non terminées.
  • Garder un retour arrière. L'ancienne image conservée sous une autre étiquette permet de revenir en arrière en une commande si un parcours casse.
  • Rester sur une branche maintenue. Une API encore en Node.js 20 n'a rien reçu cette fois-ci. Voir aussi les douze failles les plus courantes d'une API NestJS, qui relèvent elles de votre code.

Ce que j'en retiens

  • CVE-2026-111497 touche le décodeur Brotli de Node.js ; Express 5 et NestJS 11 le rendent accessible à une requête anonyme dans leur configuration par défaut.
  • La correction est la mise à jour vers 22.23.4, 24.22.1 ou 26.12.1, suivie d'une reconstruction des images et d'une vérification de la version réellement servie.
  • Node.js 20 et 25 ne reçoivent aucun correctif : c'est une migration à planifier tout de suite.
  • Refuser les corps compressés au proxy ou avec inflate: false fait gagner du temps, sans remplacer le correctif.
  • Une image épinglée, un inventaire et un redéploiement du commit en production transforment une alerte critique en une heure de travail.

Votre application tourne sous Node.js et vous ne savez pas sur quelle version, ni qui doit la mettre à jour ? Je peux faire l'inventaire avec vous, appliquer le correctif sans coupure et mettre en place la maintenance qui évite la prochaine urgence. Décrivez-moi votre installation, je vous réponds rapidement.

Sources : avis de sécurité du projet Node.js du 21 octobre 2026 (nodejs.org), notes de version de body-parser 2. Article à jour au 23 octobre 2026 ; les recommandations de votre distribution et de votre hébergeur priment.