Kurz gesagt: XXE in PHP verhindern Sie, indem Sie XML aus fremden Quellen ohne die Optionen LIBXML_NOENT, LIBXML_DTDLOAD und LIBXML_DTDVALID laden, Dokumente mit eigener DTD ablehnen und einen Entity-Loader setzen, der externe Ressourcen verweigert. Seit libxml 2.9.0 ersetzt PHP Entities standardmäßig nicht mehr, deshalb ist libxml_disable_entity_loader() seit PHP 8.0 veraltet. Ab PHP 8.4 mit libxml 2.13 steht zusätzlich LIBXML_NO_XXE bereit.
Eine alte Lücke, die nicht verschwindet
XML External Entities, kurz XXE, gelten bei vielen Entwicklern als erledigtes Thema. PHP hat das Laden externer Entities mit modernen libxml-Versionen standardmäßig abgeschaltet, und die alte Schutzfunktion wurde mit PHP 8.0 als veraltet markiert. Trotzdem tauchen XXE-Lücken in Prüfungen weiter auf.
Der Grund liegt fast immer in einer von drei Situationen: Jemand hat eine Option gesetzt, um ein Importproblem zu lösen, und damit die Entity-Ersetzung wieder eingeschaltet. Eine Bibliothek tut das intern. Oder die Anwendung läuft auf einem Altsystem, dessen PHP mit einer sehr alten libxml gebaut wurde.
XXE ist gefährlich, weil der XML-Parser dabei Inhalte nachlädt, die im Dokument nur referenziert sind: lokale Dateien oder Adressen im Netz. Mögliche Folgen sind das Auslesen von Konfigurationsdateien, Anfragen an interne Systeme ähnlich wie bei SSRF und Überlastung des Servers.
Was seit libxml 2.9 und PHP 8.0 gilt
Das PHP-Handbuch beschreibt die Lage so: Ab libxml 2.9.0 ist die Ersetzung von Entities standardmäßig deaktiviert. Externe Entities müssen deshalb nicht mehr eigens abgeschaltet werden, es sei denn, interne Entity-Referenzen sollen mit bestimmten Optionen aufgelöst werden. Daraus ergibt sich diese Übersicht:
| Option | Wirkung | Risiko bei fremdem XML |
|---|---|---|
LIBXML_NOENT |
ersetzt Entities | laut Handbuch kann sie XXE-Angriffe erleichtern |
LIBXML_DTDLOAD |
lädt das externe Subset | aktiviert das Laden externer Entities |
LIBXML_DTDVALID |
validiert gegen die DTD | kann XXE-Angriffe erleichtern |
LIBXML_NONET |
verbietet Netzwerkzugriffe beim Laden | schützende Zusatzoption |
LIBXML_NO_XXE |
schaltet externe Entities bei der Ersetzung ab | schützend, ab PHP 8.4 und libxml 2.13.0 |
LIBXML_PARSEHUGE |
hebt interne Grenzen des Parsers auf | erleichtert Überlastung, nur für vertrauenswürdige Daten |
Der irreführende Name von LIBXML_NOENT ist eine bekannte Falle: Er klingt nach „keine Entities“, bewirkt aber das Gegenteil.
Für Entwickler heißt das: Riskant ist nicht das Laden von XML an sich, sondern jede Stelle, die das Standardverhalten bewusst ändert. Eine Suche nach diesen Konstanten im Code ist deshalb der schnellste Weg zu einer ersten Einschätzung, noch vor jeder aufwendigen Prüfung.
XXE in PHP sicher abwehren: ein Muster für DOMDocument
Das folgende Beispiel kombiniert mehrere Schutzschichten. Es setzt einen Entity-Loader, der jede externe Ressource verweigert, lädt ohne riskante Optionen und lehnt Dokumente mit eigener DTD ab:
<?php
declare(strict_types=1);
function ladeFremdesXml(string $xml): DOMDocument
{
// Externe Ressourcen grundsätzlich verweigern
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;
}
Laut PHP-Handbuch blockiert ein Rückgabewert null im Loader das Laden der externen Ressource. OWASP weist darauf hin, dass es nicht genügt, null direkt an libxml_set_external_entity_loader() zu übergeben; es braucht eine Funktion, die null zurückgibt. Für SimpleXML und XMLReader gelten dieselben Optionen. Wo immer möglich, empfiehlt OWASP, DTDs ganz abzulehnen.
Wenn Sie das Datenformat selbst bestimmen können, ist JSON für den Austausch zwischen eigenen Systemen meist die einfachere Wahl. Für fremde Formate wie SAML oder Office-Dateien bleibt XML unvermeidbar.
Versteckte XML-Eingänge finden
Viele Anwendungen verarbeiten mehr XML, als ihre Entwickler denken. Gehen Sie diese Liste durch:
- Schnittstellen: SOAP-Clients und -Server, XML-RPC, Zahlungs- und Versanddienste mit XML-Antworten.
- Anmeldung: Single Sign-on mit SAML basiert auf XML-Dokumenten, die von außen kommen.
- Importe: Produktdaten, Feeds, Sitemaps, Katalogformate.
- Uploads: SVG-Bilder sind XML; Office-Formate wie DOCX und XLSX bestehen intern aus XML-Dateien.
- Bibliotheken: Composer-Pakete für Dokumente, Tabellen oder SAML, die intern libxml nutzen.
Im Code helfen zwei Suchen:
grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"
Fundstellen in vendor/ sind kein Grund zur Panik, aber ein Anlass, Version und Sicherheitsmeldungen der Bibliothek zu prüfen.
Bei SVG-Uploads ist XXE nur eines von mehreren Risiken, denn SVG-Dateien können auch Skripte enthalten. Braucht Ihre Anwendung das Format nicht zwingend, lassen Sie es für Uploads gar nicht zu. Andernfalls verarbeiten Sie SVG nur mit dem sicheren Muster oben und liefern die Dateien so aus, dass der Browser enthaltene Skripte nicht ausführt.
Achten Sie außerdem auf Fehlermeldungen. Gibt eine Anwendung die Meldungen von libxml ungefiltert an Nutzer zurück, können darin Teile des verarbeiteten Inhalts stehen. Fehler gehören ins Log, der Nutzer erhält eine allgemeine Meldung; im Beispiel oben sorgt libxml_use_internal_errors(true) dafür, dass Warnungen nicht in die Ausgabe geraten. Begrenzen Sie zudem die Größe empfangener XML-Dokumente, bevor der Parser sie überhaupt sieht.
Altsysteme: Wann der Standardschutz fehlt
Der Schutz durch das Standardverhalten hängt an der libxml-Version, mit der PHP gebaut wurde. Welche Version läuft, zeigt php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' auf der Kommandozeile oder die Ausgabe von phpinfo() in einer geschützten Umgebung. Prüfen Sie den Wert für jede PHP-Version auf dem Server, denn CLI und Webserver können unterschiedliche Builds nutzen. Container-Images bringen ihre eigene libxml mit; dort prüfen Sie im laufenden Container, nicht auf dem Host.
Besonders betroffen sind Systeme, die ohnehin auf alten PHP-Versionen laufen. Für PHP 8.1 und älter gibt es nach PHP2Shell keine offiziellen Updates des PHP-Projekts mehr; was das bedeutet, beschreibt PHP 8.1 und älter: Was jetzt gilt. Ein Altsystem mit veralteter libxml und offenen Interpreter-Lücken sollte nicht nur im Code abgesichert, sondern migriert werden. Verwandte Risiken bei der Verarbeitung fremder Daten beschreibt Unsichere Deserialisierung in PHP, Abrufe interner Ziele Server-Side Request Forgery in PHP verhindern.
Wie hilft PHP SOS konkret?
- Wir finden alle Stellen, an denen Ihre Anwendung und ihre Bibliotheken XML aus fremden Quellen verarbeiten.
- Wir entfernen riskante Parser-Optionen und bauen eine zentrale, abgesicherte Ladefunktion ein.
- Wir prüfen libxml- und PHP-Versionen auf allen Servern und planen bei Altsystemen die Migration.
- Wir testen die Änderungen mit harmlosen Prüfdokumenten und dokumentieren das Ergebnis.
Die Prüfung eigener Software beschreibt die Leistung Sicherheitsaudit und Penetrationstest.
Häufige Fragen
Sind PHP-Anwendungen seit PHP 8 automatisch vor XXE geschützt?
Im Standardfall weitgehend, weil libxml ab Version 2.9.0 Entities nicht mehr ersetzt. Der Schutz entfällt aber, sobald Code Optionen wie LIBXML_NOENT, LIBXML_DTDLOAD oder LIBXML_DTDVALID setzt oder PHP mit einer sehr alten libxml-Version gebaut ist.
Soll ich libxml_disable_entity_loader noch aufrufen?
Nein. Die Funktion ist seit PHP 8.0 veraltet, weil das Laden externer Entities standardmäßig abgeschaltet ist. Für zusätzlichen Schutz empfiehlt das PHP-Handbuch libxml_set_external_entity_loader und ab PHP 8.4 mit passender libxml die Konstante LIBXML_NO_XXE.
Wo verarbeitet meine Anwendung überhaupt XML?
Oft an unerwarteten Stellen: SOAP-Schnittstellen, Single Sign-on mit SAML, RSS-Feeds, Sitemaps, Produktimporte, SVG-Uploads und Office-Dateien, die intern aus XML bestehen. Auch Bibliotheken können XML verarbeiten, ohne dass es im eigenen Code sichtbar ist.
Was ist die Billion-Laughs-Attacke?
Ein Dokument mit verschachtelten Entities, das beim Auflösen enorm anwächst und Speicher und Rechenzeit verbraucht. Sie gehört zur selben Familie wie XXE. Schutz bieten das Abschalten der Entity-Ersetzung und das Ablehnen von Dokumenten mit eigener DTD.