Sécurité avant tout : comment parse XML using Python sans risques ?

Les modules XML de la bibliothèque standard Python ne sont pas sécurisés par défaut contre les données malveillantes. La documentation officielle le dit elle-même, et plusieurs CVE récents confirment que parse XML using Python sans précaution expose un serveur à des attaques par déni de service ou à l’exfiltration de fichiers locaux. Le sujet n’est pas théorique : des failles ont été identifiées et corrigées dans des versions maintenues de CPython au cours des deux dernières années.

CVE récents sur le parsing XML Python : des failles dans le module standard

La vulnérabilité CVE-2026-6879 touche directement xml.etree.ElementTree, le module que la plupart des développeurs Python utilisent sans y réfléchir. Des requêtes XPath contenant des prédicats d’index ([1], [last()], [last()-N]) provoquent un comportement en temps quadratique lorsque le document XML contient un grand nombre d’éléments frères portant le même tag.

Concrètement, un attaquant peut fournir un fichier XML spécialement construit pour saturer le CPU d’un serveur. Le correctif a été intégré à partir de Python 3.15.0rc1 et rétroporté sur les branches 3.10 à 3.14. Toute application qui parse du XML non fiable avec find(), findall() ou iterfind() sur des versions antérieures reste exposée.

Un second vecteur concerne le hash flooding via pyexpat, la bibliothèque C sous-jacente utilisée par xml.parsers.expat et xml.etree.ElementTree. L’attaque exploite les collisions dans les tables de hachage internes du parseur. Un correctif récent dans l’écosystème Yocto/OpenEmbedded a adopté l’API XML_SetHashSalt16Bytes des versions récentes de libexpat, avec une clé secrète de 16 octets. Ce détail montre que le risque est pris au sérieux jusque dans les distributions embarquées.

Ingénieure logiciel travaillant depuis chez elle sur du parsing XML sécurisé avec Python dans un bureau cosy

XXE et Billion Laughs : les attaques classiques du parsing XML en Python

Les deux attaques les plus documentées contre les parseurs XML restent l’injection d’entités externes (XXE) et l’expansion exponentielle d’entités, connue sous le nom de Billion Laughs.

XML External Entity (XXE)

Une attaque XXE exploite la capacité du parseur à résoudre des entités externes définies dans le DTD du document. Un XML malveillant peut référencer un fichier local (/etc/passwd sur Linux, par exemple) ou une URL distante. Si le parseur résout l’entité, le contenu du fichier est injecté dans la réponse.

xml.etree.ElementTree ne résout pas les entités externes par défaut, ce qui limite le risque pour ce module précis. En revanche, lxml avec l’option resolve_entities=True (qui est le défaut dans certaines configurations) ouvre cette surface d’attaque.

Billion Laughs

Cette attaque définit des entités XML imbriquées qui se multiplient de façon exponentielle à l’expansion. Quelques lignes de XML suffisent à consommer plusieurs gigaoctets de mémoire. Le module standard xml.etree.ElementTree y est vulnérable si aucune limite n’est configurée.

En 2024, un CVE a été enregistré contre le dépôt langchain-ai/langchain (notamment l’XMLOutputParser) parce qu’il utilisait directement xml.etree.ElementTree pour parser des sorties XML sans protection contre ce type d’expansion. Ce cas illustre que des projets récents et populaires reproduisent les mêmes erreurs.

defusedxml : la bibliothèque de référence pour parse XML using Python en sécurité

Le module defusedxml a été conçu spécifiquement pour neutraliser les vecteurs d’attaque connus sur le parsing XML. Il fournit des remplacements directs pour les modules standard (ElementTree, minidom, sax, pulldom) avec des protections activées par défaut.

Ce que defusedxml bloque sans configuration supplémentaire :

  • La résolution d’entités externes (XXE), en interdisant les déclarations DTD externes
  • L’expansion exponentielle d’entités (Billion Laughs), en limitant la profondeur et le volume des entités définies
  • Les références à des ressources réseau distantes dans les DTD ou les entités

L’utilisation est simple : remplacer import xml.etree.ElementTree as ET par import defusedxml.ElementTree as ET. Le reste du code ne change pas. C’est un changement d’une ligne qui élimine les vecteurs d’attaque les plus courants.

Les retours terrain divergent sur un point : defusedxml ne couvre pas les failles liées à la performance du parseur, comme le comportement quadratique de CVE-2026-6879. Pour ce type de vulnérabilité, seule la mise à jour de CPython vers une version corrigée protège réellement.

Configuration sécurisée de lxml pour le parsing XML Python

lxml est le parseur le plus utilisé quand les performances ou le support XPath complet sont nécessaires. Sa configuration par défaut n’est pas sécurisée pour du XML provenant de sources non fiables.

Les paramètres à vérifier systématiquement :

  • resolve_entities=False sur le parseur, pour bloquer la résolution d’entités externes
  • no_network=True pour empêcher le parseur d’effectuer des requêtes réseau lors du parsing
  • dtd_validation=False et load_dtd=False pour ignorer les DTD potentiellement malveillantes
  • Limiter la taille du document en amont (au niveau du serveur web ou du framework) avant qu’il n’atteigne le parseur

Un piège fréquent : lxml avec resolve_entities=True expose aux attaques XXE même si le développeur n’utilise pas explicitement de DTD. Le parseur tentera de résoudre toute entité déclarée dans le document, y compris celles pointant vers des ressources locales ou distantes.

Deux développeurs seniors examinant la documentation de sécurité pour le parsing XML Python dans une salle serveurs

Checklist de durcissement pour parser du XML non fiable en Python

Mettre à jour Python ne suffit pas. La sécurité du parsing XML repose sur une combinaison de choix techniques à chaque niveau de la chaîne.

Utiliser defusedxml comme point d’entrée par défaut pour tout XML provenant d’une source externe. Si lxml est nécessaire, désactiver explicitement la résolution d’entités et l’accès réseau. Maintenir CPython à jour sur les branches corrigées (3.10.21+, 3.11.16+, 3.12.14+ selon les bulletins de sécurité) pour bénéficier des correctifs sur les failles de performance comme CVE-2026-6879.

Limiter la taille des documents XML acceptés avant le parsing, au niveau du serveur ou du middleware. Un fichier XML de quelques mégaoctets ne devrait jamais saturer un serveur correctement configuré. Valider le schéma XML attendu (XSD) quand c’est possible, pour rejeter les structures inattendues avant même que le parseur ne les interprète.

Aucune bibliothèque ne protège contre toutes les failles simultanément. La défense en profondeur (parseur sécurisé, version Python à jour, validation de schéma, limites de taille) reste la seule approche fiable pour parse XML using Python sur des données non maîtrisées.