libxml_set_external_entity_loader()
Découvrez la fonction libxml_set_external_entity_loader() en PHP pour contrôler le chargement des entités externes XML et se protéger des attaques XXE.
La fonction libxml_set_external_entity_loader() enregistre un callback personnalisé que les parseurs PHP basés sur libxml (DOMDocument, SimpleXML, XMLReader) appellent chaque fois qu'un document XML tente de charger une entité externe — un fichier, une URL ou une DTD référencée depuis la balise. En contrôlant ce callback, vous décidez quelles ressources externes peuvent être chargées et lesquelles sont bloquées, ce qui constitue la méthode standard et pérenne pour se défendre contre les attaques XML External Entity (XXE). Cette page explique ce que sont les entités externes, pourquoi elles sont dangereuses, la signature de la fonction sous PHP 8 et comment écrire un chargeur sécurisé avec des exemples fonctionnels.
Qu'est-ce qu'une entité externe (et pourquoi XXE est dangereux) ?
Une entité externe est un espace réservé déclaré dans une définition de type de document (DTD) qui se résout vers du contenu provenant de l'extérieur du document XML. Par exemple :
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>Lorsqu'un parseur développe &secret;, il lit /etc/passwd et injecte le contenu du fichier dans le document. Un attaquant qui contrôle l'entrée XML peut ainsi lire des fichiers locaux, accéder à des points de terminaison réseau internes (SSRF) ou déclencher un déni de service via l'expansion « billion laughs ». Bloquer — ou mettre sur liste blanche de façon stricte — le chargement des entités externes neutralise cette menace.
Qu'est-ce que la fonction libxml_set_external_entity_loader() ?
libxml_set_external_entity_loader() est une fonction PHP intégrée qui enregistre un callback global unique. À partir du moment où il est défini, chaque requête d'entité externe provenant de n'importe quel parseur libxml passe d'abord par votre callback. Retourner null depuis le callback indique à libxml d'ignorer l'entité ; retourner le contenu de l'entité (sous forme de chaîne ou de ressource ouverte) permet son chargement. Elle a été introduite dans PHP 5.1.0 et reste l'approche recommandée car l'ancienne fonction libxml_disable_entity_loader() est dépréciée dans PHP 8.0 et est largement inutile dans les versions modernes de PHP (les entités externes ne sont pas chargées par défaut depuis libxml 2.9 / PHP 8.0).
Syntaxe
libxml_set_external_entity_loader(?callable $resolver_function): voidPasser null supprime un chargeur précédemment enregistré et restaure le comportement par défaut.
Paramètres
| Paramètre | Description |
|---|---|
resolver_function | Un callable, ou null pour réinitialiser. Le callback reçoit trois arguments et doit retourner le contenu de l'entité (une string), une resource ouverte, ou null pour bloquer le chargement. |
La signature du callback pour PHP 8.0+ est :
function (?string $public_id, ?string $system_id, array $context): string|resource|null$public_id— l'identifiant public de l'entité (souventnull).$system_id— l'URI/chemin vers lequel pointe l'entité (ex.file:///etc/passwd).$context— un tableau avec des clés telles quedirectory,intSubName,extSubURIetextSubSystemdécrivant le contexte d'analyse.
Valeur de retour
libxml_set_external_entity_loader() elle-même retourne void (elle retournait true avant PHP 8.0).
Comment utiliser libxml_set_external_entity_loader()
Définissez un callback, enregistrez-le, puis analysez. La politique sécurisée la plus simple consiste à tout bloquer en retournant toujours null :
<?php
// Block every external entity request.
libxml_set_external_entity_loader(
static fn (?string $publicId, ?string $systemId, array $context): ?string => null
);
$xml = <<<'XML'
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>
XML;
$doc = new DOMDocument();
$doc->loadXML($xml);
// The entity was blocked, so it expands to nothing.
echo "Loaded value: [" . $doc->documentElement->textContent . "]\n";
echo "Done without reading any external file.\n";
?>La référence &secret; se résout en une chaîne vide car notre chargeur a retourné null, donc /etc/passwd n'est jamais lu.
Mise sur liste blanche des sources de confiance
Si votre application a légitimement besoin de certaines entités externes (par exemple une DTD locale livrée avec votre code), autorisez uniquement les chemins en lesquels vous avez confiance et rejetez tout le reste :
<?php
function trusted_entity_loader(?string $publicId, ?string $systemId, array $context)
{
// Only allow files inside our own schema directory.
$allowedDir = __DIR__ . '/schemas/';
if ($systemId === null) {
return null;
}
$path = str_starts_with($systemId, 'file://')
? substr($systemId, 7)
: $systemId;
$real = realpath($path);
if ($real !== false && str_starts_with($real, realpath($allowedDir))) {
return file_get_contents($real); // Trusted: load it.
}
return null; // Everything else is blocked.
}
libxml_set_external_entity_loader('trusted_entity_loader');
echo "Loader registered: only ./schemas/ files may be resolved.\n";
?>Ici, tout systemId qui ne se résout pas vers un fichier réel dans schemas/ retourne null et est bloqué, tandis que les fichiers de schéma locaux de confiance se chargent normalement.
Remarque : Le chargeur est global et s'applique à chaque analyse libxml dans la requête. Réinitialisez-le avec
libxml_set_external_entity_loader(null)lorsque vous avez terminé si d'autres parties du code dans la même requête dépendent du comportement par défaut.
Erreurs courantes et pièges à éviter
- Retourner le mauvais type. Retournez une
string, uneresourceouverte ounull— retournerfalseoutruen'est pas valide et peut lever uneTypeErrorsous PHP 8. - Oublier que c'est global. Le dernier chargeur enregistré l'emporte pour toute la requête ; les bibliothèques qui définissent leur propre chargeur peuvent remplacer le vôtre.
- Supposer que vous avez toujours besoin de
libxml_disable_entity_loader(). Sous PHP 8+, les entités externes sont désactivées par défaut ; utilisez cette fonction uniquement lorsque vous avez besoin d'un contrôle personnalisé sur le chargement. - Faire confiance à
$systemIdaveuglément. Validez toujours le chemin/URL avant de le lire, sinon vous réintroduisez la faille XXE/SSRF que vous tentiez de combler.
Conclusion
libxml_set_external_entity_loader() vous offre un point de contrôle unique pour chaque entité externe qu'un parseur libxml tente de charger, en faisant la défense moderne et recommandée contre XXE et SSRF dans le traitement XML en PHP. Bloquez tout en retournant null, ou mettez sur liste blanche uniquement les ressources locales en lesquelles vous avez confiance. Pour la boîte à outils libxml plus large, consultez les fonctions connexes ci-dessous.
Voir aussi
- PHP libxml — aperçu de l'extension libxml et de ses constantes.
- libxml_disable_entity_loader() — l'ancienne méthode dépréciée pour désactiver le chargement des entités.
- PHP XML DOM — analyse XML avec
DOMDocument.