Rappel théorique
Une faille XXE (XML External Entity) survient lorsqu'une application analyse un document XML fourni par l'utilisateur avec un parseur configuré pour résoudre les entités externes et le DTD (Document Type Definition). Un attaquant peut alors déclarer ses propres entités pour faire lire des fichiers locaux, effectuer des requêtes réseau internes (SSRF) ou provoquer un déni de service.
Exemple de payload
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///var/www/html/OWASP/A04-XXE/secret.txt">
]>
<produit>
<nom>&xxe;</nom>
</produit>
Si le parseur résout l'entité &xxe;, le contenu du fichier
secret.txt est inséré dans le document XML puis renvoyé à l'utilisateur,
alors que rien dans la donnée XML ne devrait permettre de lire un fichier du serveur.
Code vulnérable (PHP / DOMDocument)
$dom = new DOMDocument();
$dom->loadXML($xmlEnvoyeParUtilisateur, LIBXML_NOENT | LIBXML_DTDLOAD);
Les options LIBXML_NOENT (substitue les entités par leur valeur) et
LIBXML_DTDLOAD (charge le DTD externe déclaré) sont précisément ce qui
permet l'attaque XXE ci-dessus.
Conséquences
- Divulgation de fichiers locaux (configuration, code source, secrets).
- SSRF : l'entité externe peut pointer vers une URL interne
(
http://...) - voir aussi le TP A10. - Déni de service : entités imbriquées qui s'expansent exponentiellement ("billion laughs").
Mesures de protection
- Ne jamais utiliser
LIBXML_NOENT/LIBXML_DTDLOADsur du XML non fiable. - Depuis PHP 8, le chargement des entités externes est désactivé par défaut dans libxml - le code redevient sûr sans flags supplémentaires.
- Valider le XML contre un schéma (XSD) strict.
- Préférer un format moins risqué (JSON) lorsque c'est possible.
Démo vulnérable
Un formulaire permet d'envoyer un document XML, analysé avec
LIBXML_NOENT | LIBXML_DTDLOAD : une entité externe permet de lire
secret.txt sur le serveur.
Démo sécurisée
Même formulaire, mais analysé sans les options dangereuses (comportement par défaut de PHP 8) : l'entité externe n'est pas résolue et le contenu du fichier n'apparaît jamais dans la réponse.