Analyse du 0day WebP
(blog.isosceles.com)- L’unique correctif de sécurité inclus dans la mise à jour stable de Chrome, CVE-2023-4863, est un débordement de tampon sur le tas dans la bibliothèque d’images WebP, et Google a indiqué qu’un exploit existait déjà en conditions réelles
- Le signalement d’Apple SEAR du 6 septembre 2023, le CVE-2023-41064 d’Apple et les éléments autour de BLASTPASS relevés par Citizen Lab concordent, ce qui suggère fortement qu’il pourrait s’agir du même bug
- La vulnérabilité a été analysée comme un problème permettant une écriture au-delà de la taille de tampon précalculée lors de la construction de la table Huffman du format de compression sans perte VP8L de WebP
- La raison pour laquelle un fuzzing classique avait du mal à la trouver tient à des conditions de déclenchement délicates : il fallait construire successivement plusieurs tables Huffman de taille maximale puis une table invalide spécifique
- Le correctif upstream de libwebp semble suffisant, mais comme libwebp est largement utilisé dans les navigateurs, systèmes d’exploitation et applications, la diffusion du patch et le sandboxing restent essentiels
Pourquoi le patch Chrome est lié à BLASTPASS
- Début septembre 2023, Google a corrigé dans une mise à jour stable de Chrome le CVE-2023-4863 signalé par Apple Security Engineering and Architecture (SEAR)
- La vulnérabilité est un débordement de tampon sur le tas dans la bibliothèque d’images WebP
- Google a déclaré savoir que « l’exploit de CVE-2023-4863 existait en conditions réelles »
- À la même période, Citizen Lab a détecté un comportement suspect sur l’iPhone d’une personne appartenant à une organisation de la société civile basée à Washington DC
- BLASTPASS est associé à un cas d’exploit zero-click zero-day via iMessage ayant servi à déployer le spyware Pegasus de NSO Group
- Après avoir transmis à Apple les résultats de son analyse technique, Citizen Lab a vu Apple publier le 7 septembre un bulletin de sécurité révélant deux CVE
- Le premier CVE d’Apple, CVE-2023-41061, correspond aux indices montrant qu’un exploit d’image, emballé dans une pièce jointe PassKit, a contourné le sandbox BlastDoor d’iMessage
- L’image malveillante semble avoir été traitée par un autre processus non sandboxé
- Le second CVE, CVE-2023-41064, est une vulnérabilité de débordement de tampon dans Apple ImageIO
- ImageIO est le framework de parsing d’images d’Apple qui identifie plusieurs formats et les redirige vers le décodeur approprié
- Faute de détails techniques, il n’était pas possible de confirmer quel format d’image était affecté par CVE-2023-41064
- Comme Apple a récemment ajouté la prise en charge de WebP à ImageIO, que l’équipe sécurité d’Apple a signalé une vulnérabilité WebP à Chrome le 6 septembre et que Google a publié un correctif d’urgence cinq jours plus tard en la marquant comme activement exploitée, il est très probable que BLASTPASS et CVE-2023-4863 soient le même bug
L’emplacement de la vulnérabilité indiqué par le patch libwebp
- En croisant l’identifiant de bug du bulletin de sécurité Chrome avec les commits open source de libwebp, le patch Fix OOB write in BuildHuffmanTable correspond à CVE-2023-4863
- Le patch a été créé le 7 septembre 2023, le lendemain du signalement d’Apple
- La vulnérabilité se situe dans VP8L, le support de compression sans perte de WebP
- La compression sans perte WebP utilise le codage Huffman pour stocker et restaurer les pixels avec une exactitude de 100 %
- L’implémentation moderne optimise le décodage en utilisant des tables plutôt qu’une structure d’arbre conceptuelle
- La version vulnérable allouait la mémoire à partir d’une taille de tampon précalculée tirée d’une table fixe, puis construisait directement les tables Huffman dans cet espace
- La nouvelle version calcule, lors d’un premier passage, la taille totale nécessaire pour la table de sortie sans effectuer d’écriture réelle
- Si cette taille totale dépasse le tampon précalculé, elle crée alors une allocation plus grande
- Le flux principal passe par
src/dec/vp8l_dec.c, avec l’allocation dehuffman_tablesdansReadHuffmanCodeset les appels àVP8LBuildHuffmanTable/BuildHuffmanTabledansReadHuffmanCode- Les tables Huffman sont divisées en 5 segments avec des tailles d’alphabet différentes
- Pour provoquer le débordement, il fallait configurer avec précision ces 5 tables
Comment le fichier de déclenchement a été construit
- La compression sans perte WebP assigne, en fonction de l’analyse de fréquence des pixels d’entrée, des suites de bits courtes aux valeurs fréquentes et des suites longues aux valeurs rares
- Les codes sont construits de sorte que le décodeur puisse toujours distinguer les longueurs des suites de bits
- L’image compressée doit inclure des informations statistiques et des affectations de codes permettant de reconstruire la correspondance entre codes et valeurs
- WebP compresse aussi les tables Huffman elles-mêmes avec du codage Huffman afin de réduire la taille du fichier
- Cette structure complique l’analyse et le déclenchement de la vulnérabilité
@mistymntncopa fourni un code de harnais permettant de générer un WebP bien formé avec des données de codage Huffman arbitraires, c’est-à-dire des code lengths- Ce harnais permettait de transmettre un tableau arbitraire de
code_lengthsà l’appel cible deBuildHuffmanTable
- Ce harnais permettait de transmettre un tableau arbitraire de
- Les expérimentations manuelles ont montré une interaction très complexe entre l’histogramme interne à
BuildHuffmanTable,num_open,num_nodeset la valeurkeyqui suit la position de départ deReplicateValue- La root table de
count[0]àcount[8]n’a pas un effet direct important surtotal_size, mais elle peut modifier l’état interne ultérieur - Les second level tables de
count[9]àcount[15]influencent directement la valeur finale detotal_size
- La root table de
- Le programme enough.c de Mark Adler affiche l’histogramme maximal possible des lookup tables d’un arbre Huffman pour une taille d’alphabet, une taille de root table et une longueur maximale de code données
- Un commentaire indique que le
kTableSizede libwebp a été calculé à l’aide de cet outil - Cet outil a permis de reproduire la taille du tampon précalculé, et l’outil de
@mistymntncopa confirmé que les code lengths produits par “enough” remplissaient à 100 % l’allocationhuffman_tables
- Un commentaire indique que le
Les conditions dans lesquelles le débordement se produit réellement
- L’outil
enoughcalcule les maximums pour des codes valides et complets- Pour l’une des petites tables, avec une taille de symbole de 40, une root table de 8 bits et une longueur maximale de code de 15, la taille maximale est de 410
- Aucun cas n’a été trouvé, parmi les codes considérés comme valides par
BuildHuffmanTable, qui produise une taille supérieure à 410
- Le débordement n’apparaît pas avec des arbres Huffman valides uniquement, mais lorsqu’un arbre Huffman invalide est injecté à la dernière étape
- On construit d’abord des tables de sortie de taille maximale avec 4 arbres Huffman valides
- Si l’on place ensuite un arbre Huffman invalide dans la dernière table,
ReplicateValuepeut écrire hors limites avant la vérification finale de cohérence
- La reproduction consiste à récupérer le commit vulnérable
7ba44f80f3b94fc0138db159afea770ef06532a0de libwebp, activer AddressSanitizer, générerbad.webpavec le code de PoC de@mistymntncop, puis le décoder avecdwebp- AddressSanitizer signale un
heap-buffer-overflowdansBuildHuffmanTable - L’écriture signalée se produit à l’adresse située juste après une zone de 11816 octets
- AddressSanitizer signale un
- Il existe plusieurs entrées capables de faire déborder
huffman_tables- Parmi les code lengths découverts, certains écrivent jusqu’à 400 octets au-delà de la fin de l’allocation
huffman_tables - Même avec un contrôle partiel sur les valeurs écrites, l’exploitation semble possible
- Parmi les code lengths découverts, certains écrivent jusqu’à 400 octets au-delà de la fin de l’allocation
- L’arbre Huffman de l’entrée invalide est partiellement déséquilibré, et une section de la branche déséquilibrée contient de nombreux nœuds internes sans enfants
- Cette structure produit des indices
keyimpossibles à atteindre avec un arbre valide
- Cette structure produit des indices
Comment le patch bloque le débordement
- Au départ, le patch pouvait sembler se contenter d’agrandir dynamiquement le tampon à la taille requise pour empêcher le débordement sur le tas
- En réalité, le premier passage de la version corrigée exécute
BuildHuffmanTablepour calculer la taille totale nécessaire sans écrire dans la table- Les entrées invalides qui provoquaient le débordement font échouer
BuildHuffmanTablelors de ce premier passage, avec un retour à 0 - Comme ce premier passage n’écrit rien, aucun accès hors limites ne se produit même si l’arbre invalide est partiellement traité
- Les entrées invalides qui provoquaient le débordement font échouer
- Aucun cas provoquant le même débordement n’a été trouvé parmi les codes valides et complets, ce qui laisse penser que ce correctif est suffisant
Pourquoi le fuzzing avait peu de chances de la trouver
- libwebp était fuzzé depuis longtemps dans Google OSS-Fuzz, et le support WebP sans perte faisait lui aussi l’objet d’un fuzzing approfondi
- La difficulté centrale tient à la complexité à la fois du format et des conditions de déclenchement
- Parmi des milliards de possibilités, il fallait d’abord construire 4 tables Huffman de taille maximale pour des tailles d’alphabet de 280 et 256
- Il fallait ensuite construire une table Huffman invalide d’une forme très spécifique pour une taille d’alphabet de 40
- À n’importe quelle étape, une seule erreur d’un bit suffisait pour que le décodeur d’image signale une erreur et s’arrête proprement
- Après le correctif du 0day WebP, Google a publié un nouveau fuzzer dédié aux routines Huffman de WebP
- Même en exécutant ce fuzzer, CVE-2023-4863 n’a pas été trouvé
- Les mutations classiques par inversion de bits, les boucles de feedback par couverture de code, ainsi que les approches fondées sur la relation entrée-état comme CmpLog d’AFL++ ont du mal à franchir les étapes intermédiaires jusqu’à cet état extrême
- Des techniques d’exécution symbolique dynamique comme TritonDSE de Quarkslab pourraient avoir une chance, mais cela n’a pas été confirmé
- Cela diffère de la vulnérabilité Load_SBit_Png de FreeType
- Load_SBit_Png n’avait pas été découvert parce que le harnais de fuzzing ne reflétait pas suffisamment la façon dont l’API était utilisée
- CVE-2023-4863 est un cas qui semble plus difficile à fuzzing à cause des contraintes propres à la vulnérabilité que d’un manque de harnais
Portée de l’impact et état de la réponse
- Citizen Lab a capturé un exploit sophistiqué utilisé en conditions réelles, et Apple comme Chrome semblent avoir déployé des mises à jour à destination de plusieurs milliards d’utilisateurs en quelques jours
- Android pouvait encore être concerné à ce moment-là
- BitmapFactory d’Android, comme ImageIO d’Apple, gère le décodage d’images et prend en charge libwebp
- À ce moment, le bulletin de sécurité Android n’incluait pas encore de correctif pour CVE-2023-4863, mais la correction avait déjà été fusionnée dans AOSP
- Si Android était affecté, cela pourrait conduire à des exploits à distance dans des applications comme Signal ou WhatsApp
- Une correction était attendue dans le bulletin de sécurité d’octobre
- Le correctif upstream de libwebp semble avoir été appliqué correctement et se diffuse vers les endroits nécessaires
- Comme libwebp est utilisé dans de nombreux environnements, il pourrait falloir du temps avant que le correctif soit suffisamment largement déployé
- Pour du code de parsing complexe, proche d’une surface d’attaque de type exploitation distante zero-click comme le décodage d’images, le fuzzing seul ne suffit pas à garantir la sécurité : il faut investir dans une revue de code proactive et un sandboxing approprié
L’asymétrie dans la divulgation des informations techniques
- Lorsque les éditeurs ne publient pas suffisamment de détails techniques, il devient plus difficile pour les défenseurs de vérifier l’impact réel d’une vulnérabilité
- Les attaquants ont une forte motivation à suivre et exploiter les vulnérabilités N-day, et l’absence de détails publics ne les ralentit généralement pas beaucoup
- Les défenseurs, eux, manquent souvent des ressources nécessaires pour mener ce niveau d’analyse technique
- Cacher jusqu’aux informations de base sur le fonctionnement de l’attaque crée une asymétrie où les attaquants disposent de davantage d’informations que les défenseurs sur la vulnérabilité et son exploitation
1 commentaires
Avis de Hacker News
Ce bug ressemble surtout au bug Timsort de 2015 [1]
Timsort est un algorithme de tri hybride astucieux issu de CPython, et plusieurs implémentations, dont OpenJDK, l’ont repris presque comme une traduction au niveau du source. Il maintient une pile de runs triés, et il existait une preuve indiquant qu’il y avait une borne finie suffisamment petite pour la taille maximale possible de la pile par construction, mais l’implémentation CPython d’origine ne correspondait pas exactement à la preuve, ce qui rendait possible un dépassement de pile dans de rares cas. C’était donc un bug de sécurité sérieux dans CPython, tandis qu’en Java ce cas lançait une exception et ne constituait pas un problème de sécurité du même type dans OpenJDK
De la même façon, ce bug WebP vient du fait que la taille maximale de la table avait été prouvée formellement, mais ne correspondait pas à la valeur présente dans le code source réel. Ce genre de bug est difficile à vérifier comme à relire. Il est facile de se dire que tout va bien parce qu’il y a une preuve et que le source semble correspondre à cette preuve. Cela ressemble à un signal fort en faveur d’une vérification formelle accessible, ainsi que de l’usage de langages à sûreté mémoire, plutôt que de simples revues humaines. Un système de types peut aussi être vu comme une forme faible de vérification formelle
[1] http://envisage-project.eu/wp-content/uploads/2015/02/sortin...
https://github.com/google/wuffs
On peut aussi satisfaire cette exigence en écrivant explicitement les mêmes vérifications, mais si l’on pensait qu’elles étaient inutiles pour des raisons de performance, il y a de fortes chances que le point où l’outil WUFFS refuse le code révèle qu’on se trompait. C’est moins fort qu’une vérification formelle complète, mais c’est une nette amélioration pour l’objectif de sûreté des programmes, et bien mieux qu’un humain qui dit « LGTM »
En dehors de la question « que faut-il corriger maintenant ? », cet article contenait plusieurs points intéressants. Même en connaissant l’emplacement de la vulnérabilité et le correctif, reproduire un PoC d’exploit peut demander pas mal de travail, et les décompresseurs sans perte à l’intérieur des décodeurs d’images peuvent être assez résistants au fuzzing. Pour les gens de la sécurité, c’est peut-être évident, mais en tant que non-spécialiste, j’ai trouvé cela intéressant à lire
Il y a quelques questions auxquelles je ne trouve pas de réponse claire. 1) D’autres navigateurs basés sur Chrome, comme Brave, sont-ils aussi affectés ? 2) Chrome sur desktop est-il aussi affecté, ou est-ce seulement un problème mobile ? 3) Pourquoi n’ai-je jamais entendu parler de WebP ? Je me demande si j’étais coupé du monde, ou si c’est une technologie avant tout mobile
Chrome sur desktop était également touché, à la fois sous Linux et sous Windows. Chrome embarque sa propre libwebp, donc même si votre distribution Linux n’a pas encore publié le correctif, si Chrome est à jour, vous êtes au moins couvert côté attaques via le navigateur
Les principaux navigateurs et systèmes d’exploitation prennent en charge un nombre étonnant de formats d’image peu connus. Par exemple, macOS peut charger du KTX2 (Khronos Texture Container), et Android du DNG (Adobe Digital Negative). Il existe beaucoup de surfaces d’attaque intéressantes et très exposées à explorer pour un attaquant
Le bug se trouve dans la bibliothèque du codec, et WebP est un écosystème pratiquement unifié côté implémentation : tout le monde utilise la même bibliothèque, donc tout le monde doit la corriger
Google a poussé WebP il y a 10 ans, mais pendant longtemps il est resté réservé à Chrome et n’a pas vraiment décollé. Il n’a pas non plus été correctement standardisé, même s’il est open source. Il compresse mieux que JPEG pour les images de faible qualité, mais sur les images de haute qualité, il a tendance à faire baver les couleurs et à rendre l’image floue. Ironiquement, au moment où WebP a commencé à être largement pris en charge, il était déjà en train de devenir techniquement dépassé par AVIF et JPEG XL
Si un appareil Android n’est plus pris en charge, est-ce qu’il est de fait exposé à des exploits 0 clic qui circulent réellement en ce moment ? Ou bien suffit-il de mettre à jour les apps SMS, Chrome, WhatsApp, Signal, etc., pour bloquer l’essentiel des vecteurs d’entrée ?
Sur les appareils qui ne sont plus pris en charge, mettre Chrome à jour corrige le problème côté Chrome, mais pour corriger des apps comme Signal ou WhatsApp, il faut une mise à niveau de l’OS Android. Chrome embarque sa propre libwebp, mais des apps très exposées comme les messageries et Gmail utilisent les interfaces fournies par l’OS pour afficher les images. Pour les appareils Android encore couverts par les mises à jour de sécurité, on peut s’attendre à des mises à jour à partir de début octobre.
Le fait qu’« il existe beaucoup d’entrées qui font effectivement déborder huffman_tables » ressemble à un problème plus général. Le logiciel A construit une table de recherche B, et cette table sert à traiter un flux de données d’entrée.
Désormais, tout développeur qui se soucie un minimum de sécurité ou de correction doit garantir l’une des deux choses suivantes : A) le logiciel est écrit de façon à ce qu’aucune donnée d’entrée ne puisse détourner ou faire échouer la table de recherche, ou B) il n’est utilisé que dans un environnement contrôlé — par exemple une communication point à point où les deux parties sont de confiance — garantissant que le flux ne détournera jamais la table de recherche ni ne provoquera d’anomalie.
B est presque impossible en dehors d’un petit groupe ou d’un bureau, et absolument impossible à l’échelle d’Internet ; il ne reste donc que A. À l’avenir, la bonne pratique générale en génie logiciel devrait être que, si l’on utilise une table de recherche pour quelque raison que ce soit, le développeur doit garantir que cette table se comporte correctement pour tous les types de données, ou bien détecter les données incorrectes et les gérer convenablement avant qu’elles n’atteignent la table.
Si j’étais un chercheur en sécurité sérieux et que j’avais du temps, je dresserais une liste de toutes les vulnérabilités de sécurité passées ayant eu un lien quelconque avec des tables de recherche, je les lirais une par une et je comparerais leurs points communs. Il y a probablement un schéma. Ensuite, j’examinerais tous les logiciels qui utilisent des tables de recherche sur des flux de données et j’auditerais leurs vulnérabilités, mais ce n’est pas le travail d’une vie pour une seule personne : c’est un sport d’équipe.
Dans quelle mesure la publication du code source a-t-elle aidé NSO à trouver ce bug ? Si le code n’avait été qu’un blob binaire, je me demande si les décompilateurs modernes auraient suffi à comprendre le code et à trouver un bug aussi obscur.
Ce qui est surprenant, c’est que ce ne soit pas une partie « nouvelle » du format d’image. La compression de Huffman existe depuis plus de 70 ans, le Huffman canonique depuis seulement quelques décennies de moins, et même JPEG utilise Huffman. C’est une technologie vieille de plusieurs décennies, avec un nombre incalculable d’articles sur sa mise en œuvre ; on aurait l’impression que les bugs auraient dû être éliminés des différentes implémentations depuis longtemps.
Comme j’avais déjà lu la spécification JPEG et écrit un décodeur, j’ai regardé la spécification WebP : https://developers.google.com/speed/webp/docs/webp_lossless_...
L’essentiel se trouve dans la section 6. La première chose qui saute aux yeux, c’est que, contrairement à la spécification JPEG, elle n’explique pas clairement comment les codes sont construits. JPEG contient de nombreux organigrammes faciles à suivre décrivant le processus. WebP ressemble à LZH/deflate (zlib), et sa « spécification » ressemble davantage à un assemblage de fragments de code source commentés qu’à une vraie spécification.
Après avoir écrit des décodeurs GIF, JPEG et PNG, je serais tenté d’écrire aussi un décodeur WebP, mais la « spécification » ci-dessus donne presque l’impression de vous dire de ne pas le faire.
Les implémentations d’arbres de Huffman impliquent de nombreux compromis différents, donc on ne peut pas considérer que, parce que la technique est ancienne, tous les bugs ont été éliminés. Charles Bloom disait que l’article de référence déterminant de 1997 sur l’optimisation de Huffman [1] était encore peu connu en 2010, et que de nombreuses optimisations avaient été redécouvertes puis oubliées. Il est donc probable qu’il existe beaucoup d’implémentations inefficaces.
[1] https://cbloomrants.blogspot.com/2010/08/08-12-10-lost-huffm...
On dirait qu’on ne peut même plus regarder des images sans se soucier de la sécurité.
Cela ressemble aux douleurs de croissance typiques liées à l’utilisation de C, un langage non sûr. Je comprends l’attrait pour la vitesse des navigateurs, mais j’aimerais que l’industrie cesse d’encourager les mêmes erreurs qu’elle répète depuis qu’elle a commencé à utiliser C.
C’est un peu comme construire un pont avec des vis autotaraudeuses plutôt qu’avec des rivets, parce que c’est plus rapide. Et quand le pont commence à s’affaisser, on se dit qu’il suffit de rajouter des vis.
« Bonne nouvelle : Apple et Chrome ont réagi de façon exemplaire, à la hauteur de l’urgence du problème », vraiment ? Difficile à accepter. C’est Google qui a classé ce problème comme spécifique à Chrome. Rien que sur les sept derniers jours, toutes les grandes distributions Linux ont dû pousser des mises à jour, et Red Hat lui a attribué un score de 9,6. Des images Docker Python téléchargées plus d’un milliard de fois, Puppeteer, WordPress, Node.js, etc. ont aussi été touchés, mais le ticket CRBug est toujours privé.
Des sites comme BleepingComputer ont rapporté ce qu’ils voyaient sans enquêter, et beaucoup d’entreprises de sécurité ont fait de même en traitant ce problème comme s’il venait d’un tiers. Quand on sait que l’autre partie n’a pas fait de due diligence, il est vraiment difficile d’établir la confiance.
Adam Caudill a écrit un bon billet, « Whose CVE is it anyway? »[0], avec l’exemple de 1Password qui a corrigé très tôt, et il cerne bien le problème dont il est question ici. Citizen Lab a refusé de dire si les deux problèmes étaient liés, mais il y a des choses qui se voient sans être un génie.
[0]: https://adamcaudill.com/2023/09/14/whose-cve-is-it-anyway/
L’auteur dit aussi que beaucoup d’autres systèmes doivent être corrigés. Mais, parmi les images Docker Python tirées un milliard de fois, combien rendent des images WebP non fiables ? Même chose pour Node, etc. Bien sûr, il faut corriger rapidement, mais ce n’est pas du même niveau qu’iOS/Android/Chrome.
Des centaines de millions de personnes dans le monde sont affectées par cette bibliothèque, et l’impact est multiplié par chaque appareil et chaque application qu’elles utilisent.
J’aime le C, mais je pense qu’une bibliothèque utilisée par des centaines de millions de personnes dans le monde ne devrait pas utiliser C. Dans des bibliothèques aussi fondamentales, le ratio de risque est trop élevé. Même un expert C finira par faire une erreur un jour.
Il me semble que voici le correctif : https://github.com/webmproject/libwebp/commit/dce8397fec159c...
« malloc fail », c’est désespérant. Slack, Discord, Teams, jusqu’à tous les OS modernes, sont touchés.
C/C++ et les langages dynamiques élargissent fortement la surface des comportements indéfinis et des bugs subtils, et restent difficiles à analyser par linting, même pour les développeurs les plus brillants, en plus d’être lourds à gérer. Les bibliothèques de base devraient être vérifiées formellement, d’une manière proche de seL4.
Un autre problème est le manque de professionnalisme répandu dans le FOSS, ainsi que le mépris de la rigueur, de la qualité, de l’exactitude et de la sécurité. Continuer à bâtir un empire sur du sable comme aujourd’hui est insensé.