Neutron, notre moteur d’IA, a obtenu un score de 97,1 % sur le benchmark CyberGym de l’UC Berkeley. En savoir plus

Analyse approfondie

Analyse approfondie

Contournement de la protection billion laughs de libexpat, corrigé dans 2.10.0 (CVE-2026-115506)

libexpat jusqu’à 2.9.0 détecte trop tard certaines variantes de billion laughs ; la version 2.10.0 corrige ce problème et trois autres défauts d’analyse.

TL;DR

  • libexpat 2.10.0, publiée le 2 novembre 2026, corrige quatre failles de sécurité. La plus importante est CVE-2026-115506, une variante de billion laughs que la protection existante détecte trop tard. Les trois autres sont un dépassement d’entier signé dans storeAtts (CVE-2026-118370), un déni de service via XML_ParserReset (CVE-2026-113351) et un déréférencement de pointeur NULL sur une entrée vide de sous-analyseur (CVE-2026-117610).
  • Toutes les versions jusqu’à 2.9.0, la version précédente, sont touchées. Les enregistrements CVE évaluent CVE-2026-115506 à 8,7 High, CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N, et les trois autres à 8,2 High.
  • Aucune exploitation n’est connue et aucun des quatre CVE ne figure au catalogue KEV de la CISA au 2 novembre 2026. Le générateur public de charges de Hanno Böck a produit les variantes qui ont révélé l’écart de détection.
  • Mettez à jour vers 2.10.0, ou attendez le rétroportage de votre distribution. Quiconque embarque une copie d’Expat doit mettre à jour l’arborescence copiée, comme le demande le mainteneur.

Suis-je concerné ?

Toutes ces conditions doivent être réunies :

  • Vous exécutez libexpat 2.9.0 ou plus ancien, ou un logiciel qui l’embarque. Le module xml.parsers.expat de Python lie Expat, et le dépôt CPython embarque lui-même une copie dans Modules/expat.
  • Votre logiciel analyse des documents XML contrôlés par l’attaquant, ou utilise une bibliothèque qui le fait.
  • Chaque faille fixe la barre différemment : CVE-2026-115506 n’exige qu’un document de taille modérée, tandis que CVE-2026-118370 exige un document de plusieurs gigaoctets dont la balise d’ouverture porte plus de 2^30 attributs.

Le mainteneur décrit Expat comme « packaged everywhere » et le classe parmi les deux analyseurs XML libres en C les plus utilisés. Les paquets des distributions le portent comme bibliothèque d’exécution, et les piles Python, Perl et Apache reposent dessus. Le suivi de sécurité de Debian liste déjà le paquet expat comme vulnérable dans toutes les suites prises en charge.

Chronologie de la version de sécurité libexpat 2.10.0 : premier CVE publié le 20 octobre, correctifs fusionnés du 23 au 29 octobre, version livrée le 2 novembre 2026.
Figure 1 : 13 jours se sont écoulés entre le premier CVE publié et la version qui corrige les quatre. Sources : enregistrements CVE, requêtes de tirage libexpat et version 2.10.0, vérifiés le 2 novembre 2026.

Branche Versions touchées Version corrigée
libexpat stable 2.7.2 à 2.9.0 (CVE-2026-113351) ; 1.95.2 à 2.9.0 (les trois autres) 2.10.0 (2026-11-02)

Vérifiez la version que vous liez. Debian et Ubuntu la livrent sous libexpat1 ; l’interpréteur Python affiche la version de la copie qu’il lie :

dpkg -l libexpat1
python3 -c "import xml.parsers.expat as e; print(e.EXPAT_VERSION)"

Un affichage comme expat_2.9.0 désigne la version touchée. Au 2 novembre 2026, le suivi de sécurité de Debian marque le paquet expat vulnérable et non corrigé dans bookworm, trixie, forky et sid.

Comment corriger ?

Mettez à jour vers libexpat 2.10.0.

  1. Si vous installez depuis votre distribution, attendez sa mise à jour de sécurité et appliquez-la dès sa parution ; l’avis du mainteneur précise que la version rend les quatre correctifs disponibles pour rétroportage.
  2. Si vous compilez depuis les sources ou embarquez une copie, mettez à jour l’arborescence copiée vers le tag R_2_10_0 et recompilez chaque consommateur. L’avis du mainteneur demande aux empaqueteurs et aux projets embarquant Expat de passer à 2.10.0.
  3. Si vous maintenez un rétroportage, la requête de tirage #1439 porte le complément que le mainteneur signale comme essentiel lors du rétroportage du correctif billion laughs.

La mise à jour reste le correctif principal. Expat permet de désactiver les entités générales à la compilation avec XML_GE, disponible depuis 2.6.0. À l’exécution, XML_SetDefaultHandler supprime l’expansion des entités générales définies en interne dans le texte, mais son comportement et sa disponibilité dépendent de l’application ou du binding. Ce n’est pas un commutateur universel à l’exécution pour les applications déployées. Les applications sans besoin de DTD peuvent rejeter les documents avec DTD avant l’analyse, y compris les documents légitimes. Consultez la documentation de l’API Expat.

Quelle est la cause première ?

Les quatre failles se situent dans expat/lib/xmlparse.c, le cœur de l’analyseur. Cette section cite le code vulnérable au tag 2.9.0.

CVE-2026-115506 : le contrôle d’amplification s’exécute après le travail d’expansion. Expat compte les octets directs et indirects par jeton pendant l’analyse, et rejette lorsque le rapport dépasse un seuil. La fonction de contrôle est :

static XML_Bool
accountingDiffTolerated(XML_Parser originParser, int tok, const char *before,
                        const char *after, int source_line,
                        enum XML_Account account) {
  ...
  *additionTarget += bytesMore;
  const XmlBigCount countBytesOutput
      = rootParser->m_accounting.countBytesDirect
        + rootParser->m_accounting.countBytesIndirect;
  const float amplificationFactor
      = accountingGetCurrentAmplification(rootParser);
  const XML_Bool tolerated
      = (countBytesOutput < rootParser->m_accounting.activationThresholdBytes)
        || (amplificationFactor
            <= rootParser->m_accounting.maximumAmplificationFactor);
  ...
  return tolerated;
}

La fonction est appelée depuis les chemins de traitement des jetons, ce qui signifie qu’Expat a déjà développé le contenu de l’entité d’un jeton au moment où les compteurs sont mis à jour. Le générateur lololol de Hanno Böck a trouvé des combinaisons de paramètres où l’analyseur exécute des dizaines de secondes d’expansion avant que le rapport cumulé franchisse le seuil. Le correctif, requête de tirage #1433, ajoute un champ expandedSize à chaque entité et précalcule jusqu’où une entité s’étendra avant de commencer l’expansion. Le rejet survient alors en amont, et la requête de tirage #1439 étend l’analyse aux sections CDATA, aux commentaires et aux instructions de traitement dans les valeurs d’entités.

CVE-2026-118370 : deux compteurs signés dans storeAtts débordent. En 2.9.0, la fonction déclare :

static enum XML_Error
storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr,
          TAG_NAME *tagNamePtr, BINDING **bindingsPtr,
          enum XML_Account account) {
  DTD *const dtd = parser->m_dtd; /* save one level of indirection */
  int attIndex = 0;
  XML_Char *uri;
  int nPrefixes = 0;

Les deux compteurs int bouclent à 2^31 attributs. Une balise d’ouverture forgée portant plus de 2^30 attributs fait déborder l’arithmétique hors limites et corrompre le tas. Le correctif, requête de tirage #1450, passe les deux en size_t et ajoute des contrôles explicites INT_MAX là où les valeurs sont stockées dans des champs entiers de l’analyseur.

CVE-2026-113351 : XML_ParserReset ne réinitialise jamais le compteur d’allocations. Expat 2.7.2 a ajouté un compteur de tas qui tolère des allocations proportionnelles à la taille du document. Réinitialiser un analyseur pour le réutiliser conserve la mémoire en réserve, mais le total bytesAllocated du compteur conserve les allocations du document précédent. Un document ordinaire présenté ensuite sur le même analyseur peut alors être rejeté avec XML_ERROR_NO_MEMORY. Le correctif, requête de tirage #1431, enregistre bytesAllocatedWhenLastReset et le soustrait, pour que chaque document reparte de zéro.

CVE-2026-117610 : un cas d’absence manquant. externalEntityInitProcessor2 en 2.9.0 gère XML_TOK_BOM, XML_TOK_PARTIAL et d’autres, mais n’a pas de cas XML_TOK_NONE. Une première analyse vide et non finale sur un sous-analyseur d’entité externe continue vers externalEntityInitProcessor3, et une analyse ultérieure plante. La requête de tirage #1444 ajoute le cas et renvoie proprement XML_ERROR_NONE.

Comment la faille est-elle exploitée ?

Aucun exploit complet n’est publié pour l’un des quatre. Les entrées d’attaque proviennent des enregistrements CVE et de l’avis du mainteneur.

  1. L’attaquant envoie un document XML de taille modérée avec une DTD volumineuse et des expansions d’entités imbriquées. Le générateur public cale ces documents pour rester sous le seuil de détection le plus longtemps possible, et l’analyse consomme des dizaines de secondes d’UC avant que accountingDiffTolerated la rejette (CVE-2026-115506).
  2. L’attaquant soumet un document de plusieurs gigaoctets dont la seule balise d’ouverture porte plus de 2^30 attributs. Les compteurs int de storeAtts bouclent, et la boucle de copie écrit au-delà de ses tampons (CVE-2026-118370).
  3. L’attaquant analyse un grand document sur un analyseur que l’application réutilise via XML_ParserReset, puis un second document ordinaire échoue avec XML_ERROR_NO_MEMORY jusqu’à recréation de l’analyseur (CVE-2026-113351).
  4. L’attaquant contrôle le découpage en blocs du flux d’une entité externe et le démarre par un bloc vide non final, ce qui plante le processus à l’analyse suivante (CVE-2026-117610).

Le générateur qui produit les documents d’attaque de l’étape 1 est public : github.com/hannob/lololol.

Comment se déroule l’attaque ?

En une phrase : un document forgé avec un arbre d’entités profond s’étend plus vite que le contrôle d’amplification ne le remarque, l’analyseur brûle donc de l’UC avant de rejeter, ou les compteurs d’attributs bouclent vers une écriture hors limites.

flowchart LR A["Document XML contrôlé
par l’attaquant"] --> B["L’expansion d’entités
ajoute des octets indirects"] B --> C["Le contrôle d’amplification ne s’exécute
qu’après chaque jeton"] C --> D["Le travail d’expansion précède
le rejet"] D --> E["Des dizaines de secondes d’UC
par analyse"]

Que peut faire un attaquant ?

Les impacts documentés sont un déni de service du processus d’analyse et, pour CVE-2026-118370, des écritures hors limites dans le tas :

  • L’attaquant n’a besoin d’aucun privilège ni interaction au-delà de la livraison d’un document à un système qui l’analyse. Les quatre vecteurs CVSS v4.0 enregistrent un vecteur d’attaque réseau.
  • CVE-2026-115506 et CVE-2026-113351 sont des failles de déni de service. Les enregistrements CVE les décrivent comme telles, et l’avis du mainteneur n’évalue aucun impact d’exécution de code.
  • CVE-2026-118370 est une corruption de mémoire avec une précondition exigeante : un document de plusieurs gigaoctets avec plus de 2^30 attributs dans une seule balise d’ouverture. L’enregistrement CVE s’arrête à « plantage des applications ou corruption de la mémoire du tas », et aucune source ne démontre l’exécution de code.
  • CVE-2026-117610 plante le processus quand l’attaquant contrôle le découpage du flux, précondition que l’enregistrement CVE signale.

Pourquoi le correctif fonctionne-t-il ?

Les quatre requêtes de tirage ferment les quatre mécanismes.

Pour CVE-2026-115506, la requête de tirage #1433 précalcule le expandedSize de chaque entité avant le début de l’expansion, et le contrôle d’amplification consulte la taille projetée au lieu d’attendre les compteurs d’octets :

  /* Indirect accounting bytes charged when this entity is expanded: 1 per text
     character, plus each reference's "&name;"/"%name;" token length and its own
     expansion. ... */
  XmlBigCount expandedSize;

Pour CVE-2026-118370, la requête de tirage #1450 change les deux compteurs et borne les stockages :

-  int attIndex = 0;
+  size_t attIndex = 0;
   XML_Char *uri;
-  int nPrefixes = 0;
+  size_t nPrefixes = 0;

Pour CVE-2026-113351, la requête de tirage #1431 ajoute le repère de réinitialisation :

+  size_t bytesAllocatedWhenLastReset;

et XML_ParserReset enregistre désormais le total d’allocations au moment de la réinitialisation, pour que le contrôle de tolérance du document suivant soustraie ce que le précédent a laissé.

Pour CVE-2026-117610, la requête de tirage #1444 ajoute le cas manquant :

+  case XML_TOK_NONE:
+    if (! parser->m_parsingStatus.finalBuffer) {
+      *endPtr = start;
+    }
+    return XML_ERROR_NONE;

Le mainteneur note que la version ferme 12 failles de sécurité au total contre 20 nouvelles découvertes sur la période, avec 13 problèmes de sécurité connus encore non corrigés à la publication. Les quatre correctifs sont complets pour les mécanismes qu’ils visent ; savoir si d’autres variantes de billion laughs subsistent est précisément ce que le générateur continue de sonder.

Comment vérifier si j’ai été compromis ?

Aucune des quatre failles ne fuit de données par conception : les vérifications portent sur les anomalies de disponibilité.

  1. Examinez les journaux des analyseurs XML traitant des entrées non fiables : XML_ERROR_NO_MEMORY peut justifier une investigation sur CVE-2026-113351 ; une consommation prolongée de CPU pendant l’analyse et des erreurs XML_ERROR_AMPLIFICATION_LIMIT_BREACH peuvent justifier une investigation sur CVE-2026-115506. Ces observations seules ne prouvent pas une exploitation.
  2. Cherchez les requêtes livrant des charges XML anormalement volumineuses aux services qui les analysent, y compris toute soumission de plusieurs gigaoctets (CVE-2026-118370).
  3. Pour CVE-2026-117610, examinez les vidages de plantage dans les chemins de sous-analyseur d’entité externe.

Un document billion laughs rejeté par Expat ne laisse aucune trace au-delà de l’erreur de rejet, et un rejet ne signifie pas que le service a été dégradé.

Chronologie

  • 2026-10-20 : CVE-2026-113351 est publié pour le déni de service XML_ParserReset, signalé par Pu Cheng.
  • 2026-10-22 : CVE-2026-115506 est publié pour l’écart de détection billion laughs, après la découverte des variantes par le générateur de Hanno Böck.
  • 2026-10-23 : le correctif de XML_ParserReset, requête de tirage #1431, est fusionné.
  • 2026-10-26 : le complément billion laughs, requête de tirage #1439, est fusionné.
  • 2026-10-28 : CVE-2026-117610 et CVE-2026-118370 sont publiés.
  • 2026-10-29 : les derniers correctifs, requêtes de tirage #1444 et #1450, sont fusionnés.
  • 2026-11-02 : libexpat 2.10.0 est publiée avec les quatre correctifs.

FAQ

Laquelle des quatre prioriser ?

CVE-2026-115506. Son entrée est un document de taille modérée que tout point d’entrée XML accepte, et son coût est de dizaines de secondes d’UC par analyse. CVE-2026-118370 est une corruption de mémoire, mais sa précondition de plusieurs gigaoctets la rend bien plus difficile à exploiter.

Les applications Python sont-elles concernées ?

Toute la chaîne est concernée dès qu’un processus lie un Expat touché, y compris Python via xml.parsers.expat. L’interpréteur affiche la version liée via EXPAT_VERSION. Sur les constructions Python liées au paquet système, la mise à jour du libexpat1 système met à jour l’analyseur.

L’une de ces failles a-t-elle été exploitée ?

Aucune exploitation n’est signalée pour l’une des quatre, et aucune ne figure au catalogue KEV de la CISA au 2 novembre 2026. Le générateur public démontre l’attaque, pas une utilisation réelle.

Pourquoi un plancher 2.7.2 pour une faille et aucun pour les autres ?

Le compteur d’allocations que CVE-2026-113351 casse a été ajouté en 2.7.2 : cette faille ne peut pas exister avant. Les trois autres précèdent leurs protections ou vivent dans des chemins présents avant 2.0, d’où le « jusqu’à 2.9.0 » des enregistrements.

Peut-on simplement désactiver l’expansion d’entités ?

Les contrôles à la compilation et à l’exécution diffèrent : XML_GE contrôle les entités générales à la compilation, tandis que XML_SetDefaultHandler modifie leur expansion interne dans le texte. Vérifiez ce qu’expose votre application ou binding et testez la compatibilité ; la mise à jour vers 2.10.0 reste le correctif principal.

Qui a trouvé et corrigé ces failles ?

Hanno Böck a trouvé les variantes billion laughs avec son générateur, et Pu Cheng a signalé la faille de réinitialisation. Shani Yosef a apporté l’idée clé du correctif avant expansion, comme le créditent l’avis de publication et les enregistrements CVE.

Trouvez où Expat s’exécute avec Ostorlab

Les scans de dépendances d’Ostorlab construisent l’inventaire des paquets installés par vos services et vos images de conteneurs, pour repérer chaque déploiement portant un libexpat touché. Lancez un scan avec Ostorlab pour confirmer la mise à jour 2.10.0 ou rétroportée partout où elle compte.

Références

  • Avis de publication du mainteneur d’Expat, « Expat 2.10.0 released, fixes four vulnerabilities », publié le 2 novembre 2026, avec l’explication de l’attaque, les crédits et la note de rétroportage pour la requête de tirage #1439 : https://blog.hartwork.org/posts/expat-2-10-0-released/ (consulté le 2 novembre 2026)
  • Annonce de publication oss-security, « libexpat 2.10.0 fixes two vulnerabilities », publiée le 2 novembre 2026, listant les quatre CVE, les entrées du journal des modifications et les requêtes de tirage correctives : https://www.openwall.com/lists/oss-security/2026/11/02/6 (consulté le 2 novembre 2026)
  • Page de la version Expat 2.10.0 sur GitHub, publiée le 2 novembre 2026 : https://github.com/libexpat/libexpat/releases/tag/R_2_10_0 (consulté le 2 novembre 2026)
  • Enregistrement CVE-2026-115506, publié le 22 octobre 2026, CVSS v4.0 8.7 High et CVSS v3.1 7.5 High : https://www.cve.org/CVERecord?id=CVE-2026-115506 (consulté le 2 novembre 2026)
  • Enregistrement CVE-2026-113351, publié le 20 octobre 2026, CVSS v4.0 8.2 High, CVSS v3.1 5.9 Medium : https://www.cve.org/CVERecord?id=CVE-2026-113351 (consulté le 2 novembre 2026)
  • Enregistrement CVE-2026-117610, publié le 28 octobre 2026, CVSS v4.0 8.2 High, CVSS v3.1 5.9 Medium : https://www.cve.org/CVERecord?id=CVE-2026-117610 (consulté le 2 novembre 2026)
  • Enregistrement CVE-2026-118370, publié le 28 octobre 2026, CVSS v4.0 8.2 High, CVSS v3.1 5.9 Medium : https://www.cve.org/CVERecord?id=CVE-2026-118370 (consulté le 2 novembre 2026)
  • Requête de tirage corrective #1431 pour CVE-2026-113351, fusionnée le 23 octobre 2026 : https://github.com/libexpat/libexpat/pull/1431 (consulté le 2 novembre 2026)
  • Requête de tirage corrective #1433 pour CVE-2026-115506, la taille d’expansion précalculée, et son complément #1439 pour CDATA, commentaires et instructions de traitement : https://github.com/libexpat/libexpat/pull/1433 (consulté le 2 novembre 2026)
  • Requête de tirage corrective #1444 pour CVE-2026-117610, fusionnée le 29 octobre 2026 : https://github.com/libexpat/libexpat/pull/1444 (consulté le 2 novembre 2026)
  • Requête de tirage corrective #1450 pour CVE-2026-118370, fusionnée le 29 octobre 2026 : https://github.com/libexpat/libexpat/pull/1450 (consulté le 2 novembre 2026)
  • Générateur de charges de Hanno Böck pour les variantes billion laughs, lié depuis le journal des modifications d’Expat : https://github.com/hannob/lololol (consulté le 2 novembre 2026)
  • Page d’accueil du projet Expat, décrivant Expat comme empaqueté partout et listant utilisateurs, liaisons et enrobes : https://libexpat.github.io/ (consulté le 2 novembre 2026)
  • Source vulnérable de expat/lib/xmlparse.c au tag R_2_9_0, pour le code cité : https://github.com/libexpat/libexpat/blob/R_2_9_0/expat/lib/xmlparse.c (consulté le 2 novembre 2026)
  • Suivi de sécurité Debian pour CVE-2026-115506, marquant expat vulnérable et non corrigé dans bookworm, trixie, forky et sid au 2 novembre 2026 : https://security-tracker.debian.org/tracker/CVE-2026-115506 (consulté le 2 novembre 2026)
  • Page de sécurité Ubuntu pour CVE-2026-115506, listant le paquet source expat vulnérable et les paquets utilisant l’expat système : https://ubuntu.com/security/CVE-2026-115506 (consulté le 2 novembre 2026)
  • Flux JSON du catalogue KEV de la CISA, recherché pour les quatre CVE sans correspondance au 2 novembre 2026 : https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (consulté le 2 novembre 2026)