Résumé

  • Le parseur HTML de Cloudflare pouvait lire au-delà de son tampon et insérer dans une réponse des fragments issus d’autres requêtes, rompant l’isolation attendue entre clients d’une même périphérie.
  • Les interrupteurs d’urgence et le correctif ont arrêté les nouvelles divulgations ; les pages déjà mises en cache et les identifiants potentiellement exposés relevaient toutefois d’une seconde opération, impossible à déclarer achevée sur la foi du seul patch.

Le site déclencheur n’était pas nécessairement la victime

Le 17 février 2017, Project Zero signala à Cloudflare des pages contenant des données étrangères à leur contenu. On y trouvait des cookies, des jetons d’authentification, des corps POST et d’autres morceaux de mémoire du processus. Certaines réponses avaient été indexées par des moteurs de recherche.

Le surnom Cloudbleed rappelle Heartbleed, mais l’architecture diffère. L’erreur se produisait dans un service partagé qui transformait les pages de nombreux clients. Le HTML d’un site pouvait ouvrir le chemin défectueux alors que les octets voisins appartenaient à la requête d’un autre site. Le domaine déclencheur et le propriétaire des données occupaient deux rôles distincts.

Une liste des déclencheurs ne constitue donc pas une liste de victimes. Un client n’avait pas besoin d’utiliser la fonction en cause : il suffisait que sa requête partage le mauvais processus au mauvais instant. L’invariant rompu était plus profond que la validité d’un document : le travail d’un locataire ne doit jamais pouvoir lire ni émettre la mémoire d’un autre.

Une nouvelle mise en mémoire a réveillé un ancien défaut

Cloudflare localisa l’erreur dans du code ancien produit avec Ragel et appelé par trois transformations : le masquage d’adresses électroniques, Server-Side Excludes et la réécriture automatique vers HTTPS. L’absence d’une instruction fhold pouvait laisser un pointeur en fin de tampon ; l’appelant interprétait ensuite le résultat comme une plage valide et copiait au-delà de la limite.

La fréquence augmenta après le déploiement du parseur cf-html, qui modifiait la façon de découper les tampons. Cloudflare a pris soin de ne pas attribuer le défaut à Ragel ni à cf-html. Le nouveau composant avait changé les conditions d’exécution du vieux code et rendu visible une faute latente.

C’est le risque d’interface que l’examen d’un composant isolé ne voit pas. Un déploiement sérieux doit tester les limites exactes, comparer ancien et nouveau comportement et examiner les accidents des consommateurs hérités que la nouveauté remet en mouvement.

Une séquence d’arrêt datée précisément

Le premier pouvoir de reprise appartenait à l’opérateur de la périphérie. La chronologie UTC détaillée du rapport note la réception des informations à 00 h 32 le 18 février, l’arrêt mondial du masquage d’adresses à 01 h 19, celui de la réécriture HTTPS à 04 h 24, puis le déploiement mondial du correctif portant l’interrupteur de cf-html à 07 h 22. Server-Side Excludes ne disposait pas de son propre interrupteur global : il fallut donc en créer et en déployer un.

Un tel interrupteur n’est pas un confort d’exploitation. Il fait partie du modèle de sécurité d’une fonction. Sans lui, il faut modifier le code pendant l’urgence. Avec lui, on peut sacrifier une transformation facultative sans interrompre le reste du proxy.

Cloudflare indiqua que les clés privées SSL des clients n’avaient pas été accessibles par ce chemin, car la terminaison TLS vivait dans une autre instance NGINX. La limite n’était pas vide de secrets : la mémoire du processus HTTP contenait une clé privée utilisée pour le chiffrement entre machines Cloudflare et quelques secrets internes d’authentification. La séparation a réduit le rayon d’impact sans l’annuler.

La réécriture ultérieure en Rust a rendu l’accès hors limites impossible dans le code sûr ordinaire. Ce choix enlève une classe de faute au lieu d’exiger qu’un relecteur trouve chaque erreur de pointeur. Il ne garantit ni l’autorisation, ni les bibliothèques non sûres, ni l’ensemble du flux de données.

Une estimation n’est pas un recensement

Le premier rapport plaçait la période la plus exposée du 13 au 18 février et évaluait le phénomène à environ une requête potentiellement fuyarde sur 3,3 millions, soit 0,00003 %. L’analyse suivante estima 1 242 071 déclenchements entre le 22 septembre 2016 et le 18 février 2017 : 605 037 avant le 13 février, produits par moins de 180 sites, puis 637 034 pendant les cinq derniers jours, provenant de 6 457 sites.

Ces nombres portent leurs propres limites. Les journaux détaillés n’étaient échantillonnés qu’à 1 % et n’existaient que du 8 au 18 février. Les périodes antérieures furent reconstruites à partir d’agrégats et de traces d’accident. Cloudflare ne trouva pas de preuve d’exploitation malveillante dans les données consultées ; cela ne prouve pas qu’aucune exploitation n’a jamais eu lieu.

Le nombre de déclenchements n’est pas celui des victimes. Une moyenne très faible ne mesure pas la gravité d’un jeton particulier. L’absence de trace dans un échantillon ne ferme pas l’histoire.

Le correctif ne rappelait pas les octets

La désactivation empêcha de nouvelles réponses contaminées, sans retirer celles déjà livrées. Cloudflare estima que les robots de recherche représentaient plus de la moitié des déclenchements, fit purger plus de 80 000 pages uniques et trouva dans des caches tiers des données associées à 150 clients.

Ces chiffres ne donnent pas une borne supérieure. Toute page purgée ne contenait pas nécessairement un secret ; 150 clients identifiés ne signifient pas 150 clients au total ; aucun opérateur de cache ne peut prouver l’absence de copies privées. Le confinement et la récupération possèdent des critères de fin différents.

Le patch démontre au mieux que le chemin connu ne crée plus de fuite dans les conditions testées. Les détenteurs des caches doivent effacer leurs exemplaires. Les clients doivent révoquer ou remplacer sessions, jetons, clés d’API et mots de passe dans chaque système qui leur accordait un pouvoir. Le refus de l’ancien secret, et non la seule émission d’un nouveau, constitue la preuve utile.

Une règle commune minimale, des choix locaux réversibles

La primauté du code en fonctionnement de Heng Lu oblige à rechercher un résultat observable : aucune mémoire étrangère dans les réponses, état des fonctions vérifiable, anciens identifiants refusés et caches résiduels traités par ceux qui les contrôlent. La mention « résolu » ne produit aucun de ces faits.

La spécification initiale minimale tient en une phrase : une réponse ne contient que les données autorisées pour cette réponse. Les transformations facultatives, leur parseur, leur cadence de déploiement et leur arrêt restent des décisions locales. Elles peuvent évoluer ou disparaître sans affaiblir la membrane commune ni imposer le même risque à tous les clients.

Cloudbleed n’a pas condamné l’infrastructure partagée. Il a montré qu’elle exige une membrane dure et que toute fonction optionnelle qui la traverse doit pouvoir disparaître avant le service entier.

Sources