Situation actuelle : PHP2Shell. Deux failles PHP critiques (CVSS 9.2) permettent d'exécuter du code malveillant sans authentification. Correctifs disponibles depuis le 21 octobre 2026.Ce qu'il faut faire maintenant →

Empêcher les XML External Entities (XXE) en PHP

Comment les développeurs traitent le XML en toute sécurité en PHP : comportement par défaut de libxml, options risquées comme LIBXML_NOENT, protection avec un chargeur d'entités et LIBXML_NO_XXE, et entrées XML cachées typiques.

État 6 min de lectureDéveloppement sécurisé

En bref : vous empêchez XXE en PHP en chargeant le XML de sources tierces sans les options LIBXML_NOENT, LIBXML_DTDLOAD et LIBXML_DTDVALID, en rejetant les documents comportant leur propre DTD et en définissant un chargeur d'entités qui refuse les ressources externes. Depuis libxml 2.9.0, PHP ne remplace plus les entités par défaut ; c'est pourquoi libxml_disable_entity_loader() est obsolète depuis PHP 8.0. À partir de PHP 8.4 avec libxml 2.13, LIBXML_NO_XXE est en outre disponible.

Une vieille faille qui ne disparaît pas

Les XML External Entities, en abrégé XXE, passent pour un sujet réglé aux yeux de nombreux développeurs. Avec les versions modernes de libxml, PHP a désactivé par défaut le chargement des entités externes, et l'ancienne fonction de protection a été marquée comme obsolète avec PHP 8.0. Des failles XXE apparaissent pourtant toujours lors des audits.

La raison tient presque toujours à l'une de ces trois situations : quelqu'un a défini une option pour résoudre un problème d'import et réactivé ainsi le remplacement des entités. Une bibliothèque le fait en interne. Ou l'application tourne sur un système ancien dont le PHP a été compilé avec une libxml très ancienne.

XXE est dangereux parce que l'analyseur XML y recharge des contenus qui ne sont que référencés dans le document : fichiers locaux ou adresses du réseau. Les conséquences possibles sont la lecture de fichiers de configuration, des requêtes vers des systèmes internes comme avec SSRF, et la surcharge du serveur.

Ce qui vaut depuis libxml 2.9 et PHP 8.0

Le manuel PHP décrit la situation ainsi : à partir de libxml 2.9.0, le remplacement des entités est désactivé par défaut. Il n'est donc plus nécessaire de désactiver spécialement les entités externes, sauf si des références d'entités internes doivent être résolues avec certaines options. On en tire ce tableau récapitulatif :

Option Effet Risque avec du XML tiers
LIBXML_NOENT remplace les entités selon le manuel, elle peut faciliter les attaques XXE
LIBXML_DTDLOAD charge le sous-ensemble externe active le chargement des entités externes
LIBXML_DTDVALID valide par rapport à la DTD peut faciliter les attaques XXE
LIBXML_NONET interdit les accès réseau au chargement option de protection complémentaire
LIBXML_NO_XXE désactive les entités externes lors du remplacement protectrice, dès PHP 8.4 et libxml 2.13.0
LIBXML_PARSEHUGE supprime les limites internes de l'analyseur facilite la surcharge, uniquement pour des données de confiance

Le nom trompeur de LIBXML_NOENT est un piège connu : il évoque « no entities », mais produit l'effet inverse.

Pour les développeurs, cela signifie : ce n'est pas le chargement du XML en soi qui est risqué, mais chaque endroit qui modifie délibérément le comportement par défaut. Une recherche de ces constantes dans le code est donc le moyen le plus rapide d'obtenir une première évaluation, avant tout examen approfondi.

Se défendre sûrement contre XXE en PHP : un modèle pour DOMDocument

L'exemple suivant combine plusieurs couches de protection. Il définit un chargeur d'entités qui refuse toute ressource externe, charge sans options risquées et rejette les documents comportant leur propre DTD :

<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // refuser par principe les ressources externes
    libxml_set_external_entity_loader(
        static fn (?string $public, string $system, array $context) => null
    );

    $optionen = LIBXML_NONET;
    if (defined('LIBXML_NO_XXE')) {
        $optionen |= LIBXML_NO_XXE;
    }

    $vorher = libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $ok = $dom->loadXML($xml, $optionen);
    libxml_use_internal_errors($vorher);

    if (!$ok) {
        throw new InvalidArgumentException('XML ungültig.');
    }
    if ($dom->doctype !== null) {
        throw new InvalidArgumentException('Dokumente mit DTD werden nicht angenommen.');
    }
    return $dom;
}

Selon le manuel PHP, une valeur de retour null dans le chargeur bloque le chargement de la ressource externe. L'OWASP souligne qu'il ne suffit pas de transmettre directement null à libxml_set_external_entity_loader() ; il faut une fonction qui renvoie null. Les mêmes options valent pour SimpleXML et XMLReader. Dès que possible, l'OWASP recommande de rejeter entièrement les DTD.

Astuce

Si vous pouvez déterminer vous-même le format de données, JSON est généralement le choix le plus simple pour l'échange entre vos propres systèmes. Pour des formats tiers comme SAML ou les fichiers Office, XML reste inévitable.

Repérer les entrées XML cachées

De nombreuses applications traitent plus de XML que leurs développeurs ne le pensent. Parcourez cette liste :

  1. Interfaces : clients et serveurs SOAP, XML-RPC, services de paiement et d'expédition avec réponses XML.
  2. Authentification : l'authentification unique avec SAML repose sur des documents XML qui viennent de l'extérieur.
  3. Imports : données de produits, flux, sitemaps, formats de catalogue.
  4. Téléversements : les images SVG sont du XML ; les formats Office comme DOCX et XLSX sont constitués en interne de fichiers XML.
  5. Bibliothèques : paquets Composer pour documents, tableurs ou SAML, qui utilisent libxml en interne.

Dans le code, deux recherches aident :

grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"

Les occurrences dans vendor/ ne sont pas une raison de paniquer, mais l'occasion de vérifier la version et les avis de sécurité de la bibliothèque.

Pour les téléversements SVG, XXE n'est qu'un risque parmi d'autres, car les fichiers SVG peuvent aussi contenir des scripts. Si votre application n'a pas absolument besoin du format, ne l'autorisez pas du tout pour les téléversements. Sinon, ne traitez le SVG qu'avec le modèle sûr ci-dessus et diffusez les fichiers de façon que le navigateur n'exécute pas les scripts qu'ils contiennent.

Soyez aussi attentif aux messages d'erreur. Si une application renvoie sans filtre aux utilisateurs les messages de libxml, ils peuvent contenir des parties du contenu traité. Les erreurs relèvent du journal, l'utilisateur reçoit un message général ; dans l'exemple ci-dessus, libxml_use_internal_errors(true) veille à ce que les avertissements n'atterrissent pas dans la sortie. Limitez en outre la taille des documents XML reçus avant même que l'analyseur ne les voie.

Systèmes anciens : quand la protection par défaut manque

La protection par le comportement par défaut dépend de la version de libxml avec laquelle PHP a été compilé. La version utilisée est indiquée par php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' en ligne de commande ou par la sortie de phpinfo() dans un environnement protégé. Vérifiez la valeur pour chaque version de PHP du serveur, car la CLI et le serveur web peuvent utiliser des builds différents. Les images de conteneurs apportent leur propre libxml ; vérifiez-y dans le conteneur en fonctionnement, pas sur l'hôte.

Les systèmes qui tournent de toute façon sur d'anciennes versions de PHP sont particulièrement concernés. Pour PHP 8.1 et antérieures, le projet PHP ne publie plus de mises à jour officielles après PHP2Shell ; ce que cela signifie est décrit dans PHP 8.1 et antérieures : ce qui s'applique désormais. Un système ancien avec une libxml obsolète et des failles d'interpréteur ouvertes ne devrait pas seulement être sécurisé dans le code, mais migré. Les risques voisins liés au traitement de données tierces sont décrits dans Désérialisation non sécurisée en PHP, les appels vers des cibles internes dans Empêcher le Server-Side Request Forgery en PHP.

Comment PHP SOS aide-t-il concrètement ?

  • Nous trouvons tous les endroits où votre application et ses bibliothèques traitent du XML provenant de sources tierces.
  • Nous supprimons les options d'analyseur risquées et mettons en place une fonction de chargement centrale et sécurisée.
  • Nous vérifions les versions de libxml et de PHP sur tous les serveurs et planifions la migration pour les systèmes anciens.
  • Nous testons les modifications avec des documents de contrôle inoffensifs et documentons le résultat.

La vérification de logiciels internes est décrite dans la prestation audit de sécurité et test d'intrusion.

Questions fréquentes

Les applications PHP sont-elles automatiquement protégées contre XXE depuis PHP 8 ?

Dans le cas par défaut, largement, car libxml ne remplace plus les entités à partir de la version 2.9.0. La protection disparaît toutefois dès que le code définit des options comme LIBXML_NOENT, LIBXML_DTDLOAD ou LIBXML_DTDVALID, ou que PHP est compilé avec une version de libxml très ancienne.

Dois-je encore appeler libxml_disable_entity_loader ?

Non. La fonction est obsolète depuis PHP 8.0, car le chargement des entités externes est désactivé par défaut. Pour une protection supplémentaire, le manuel PHP recommande libxml_set_external_entity_loader et, à partir de PHP 8.4 avec une libxml adaptée, la constante LIBXML_NO_XXE.

Où mon application traite-t-elle du XML, au juste ?

Souvent à des endroits inattendus : interfaces SOAP, authentification unique avec SAML, flux RSS, sitemaps, imports de produits, téléversements SVG et fichiers Office, qui sont constitués en interne de XML. Des bibliothèques peuvent aussi traiter du XML sans que cela soit visible dans votre propre code.

Qu'est-ce que l'attaque Billion Laughs ?

Un document avec des entités imbriquées, qui grossit énormément lors de la résolution et consomme mémoire et temps de calcul. Elle appartient à la même famille que XXE. La désactivation du remplacement des entités et le rejet des documents comportant leur propre DTD offrent une protection.

Sources

Contact immédiat

Chaque heure compte. Écrivez-nous maintenant.

Décrivez brièvement votre système, votre version de PHP et ce que vous avez observé. Nous vous répondons, faisons le point et travaillons à distance jusqu'à la clôture et la documentation de l'incident.

Aide immédiate