libxml_set_external_entity_loader()
Erfahren Sie, wie libxml_set_external_entity_loader() in PHP funktioniert und wie Sie externe XML-Entitäten sicher kontrollieren und XXE-Angriffe abwehren.
Die Funktion libxml_set_external_entity_loader() registriert einen benutzerdefinierten Callback, den die libxml-basierten Parser in PHP (DOMDocument, SimpleXML, XMLReader) aufrufen, sobald ein XML-Dokument eine externe Entität laden möchte — also eine Datei, URL oder DTD, auf die innerhalb des Markups verwiesen wird. Durch die Kontrolle dieses Callbacks entscheiden Sie, welche externen Ressourcen geladen werden dürfen und welche blockiert werden. Dies ist der empfohlene, zukunftssichere Weg, um sich gegen XML External Entity (XXE)-Angriffe zu schützen. Diese Seite erklärt, was externe Entitäten sind, warum sie gefährlich sind, die Signatur der Funktion in PHP 8 und wie Sie einen sicheren Loader mit funktionierenden Beispielen schreiben.
Was ist eine externe Entität (und warum ist XXE gefährlich)?
Eine externe Entität ist ein Platzhalter, der in einer Document Type Definition (DTD) deklariert wird und auf Inhalte außerhalb des XML-Dokuments verweist. Beispiel:
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>Wenn ein Parser &secret; expandiert, liest er /etc/passwd und fügt den Dateiinhalt in das Dokument ein. Ein Angreifer, der die XML-Eingabe kontrolliert, kann damit lokale Dateien lesen, interne Netzwerk-Endpunkte erreichen (SSRF) oder durch "Billion Laughs"-Expansion einen Denial-of-Service auslösen. Das Blockieren — oder strikte Whitelisting — des Ladens externer Entitäten verhindert dies.
Was ist die Funktion libxml_set_external_entity_loader()?
libxml_set_external_entity_loader() ist eine eingebaute PHP-Funktion, die einen einzelnen globalen Callback registriert. Ab dem Moment, in dem er gesetzt wird, läuft jede Anfrage nach einer externen Entität von einem beliebigen libxml-Parser zuerst durch Ihren Callback. Gibt der Callback null zurück, weist libxml die Entität ab; gibt er den Entitätsinhalt zurück (als String oder geöffnete Resource), wird sie geladen. Die Funktion wurde in PHP 5.1.0 eingeführt und bleibt der empfohlene Ansatz, da das ältere libxml_disable_entity_loader() in PHP 8.0 als veraltet markiert wurde und in modernem PHP weitgehend überflüssig ist (externe Entitäten werden seit libxml 2.9 / PHP 8.0 standardmäßig nicht geladen).
Syntax
libxml_set_external_entity_loader(?callable $resolver_function): voidDas Übergeben von null entfernt einen zuvor registrierten Loader und stellt das Standardverhalten wieder her.
Parameter
| Parameter | Beschreibung |
|---|---|
resolver_function | Ein callable oder null zum Zurücksetzen. Der Callback erhält drei Argumente und muss den Entitätsinhalt (ein string), eine geöffnete resource oder null zum Blockieren zurückgeben. |
Die Callback-Signatur ab PHP 8.0+ lautet:
function (?string $public_id, ?string $system_id, array $context): string|resource|null$public_id— der öffentliche Bezeichner der Entität (oftnull).$system_id— der URI/Pfad, auf den die Entität verweist (z. B.file:///etc/passwd).$context— ein Array mit Schlüsseln wiedirectory,intSubName,extSubURIundextSubSystem, die den Parse-Kontext beschreiben.
Rückgabewert
libxml_set_external_entity_loader() selbst gibt void zurück (vor PHP 8.0 wurde true zurückgegeben).
Wie man libxml_set_external_entity_loader() verwendet
Definieren Sie einen Callback, registrieren Sie ihn und parsen Sie dann. Die einfachste sichere Richtlinie ist, alles zu blockieren, indem immer null zurückgegeben wird:
<?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";
?>Der Verweis &secret; wird zu einem leeren String aufgelöst, da unser Loader null zurückgegeben hat — /etc/passwd wird also niemals gelesen.
Vertrauenswürdige Quellen auf die Whitelist setzen
Wenn Ihre Anwendung einige externe Entitäten tatsächlich benötigt (z. B. eine lokale DTD, die mit Ihrem Code ausgeliefert wird), erlauben Sie nur die Pfade, denen Sie vertrauen, und weisen Sie alles andere ab:
<?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";
?>Jede systemId, die nicht zu einer echten Datei innerhalb von schemas/ aufgelöst werden kann, gibt null zurück und wird blockiert, während vertrauenswürdige lokale Schema-Dateien normal geladen werden.
Hinweis: Der Loader ist global und gilt für jeden libxml-Parse-Vorgang in der Anfrage. Setzen Sie ihn mit
libxml_set_external_entity_loader(null)zurück, wenn Sie fertig sind, falls anderer Code in derselben Anfrage auf das Standardverhalten angewiesen ist.
Häufige Fehler und Fallstricke
- Falschen Typ zurückgeben. Geben Sie einen
string, eine geöffneteresourceodernullzurück — die Rückgabe vonfalseodertrueist ungültig und kann unter PHP 8 einenTypeErrorauslösen. - Vergessen, dass er global ist. Der zuletzt registrierte Loader gilt für die gesamte Anfrage; Bibliotheken, die ihren eigenen Loader setzen, können Ihren überschreiben.
- Davon ausgehen, dass
libxml_disable_entity_loader()noch benötigt wird. Ab PHP 8+ sind externe Entitäten standardmäßig deaktiviert; verwenden Sie diese Funktion nur, wenn Sie benutzerdefinierte Kontrolle über das Laden benötigen. $systemIdblind vertrauen. Validieren Sie immer den Pfad/die URL, bevor Sie ihn lesen, sonst öffnen Sie die XXE/SSRF-Lücke, die Sie schließen wollten, wieder.
Fazit
libxml_set_external_entity_loader() bietet Ihnen einen einzigen Kontrollpunkt für jede externe Entität, die ein libxml-Parser zu laden versucht, und ist damit die moderne, empfohlene Verteidigung gegen XXE und SSRF bei der XML-Verarbeitung in PHP. Blockieren Sie alles durch Rückgabe von null, oder setzen Sie nur die lokalen Ressourcen auf die Whitelist, denen Sie vertrauen. Weitere Informationen zum libxml-Toolkit finden Sie in den verwandten Funktionen unten.
Siehe auch
- PHP libxml — Überblick über die libxml-Erweiterung und ihre Konstanten.
- libxml_disable_entity_loader() — die ältere, veraltete Methode zum Deaktivieren des Ladens von Entitäten.
- PHP XML DOM — XML-Parsing mit
DOMDocument.