defusedxml – Protection contre les bombes XML et autres exploits
« Ce n’est que du XML, que pourrait-il se passer de mal ? »
Christian Heimes
Résumé
Les conséquences d’une attaque sur une bibliothèque XML vulnérable peuvent être considérables. Avec seulement quelques centaines de octets de données XML, un attaquant peut monopoliser plusieurs gigaoctets de mémoire en quelques secondes. De plus, un attaquant peut maintenir les processeurs occupés pendant une période prolongée avec une requête de taille petite à moyenne. Dans certaines situations, il est même possible d’accéder à des fichiers locaux sur votre serveur, de contourner un pare-feu ou d’exploiter des services pour rediriger des attaques vers des tiers.
Ces attaques exploitent des fonctionnalités moins courantes de XML et de ses analyseurs. La plupart des développeurs ne sont pas familiers avec des éléments tels que les instructions de traitement et les expansions d’entités, héritées de SGML. Au mieux, ils connaissent à travers leur expérience avec HTML, mais ignorent qu'une définition de type de document (DTD) peut générer une requête HTTP ou charger un fichier depuis le système de fichiers.
Ces problèmes ne sont pas nouveaux. Le cas des « Billion Laughs » a été signalé pour la première fois en 2003. Pourtant, certaines bibliothèques XML et applications demeurent vulnérables, et même les utilisateurs intensifs de XML sont souvent surpris par ces fonctionnalités. Il est difficile de désigner un responsable dans cette situation. Il serait trop simpliste de blâmer uniquement les analyseurs XML et les bibliothèques pour leurs paramètres par défaut non sécurisés. Après tout, ils respectent correctement les spécifications XML. Les développeurs d’applications ne doivent pas supposer qu’une bibliothèque est toujours configurée pour la sécurité et pour gérer des données potentiellement nuisibles par défaut.
Vecteurs d’attaque
Billion Laughs / Expansion exponentielle d’entités
L’attaque Billion Laughs, également connue sous le nom d’expansion exponentielle d’entités, utilise plusieurs niveaux d’entités imbriquées. L’exemple original utilise 9 niveaux avec 10 expansions à chaque niveau pour transformer la chaîne lol
en une chaîne de 3 * 10 9 octets, d’où le nom « billion laughs ». La chaîne résultante occupe 3 Go (2,79 GiB) de mémoire ; les chaînes intermédiaires nécessitent également de la mémoire supplémentaire. Comme la plupart des analyseurs ne mettent pas en cache l’étape intermédiaire pour chaque expansion, cela se répète encore et encore, augmentant ainsi la charge CPU.
Un document XML de quelques centaines d’octets peut perturber tous les services d’une machine en quelques secondes.
Exemple de XML :
]>
&d;
Expansion d’entités à explosion quadratique
Une attaque par explosion quadratique est similaire à l’attaque Billion Laughs ; elle exploite également l’expansion d’entités. Au lieu d’entités imbriquées, elle répète une grande entité contenant plusieurs milliers de caractères sur plusieurs niveaux, ce qui peut également entraîner une surcharge significative des ressources système.
texte« `markdown
Support XPath (6) | Non | Non | Non | Non | Non |
Support XSLT (6) | Non | Non | Non | Non | Non |
Support XInclude (6) | Non | Oui (5) | Non | Non | Non |
Bibliothèque C | expat | expat | expat | expat | expat |
Vulnérabilités et caractéristiques
- Le parseur expat>=2.4.0 offre une protection contre les attaques de type « billion laughs » (CVE-2013-0340). Ce parseur est configuré avec des valeurs par défaut raisonnables pour
XML_SetBillionLaughsAttackProtectionMaximumAmplification
etXML_SetBillionLaughsAttackProtectionActivationThreshold
. - Les versions de Python>=3.6.8, >=3.7.1, et >=3.8 ne récupèrent plus les ressources locales et distantes via urllib, voir bpo-17239.
- Le module xml.etree ne développe pas les entités et génère une ParserError lorsqu’une entité est rencontrée.
- Le minidom ne développe pas les entités et retourne simplement l’entité non développée telle quelle.
- La bibliothèque offre un support (limité) pour XInclude mais nécessite une étape supplémentaire pour traiter l’inclusion.
- Certaines de ces fonctionnalités peuvent introduire des failles exploitables, voir Autres éléments à considérer.
Configurations dans la bibliothèque standard
Fonctionnalités de xml.sax.handler
feature_external_ges (http://xml.org/sax/features/external-general-entities)
désactive l’expansion des entités externes.
feature_external_pes (http://xml.org/sax/features/external-parameter-entities)
cette option est ignorée et ne modifie aucune fonctionnalité.
Options de DOM xml.dom.xmlbuilder
external_parameter_entities
ignoré
external_general_entities
ignoré
external_dtd_subset
ignoré
entities
incertain
defusedxml
Le paquet defusedxml (disponible sur PyPI) propose plusieurs solutions de contournement et correctifs spécifiques à Python pour les vulnérabilités de déni de service et autres failles dans les bibliothèques XML de Python. Pour bénéficier de cette protection, il suffit d’importer et d’utiliser les fonctions/classes appropriées du module defusedxml au lieu du module d’origine. Le module defusedxml.xmlrpc est mis en œuvre comme un patch.
Au lieu de :
>>> from xml.etree.ElementTree import parse
>>> et=parse(xmlfile)
modifiez le code pour :
>>> from defusedxml.ElementTree import parse
>>> et=parse(xmlfile)
Remarque : Les modules defusedxml ne sont pas des remplacements directs de leurs homologues de la bibliothèque standard. Ils ne fournissent que des fonctions et classes liées à l’analyse et au chargement de XML. Pour toutes les autres fonctionnalités, utilisez les classes, fonctions et constantes des modules de la bibliothèque standard. Par exemple :
>>> from defusedxml import ElementTree as DET
>>> from xml.etree.ElementTree import ET
>>> root=DET.fromstring(" ")
>>> root.append(ET.Element("item"))
>>> ET.tostring(root)
b' '
De plus, le paquet propose une fonction non testée pour patcher tous les modules de la bibliothèque standard avec defusedxml.defuse_stdlib()
.
Avertissement : defuse_stdlib()
doit être évité. Cela peut perturber les paquets tiers ou provoquer des effets secondaires inattendus. Il est préférable d’utiliser explicitement les fonctionnalités d’analyse de defusedxml.
Toutes les fonctions et classes de parseur acceptent trois arguments supplémentaires. Elles retournent soit les mêmes objets que les fonctions d’origine, soit des sous-classes compatibles.
forbid_dtd (par défaut : Faux)
interdit le XML avec une instruction de traitement
et génère une exception DTDForbidden lorsqu’une instruction de traitement DTD est trouvée.
forbid_entities (par défaut : Vrai)
interdit le XML avec des déclarations à l’intérieur de la DTD et génère une exception EntitiesForbidden lorsqu’une entité est déclarée.
forbid_external (par défaut : Vrai)
interdit tout accès aux ressources distantes ou locales dans les entités externes ou DTD et génère une exception ExternalReferenceForbidden lorsqu’une DTD ou une entité fait référence à une ressource externe.
defusedxml (paquet)
DefusedXmlException, DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden, NotSupportedError
defuse_stdlib() (expérimental)
defusedxml.cElementTree
« `AVIS defusedxml.cElementTree
est obsolète et sera supprimé dans une future version. Importez plutôt depuis defusedxml.ElementTree
.
parse(), iterparse(), fromstring(), XMLParser
defusedxml.ElementTree
parse(), iterparse(), fromstring(), XMLParser
defusedxml.expatreader
create_parser(), DefusedExpatParser
defusedxml.sax
parse(), parseString(), make_parser()
defusedxml.expatbuilder
parse(), parseString(), DefusedExpatBuilder, DefusedExpatBuilderNS
defusedxml.minidom
parse(), parseString()
defusedxml.pulldom
parse(), parseString()
defusedxml.xmlrpc
La correction est mise en œuvre sous forme de patch temporaire pour le paquet xmlrpc de la bibliothèque standard (3.x) ou le module xmlrpclib (2.x). La fonction monkey_patch() active les corrections, tandis que unmonkey_patch() les désactive et remet le code dans son état précédent.
Ce patch temporaire protège contre les attaques liées à XML ainsi que contre les bombes de décompression et les requêtes ou réponses excessivement volumineuses. La limite par défaut est de 30 Mo pour les requêtes, les réponses et la décompression gzip. Vous pouvez modifier cette valeur par défaut en changeant la variable de module MAX_DATA. Une valeur de -1 désactive la limite.
defusedxml.lxml
OBSOLETE Ce module est obsolète et sera supprimé dans une future version.
lxml est sécurisé contre la plupart des scénarios d’attaque. lxml utilise libxml2
pour l’analyse XML. La bibliothèque dispose de protections intégrées contre les attaques de type billion laughs et de blowup quadratique. Le parseur permet un nombre limité d’expansions d’entités, puis échoue. lxml désactive également l’accès réseau par défaut. libxml2 lxml FAQ fournit des recommandations supplémentaires pour une analyse sécurisée, par exemple des contre-mesures contre les bombes de compression.
Le parseur par défaut résout les entités et protège contre les arbres énormes et les entités profondément imbriquées. Pour désactiver l’expansion des entités, utilisez un objet parseur personnalisé :
from lxml import etree
parser=etree.XMLParser(resolve_entities=False)
root=etree.fromstring(" ", parser=parser)
Le module sert d’exemple de la manière dont vous pourriez protéger le code utilisant lxml.etree. Il implémente une classe Element personnalisée qui filtre les instances d’entités, une fabrique de parseurs personnalisée et un stockage local par thread pour les instances de parseurs. Il dispose également d’une fonction check_docinfo() qui inspecte un arbre pour des DTD internes ou externes et des déclarations d’entités. Pour vérifier les entités, lxml> 3.0 est requis.
parse(), fromstring() RestrictedElement, GlobalParserTLS, getDefaultParser(), check_docinfo()
defusedexpat
Le paquet defusedexpat (defusedexpat sur PyPI) n’est plus supporté. Le parseur expat 2.4.0 et plus récent inclut une protection contre les bombes XML de type billion laughs.
Comment éviter les vulnérabilités XML
Mettez à jour vers Python 3.6.8, 3.7.1 ou une version plus récente. Les parseurs SAX et DOM ne chargent pas les entités externes à partir de fichiers ou de ressources réseau.
Mettez à jour vers expat 2.4.0 ou une version plus récente. Il inclut une protection contre les bombes de type billion laughs avec des limites par défaut raisonnables pour atténuer les attaques de type billion laughs et blowup quadratique.
Les binaires officiels de python.org utilisent libexpat 2.4.0 depuis les versions 3.7.12, 3.8.12, 3.9.7 et 3.10.0 (août 2021). Les fournisseurs tiers peuvent utiliser des versions plus anciennes ou plus récentes d’expat. pyexpat.version_info
contient la version actuelle d’exécution de libexpat. Les fournisseurs peuvent avoir intégré des corrections dans des versions plus anciennes sans changer le numéro de version.
Exemple :
import sys
import pyexpat
has_mitigations=(
sys.version_info>=(3, 7, 1) and
pyexpat.version_info>=(2, 4, 0)
)
Meilleures pratiques
- Ne pas autoriser les DTD
- Ne pas étendre les entités
- Ne pas résoudre les externes
- Limiter la profondeur d’analyse
- Limiter la taille totale des entrées
- Limiter le temps d’analyse
- Préférer un parseur de type SAX ou itératif pour des données potentiellement volumineuses
- Valider et correctement citer les arguments pour les transformations XSL et les requêtes XPath
- Ne pas utiliser d’expressions XPath provenant de sources non fiables
- Ne pas appliquer de transformations XSL provenant de sources non fiables
(basé sur les recommandations de Brad Hill sur la sécurité XML)
Autres éléments à considérer
Les XML, les parseurs XML et les bibliothèques de traitement possèdent de nombreuses fonctionnalités et problèmes potentiels qui pourraient mener à des vulnérabilités DoS ou à des exploits de sécurité dans les applications. J’ai compilé une liste incomplète de problèmes théoriques nécessitant des recherches et une attention plus approfondie. Cette liste est délibérément pessimiste et un peu paranoïaque, incluant des éléments qui pourraient mal tourner dans des circonstances imprévisibles.
attaque par explosion d’attributs / collision de hachage
Les parseurs XML peuvent utiliser un algorithme avec un temps d’exécution quadratique O(n 2) pour gérer les attributs et les espaces de noms. S’ils utilisent des tables de hachage (dictionnaires) pour stocker les attributs et les espaces de noms, l’implémentation peut être vulnérable aux attaques par collision de hachage, réduisant ainsi les performances à O(n 2) à nouveau. Dans tous les cas, un attaquant peut provoquer une attaque par déni de service avec un document XML contenant des milliers d’attributs dans un seul nœud.
Les Risques Associés aux Bombes de Décompression
Les bombes de décompression, également connues sous le nom de bombes ZIP, représentent un problème pour toutes les bibliothèques XML capables d’analyser des flux XML compressés, tels que les flux HTTP gzippés ou les fichiers LZMA. Pour un attaquant, cela peut réduire le volume de données transmises de manière exponentielle. Par exemple, Gzip peut compresser 1 Go de zéros à environ 1 Mo, tandis que LZMA est encore plus efficace.
$ dd if=/dev/zero bs=1M count=1024 | gzip > zeros.gz
$ dd if=/dev/zero bs=1M count=1024 | lzma -z > zeros.xy
$ ls -sh zeros.*
1020K zeros.gz
148K zeros.xy
Aucune des bibliothèques XML standard de Python ne décompresse les flux, à l’exception de xmlrpclib
, qui est vulnérable aux bombes de décompression.
La bibliothèque lxml peut charger et traiter des données compressées via libxml2 de manière transparente. Libxml2 gère efficacement de très gros blocs de données compressées sans consommer trop de mémoire. Cependant, elle ne protège pas les applications contre les bombes de décompression. Une approche soigneusement écrite utilisant SAX ou une méthode similaire peut être sécurisée.
Instructions de Traitement
Les instructions de traitement (PI) telles que :
peuvent poser des menaces supplémentaires lors du traitement XML. Cela dépend de la manière dont un processeur gère ces instructions. Les problèmes d’accès aux URL, qu’il s’agisse de fichiers locaux ou de ressources réseau, s’appliquent également aux instructions de traitement.
Fonctionnalités DTD et Sécurité
Les DTD (Document Type Definition) possèdent plusieurs fonctionnalités, mais je n’ai pas encore exploré comment celles-ci pourraient constituer une menace pour la sécurité.
Vulnérabilités XPath
Les déclarations XPath peuvent introduire des vulnérabilités de type déni de service (DoS). Il est crucial de ne jamais exécuter des requêtes provenant de sources non fiables. Un attaquant pourrait également créer un document XML qui rend certaines requêtes XPath coûteuses ou gourmandes en ressources.
Attaques par Injection XPath
Les attaques par injection XPath fonctionnent de manière similaire aux attaques par injection SQL. Les arguments des requêtes XPath doivent être correctement cités et validés, surtout s’ils proviennent de l’utilisateur. Il est conseillé de consulter des ressources sur les dangers des injections XPath pour en comprendre les implications.
La bibliothèque standard de Python ne prend pas en charge XPath. En revanche, lxml permet des requêtes XPath paramétrées qui effectuent une citation appropriée. Il suffit d’utiliser correctement sa méthode xpath() :
# À ÉVITER
tree.xpath("/tag[@id='%s']" % value)
# À FAIRE
tree.xpath("/tag[@id=$tagid]", tagid=name)
XInclude et Sécurité
XInclude est une méthode permettant de charger et d’inclure des fichiers externes :
Cette fonctionnalité doit être désactivée lors du traitement de fichiers XML provenant de sources non fiables. Certaines bibliothèques XML de Python et libxml2 prennent en charge XInclude, mais n’offrent pas d’option pour limiter l’inclusion à des répertoires autorisés.
Localisation de XMLSchema
Un parseur XML validant peut télécharger des fichiers de schéma à partir des informations contenues dans un attribut xsi:schemaLocation
.
Transformation XSL
Il est important de garder à l’esprit que XSLT est un langage complet de Turing. Ne traitez jamais de code XSLT provenant de sources inconnues ou non fiables ! Les processeurs XSLT peuvent vous permettre d’interagir avec des ressources externes de manière imprévisible. Certains processeurs prennent même en charge des extensions qui permettent un accès en lecture/écriture au système de fichiers, ainsi qu’à des objets JRE ou à des scripts avec Jython.
Un exemple d’attaque sur la sécurité XML pour Xalan-J :
Vulnérabilités Associées aux CVEs
CVE-2013-1664
L’expansion d’entités non restreinte entraîne des vulnérabilités de déni de service dans les bibliothèques XML de Python (bombe XML).
CVE-2013-1665
L’expansion d’entités externes dans les bibliothèques XML de Python peut causer des failles de sécurité et des vulnérabilités de déni de service.
Langages et Frameworks Alternatifs
D’autres langages de programmation et frameworks présentent également des vulnérabilités. Par exemple, plusieurs d’entre eux sont touchés par le fait que libxml2, jusqu’à la version 2.9.0, ne dispose d’aucune protection contre les attaques d’explosion quadratique. De plus, beaucoup d’entre eux ont des paramètres par défaut potentiellement dangereux pour l’expansion d’entités et les entités externes.
Perl
Le module XML::Simple de Perl est vulnérable à l’expansion d’entités quadratiques ainsi qu’à l’expansion d’entités externes (locales et distantes).
Ruby
Le parseur de documents REXML de Ruby est sensible aux attaques d’expansion d’entités (quadratiques et exponentielles), mais il ne procède pas à l’expansion d’entités externes par défaut. Pour contrer l’expansion d’entités, il est nécessaire de désactiver cette fonctionnalité :
REXML::Document.entity_expansion_limit=0
Les bibliothèques libxml-ruby et hpricot n’effectuent pas d’expansion d’entités dans leur configuration par défaut.
PHP
L’API SimpleXML de PHP est vulnérable à l’expansion d’entités quadratiques et charge des entités à partir de ressources locales et distantes. L’option LIBXML_NONET
désactive l’accès réseau, mais permet toujours l’accès aux fichiers locaux. L’option LIBXML_NOENT
semble ne pas avoir d’effet sur l’expansion d’entités dans PHP 5.4.6.
C# / .NET / Mono
Les informations sur les attaques DoS XML et les défenses (MSDN) indiquent que .NET est vulnérable avec ses paramètres par défaut. L’article fournit des extraits de code pour créer un lecteur XML sécurisé :
XmlReaderSettings settings=new XmlReaderSettings();
settings.ProhibitDtd=false;
settings.MaxCharactersFromEntities=1024;
settings.XmlResolver=null;
XmlReader reader=XmlReader.Create(stream, settings);
Java
Non testé. La documentation de Xerces et son Xerces SecurityManager suggèrent que Xerces est également vulnérable aux attaques de type billion laughs avec ses paramètres par défaut. Il effectue également la résolution d’entités lorsqu’un org.xml.sax.EntityResolver
est configuré. Les spécialistes de Java recommandent d’utiliser une usine de constructeurs personnalisée :
DocumentBuilderFactory builderFactory=DocumentBuilderFactory.newInstance();
builderFactory.setXIncludeAware(False);
builderFactory.setExpandEntityReferences(False);
builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, True);
# soit
builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", True);
# ou si vous avez besoin de DTDs
builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", False);
builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", False);
builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", False);
builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", False);
À Faire
- DOM : Utiliser les options xml.dom.xmlbuilder pour la gestion des entités.
- SAX : Prendre en compte feature_external_ges et feature_external_pes.
- Tester le patching expérimental des modules de la bibliothèque standard.
- Améliorer la documentation.
Licence
Copyright (c) 2013-2023 par Christian Heimes.
Licencié à PSF sous un accord de contributeur.
Voir https://www.python.org/psf/license pour les détails de la licence.
Remerciements
Brett Cannon (développeur principal Python)
révision et nettoyage de code.
Antoine Pitrou (développeur principal Python)
révision de code.
Aaron Patterson, Ben Murphy et Michael Koziarski (communauté Ruby)
Un grand merci à Aaron, Ben et Michael de la communauté Ruby pour leur rapport et leur assistance.
Thierry Carrez (OpenStack)
Un grand merci à Thierry pour son rapport à l’équipe de réponse aux incidents de sécurité Python au nom de l’équipe de sécurité OpenStack.
Carl Meyer (Django)
Un grand merci à Carl pour son rapport à PSRT au nom de l’équipe de sécurité Django.
Daniel Veillard (libxml2)
Un grand merci à Daniel pour son aide.
Évolution de defusedxml : Mises à jour et améliorations
Le projet defusedxml a connu plusieurs mises à jour significatives, visant à renforcer la sécurité et la compatibilité avec les versions récentes de Python. Ces améliorations sont essentielles pour garantir une manipulation sécurisée des fichiers XML, un domaine souvent vulnérable aux attaques.
Dernières versions et changements notables
Version 0.8.0
Date de sortie : 2023
- Correction des tests sans lxml.
- Tests effectués sur 3.13-dev et PyPy 3.9.
Version 0.8.0rc2
Date de sortie : 29 septembre 2023
- Suppression des avertissements de dépréciation dans defuse_stdlib.
- Mise à jour des informations de sécurité concernant lxml.
Version 0.8.0rc1
Date de sortie : 26 septembre 2023
- Fin du support pour Python 2.7, 3.4 et 3.5.
- Tests effectués sur 3.10, 3.11 et 3.12.
- Ajout de
defusedxml.ElementTree.fromstringlist()
. - Mise à jour du tableau des vulnérabilités et fonctionnalités dans le README.
- À venir : suppression Le module
defusedxml.lxml
n’est plus maintenu depuis 2019 et sera retiré dans la prochaine version. - À venir : suppression Le module
defusedxml.cElementTree
sera également retiré. Il est recommandé d’utiliserdefusedxml.ElementTree
à la place.
Historique des versions précédentes
Version 0.7.1
Date de sortie : 8 mars 2021
- Correction de la régression
defusedxml.ElementTree.ParseError
(#63) ; l’exceptionParseError
est à nouveau le même objet de classe quexml.etree.ElementTree.ParseError
.
Version 0.7.0
Date de sortie : 4 mars 2021
- Aucun changement.
Version 0.6.0
Date de sortie : 17 avril 2019
- Augmentation de la couverture des tests.
- Ajout de badges au README.
Références et ressources supplémentaires
- XML DoS et défenses (MSDN)
- Billion Laughs sur Wikipedia
- ZIP bomb sur Wikipedia
- Configurer les parseurs SAX pour un traitement sécurisé
- Tests pour l’injection XML
Ces mises à jour et références soulignent l’importance de maintenir des outils de traitement XML sécurisés et à jour, en réponse aux menaces croissantes dans le domaine de la cybersécurité.
Évolutions de defusedxml
Version 0.3 de defusedxml
Date de publication : 19 février 2013
- Amélioration de la documentation
Version 0.2 de defusedxml
Date de publication : 15 février 2013
- Changement de nom de ExternalEntitiesForbidden en ExternalReferenceForbidden
- Modification de defusedxml.lxml.check_dtd() en check_docinfo()
- Uniformisation des noms d’arguments dans les rappels
- Ajout d’arguments et de représentations formatées aux exceptions
- Inclusion de l’argument forbid_external dans toutes les fonctions et classes
- Augmentation du nombre de tests
- Documentation abondante
- Ajout de code d’exemple pour d’autres langages (Ruby, Perl, PHP) et parseurs (Genshi)
- Protection contre les attaques XML et gzip dans xmlrpclib
Version 0.1 de defusedxml
Date de publication : 8 février 2013
- Lancement initial et interne pour la révision PSRT