Web Application Security
Chapitre 5 / 9 · 11 min de lecture

XML External Entity (XXE)

Quand un parseur XML trop permissif lit vos fichiers : l'attaque XXE — et comment la neutraliser.

L'attaque par entité externe XML — XML External Entity (XXE) — fait partie de ces failles déroutantes : elle est souvent très simple à déclencher, mais ses conséquences peuvent être dévastatrices. Tout repose sur un détail de configuration d'un composant que l'on regarde rarement : le parseur XML (XML parser) qui transforme un document texte en arbre d'objets exploitable par l'application. Quand ce parseur est laissé dans sa configuration permissive par défaut, un document XML soigneusement rédigé peut lui faire lire des fichiers du serveur, ouvrir des connexions réseau internes, ou s'effondrer sous une bombe d'expansion.

Ce chapitre est délibérément orienté défense. Nous expliquons d'abord, de façon conceptuelle, comment un document XML peut détourner le parseur — juste assez pour comprendre la menace — puis nous consacrons l'essentiel du propos aux contre-mesures. La bonne nouvelle, que Hoffman martèle, est que la parade tient souvent en une seule ligne de configuration. La mauvaise, c'est que cette ligne manque encore dans d'innombrables applications en production.

Note

Cadre éthique : tout ce qui suit s'adresse à la défense de vos propres applications ou à des tests explicitement autorisés. Sonder un parseur XML d'un système tiers sans mandat écrit est illégal. En cas de découverte fortuite, appliquez la divulgation responsable (responsible disclosure).

Pourquoi le XML est concerné : le mécanisme

La spécification XML prévoit une annotation spéciale pour importer des fichiers externes : l'entité externe (external entity). Une entité est une sorte de variable de substitution déclarée dans la définition de type du document — la DTD (Document Type Definition). Une entité interne se contente de remplacer un nom par un texte. Une entité externe, elle, demande au parseur d'aller chercher son contenu à l'extérieur : un fichier local, une URL réseau.

Le problème est là : cette directive est interprétée sur la machine qui évalue le document. Si votre serveur reçoit un document XML d'un utilisateur et le confie à un parseur qui résout les entités externes, l'utilisateur dicte au serveur quels fichiers lire. Un parseur naïf ne voit là aucune anomalie : il suit fidèlement la spécification.

Voici, à titre strictement illustratif, à quoi ressemble une entité externe. L'objectif est de reconnaître la structure pour la bloquer, pas de la réutiliser.

<!-- Document illustratif : déclaration d'une entité externe. -->
<?xml version="1.0"?>
<!DOCTYPE document [
  <!ENTITY ressource SYSTEM "file:///chemin/vers/un/fichier">
]>
<document>&ressource;</document>

Le parseur permissif remplace &ressource; par le contenu du fichier désigné. Si ce contenu se retrouve ensuite dans la réponse renvoyée à l'appelant, le fichier du serveur est exfiltré.

Attention

Hoffman insiste : ces attaques visent souvent des fichiers sensibles du système — fichiers de configuration, identifiants, fichiers d'autres utilisateurs. Un parseur XML mal configuré peut compromettre le serveur entier, bien au-delà de l'application web qui tourne dessus.

Ce n'est pas que « du XML »

On pourrait croire que les points d'entrée acceptant du XML sont rares. C'est une illusion dangereuse. De nombreux formats sont du XML déguisé ou dérivé, et beaucoup de parseurs les acceptent comme entrées : le SVG (images vectorielles), le HTML/DOM, certains PDF (XFDF), le RTF, et les formats de documents bureautiques (qui sont, sous le capot, du XML compressé). Tout endpoint qui ingère l'un de ces formats touche potentiellement un parseur XML.

Les API SOAP sont un cas emblématique : leur protocole repose entièrement sur XML. Les imports de fichiers, les utilitaires de conversion (XML vers image, par exemple) et toute fonctionnalité « envoyez-nous votre document » sont autant de portes d'entrée à examiner.

XXE direct contre XXE indirect

Hoffman distingue deux scénarios, importants à connaître car ils déterminent la donnée volée ressort — et donc comment détecter le problème.

XXE direct

Dans le XXE direct, l'application reçoit directement un objet XML porteur de l'entité externe, le parse, et renvoie un résultat qui inclut le contenu de l'entité. Le livre prend l'exemple d'une banque fictive dotée d'un utilitaire de capture d'écran : le navigateur convertit le DOM de la page en XML, l'envoie à un service qui le transforme en image JPG, et renvoie l'image au support.

Tant que le payload reste un simple DOM converti, rien de dangereux. Mais ce payload est entièrement modifiable par l'utilisateur : rien n'empêche de forger une requête réseau et d'envoyer un XML maison contenant une entité externe. Si le parseur côté serveur ne désactive pas explicitement les entités externes, le contenu du fichier ciblé apparaît dans l'image renvoyée. Le canal de retour est direct : la réponse de l'API contient la donnée.

Piège courant

Le danger ne vient pas du formulaire « légitime » du navigateur, mais du fait que toute entrée venue du client est falsifiable. Ne faites jamais confiance à la forme du payload sous prétexte que votre propre code génère la version normale. Le serveur doit se protéger comme si chaque octet était hostile.

XXE indirect (ou aveugle)

Le XXE indirect est plus sournois. Ici, l'API publique n'accepte pas d'XML : elle prend, disons, un simple paramètre via un endpoint REST/JSON. Mais en coulisses, le serveur convertit ce paramètre en XML pour dialoguer avec un autre système — typiquement un logiciel d'entreprise (CRM, comptabilité, RH) ou une API SOAP héritée — un système qui, lui, attend du XML.

Ce cas est extrêmement courant. Les entreprises font évoluer leurs systèmes par morceaux : des façades JSON/REST modernes finissent par communiquer avec des API XML/SOAP plus anciennes. Ces points de jonction entre logiciels récents et systèmes hérités sont des terrains fertiles pour XXE, et le XML y est souvent invisible de l'extérieur. La donnée volée ne revient alors pas dans la réponse principale : elle peut s'exfiltrer par un canal secondaire (d'où le qualificatif d'« aveugle »).

CritèreXXE directXXE indirect / aveugle
Entrée publiqueObjet XML expliciteParamètre non-XML (JSON, REST)
Présence du XMLVisibleCachée (conversion côté serveur)
Canal de retourRéponse de l'APICanal secondaire / hors bande
DétectionFacileDifficile, demande de l'inférence

Les impacts : une faille « passerelle »

Hoffman qualifie XXE d'attaque « passerelle » (gateway attack). Elle commence souvent comme un accès en lecture seule, mais offre à l'attaquant une plateforme de reconnaissance qui ouvre la voie à des compromissions plus graves. L'impact final peut aller de la simple lecture de données jusqu'à l'exécution de code à distance et la prise de contrôle complète du serveur.

Trois grandes variantes méritent d'être retenues, chacune avec sa parade.

Variante XXEImpactDéfense principale
Lecture de fichier local (file://)Vol de configuration, identifiants, fichiers d'autres utilisateursDésactiver entités externes et DTD
Requête réseau interne (entité vers URL)Falsification de requête côté serveur (SSRF), reconnaissance interneDésactiver entités externes ; cloisonner le réseau
Expansion d'entités (« billion laughs »)Déni de service (DoS) par explosion mémoireDésactiver l'expansion d'entités, limiter la taille

Sur la dernière ligne : l'attaque dite « billion laughs » ne lit aucun fichier. Elle imbrique des entités internes qui se référencent en cascade, de sorte qu'un document minuscule s'expanse jusqu'à saturer la mémoire — un déni de service (denial of service). C'est pourquoi la défense ne se limite pas aux seules entités externes, mais englobe le traitement des entités et des DTD en général.

Comment se défendre : désactiver les entités externes

Voici le cœur du chapitre. La contre-mesure numéro un, quasi suffisante à elle seule, est de désactiver la résolution des entités externes et des DTD dans la configuration de votre parseur. Comment, exactement, dépend du parseur — mais c'est typiquement une à deux lignes de configuration.

À retenir

Le défaut non sûr des vieux parseurs est la source du problème. OWASP signale que les parseurs XML Java sont particulièrement exposés, car beaucoup ont les entités externes activées par défaut. Selon le langage et la bibliothèque, XXE peut être désactivé d'office — ou pas. Ne présumez jamais : lisez la documentation de l'API de votre parseur et vérifiez.

Java : interdire la déclaration de DOCTYPE

L'approche la plus robuste, recommandée par Hoffman, consiste à interdire purement et simplement la déclaration de DTD. Si aucune <!DOCTYPE> n'est tolérée, aucune entité ne peut être déclarée — externe ou récursive — ce qui ferme du même coup la porte à la lecture de fichiers, à la SSRF et au « billion laughs ».

// ❌ AVANT : factory permissive, entités externes résolues.
DocumentBuilderFactory factory =
    DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(fluxUtilisateur);
// ✅ APRÈS : la déclaration de DOCTYPE est interdite.
DocumentBuilderFactory factory =
    DocumentBuilderFactory.newInstance();
factory.setFeature(
    "http://apache.org/xml/features/disallow-doctype-decl",
    true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(fluxUtilisateur);

Si votre application doit accepter des DTD pour une raison métier, repliez-vous sur le verrouillage des seules entités externes et de la résolution réseau, en désactivant external-general-entities et external-parameter-entities. Interdire le DOCTYPE reste néanmoins la défense la plus simple et la plus complète.

Node.js : éviter le XML quand c'est possible, sinon verrouiller

En JavaScript côté serveur, la meilleure défense est souvent de ne pas activer ce dont vous n'avez pas besoin. Beaucoup de bibliothèques de parsing exposent une option explicite pour le traitement des entités et des DTD ; laissez-la désactivée.

// ❌ AVANT : parseur configuré pour résoudre les DTD/entités.
import { parseXml } from "une-lib-xml";

function lireDocument(xmlBrut) {
  // Options permissives : DTD et entités externes autorisées.
  return parseXml(xmlBrut, { dtd: true, externalEntities: true });
}
// ✅ APRÈS : DTD et entités externes explicitement coupées.
import { parseXml } from "une-lib-xml";

function lireDocument(xmlBrut) {
  return parseXml(xmlBrut, {
    dtd: false, // aucune <!DOCTYPE> tolérée
    externalEntities: false, // aucune résolution file:// ou http://
    resolveEntities: false, // pas d'expansion d'entités
  });
}

Les noms d'options varient d'une bibliothèque à l'autre : reportez-vous à la documentation pour identifier les vôtres. Le principe reste constant — aucune DTD, aucune entité externe, pas d'expansion.

Astuce

Adoptez une posture de liste blanche par défaut : partez d'un parseur qui ne résout rien, puis n'activez explicitement que les fonctionnalités strictement nécessaires. Un défaut sûr vous protège même quand un développeur oublie de durcir la configuration.

Préférer un format plus simple : JSON

Selon Hoffman, la question à se poser en amont est : avez-vous vraiment besoin de XML ? Si votre application transporte des données hiérarchiques légères qui se trouvent simplement avoir une forme XML, la ré-architecturer autour de JSON simplifie le code et élimine tout risque XXE, puisque JSON ne connaît ni DTD ni entités externes.

CritèreXMLJSON
Taille du payloadVolumineuseCompacte
Complexité de la specÉlevéeFaible
Facilité d'usageParsing complexeParsing simple (compat. JS)
Validation par schémaOuiNon (nativement)
Rendu façon HTMLAiséDifficile
SécuritéPlus faiblePlus élevée

Les risques de sécurité du XML découlent directement de la puissance de sa spécification : sa capacité à incorporer des fichiers externes et du multimédia. JSON, qui stocke essentiellement des paires clé/valeur (et des tableaux ou objets imbriqués) dans une chaîne, est par nature plus sûr.

Cela dit, JSON n'est pas une solution universelle. Il reste impraticable si votre application parse réellement du XML, du SVG ou des formats dérivés, ou si vous avez besoin de validation par schéma stricte et de métadonnées riches — domaines où XML garde l'avantage. Si votre organisation refuse JSON, des formats comme YAML, BSON ou EDN sont des alternatives possibles, à évaluer avec la même rigueur avant adoption.

Note

Le choix du format est un arbitrage : XML reste pertinent pour les charges destinées à être rendues (HTML-like) ou exigeant une structure rigide validée. Mais dès que XML n'est pas requis, le retirer supprime une classe entière de vulnérabilités.

Valider, restreindre, maintenir à jour

Désactiver les entités externes est nécessaire, mais une défense en profondeur ajoute trois habitudes.

  • Valider et restreindre les entrées. Limitez la taille des documents acceptés (rempart contre l'expansion d'entités), refusez les types de contenu inattendus, et n'acceptez du XML que sur les endpoints qui en ont réellement besoin.
  • Tenir les bibliothèques à jour. Les défauts non sûrs des vieux parseurs sont la cause première de XXE. Les versions récentes corrigent souvent ces défauts par des réglages plus prudents : maintenir vos dépendances à jour fait partie de la défense.
  • Tester systématiquement. Hoffman recommande de toujours éprouver chaque nouvel endpoint XML, car une seule ligne de configuration manquante suffit à ouvrir une brèche béante. Intégrez ce contrôle à votre revue de sécurité.

Attention

Méfiez-vous tout particulièrement du XXE indirect : un endpoint qui ne « parle » pas XML peut malgré tout convertir vos données en XML pour un système hérité. Auditez donc aussi les intégrations internes et les passerelles SOAP, pas seulement les API exposées qui acceptent visiblement du XML.

À retenir

  • XXE abuse d'un parseur XML qui résout les entités externes : un document forgé peut lire des fichiers du serveur, déclencher des requêtes réseau internes (SSRF) ou provoquer un déni de service par expansion d'entités.
  • Tout endpoint qui ingère du XML — ou un dérivé (SVG, HTML/DOM, PDF, RTF, SOAP, documents bureautiques) — est concerné, même quand le XML est caché derrière une conversion côté serveur (XXE indirect).
  • La contre-mesure n°1, quasi suffisante, est de désactiver les entités externes et les DTD dans la configuration du parseur — souvent une à deux lignes, comme disallow-doctype-decl côté Java.
  • Ne présumez jamais des défauts : beaucoup de parseurs (notamment Java) activent les entités externes par défaut ; lisez la documentation et vérifiez la configuration explicitement.
  • Quand le XML n'est pas requis, préférez JSON : un format plus simple qui élimine d'emblée le risque XXE ; gardez XML pour le rendu ou la validation par schéma.
  • Défense en profondeur : validez et limitez la taille des entrées, maintenez les bibliothèques à jour, et testez chaque endpoint XML — une seule ligne manquante peut compromettre le serveur entier.