Actuele situatie: PHP2Shell. Twee kritieke PHP-kwetsbaarheden (CVSS 9.2) maken schadelijke code zonder inloggen mogelijk. Patches sinds 21-10-2026.Wat u nu moet doen →

XML external entities (XXE) in PHP voorkomen

Hoe ontwikkelaars XML in PHP veilig verwerken: standaardgedrag van libxml, riskante opties zoals LIBXML_NOENT, bescherming met entity-loader en LIBXML_NO_XXE en typische verborgen XML-invoerpunten.

Stand 6 min. leestijdVeilig ontwikkelen

Kort gezegd: XXE in PHP voorkomt u door XML uit vreemde bronnen te laden zonder de opties LIBXML_NOENT, LIBXML_DTDLOAD en LIBXML_DTDVALID, documenten met een eigen DTD af te wijzen en een entity-loader in te stellen die externe bronnen weigert. Sinds libxml 2.9.0 vervangt PHP entities standaard niet meer, daarom is libxml_disable_entity_loader() sinds PHP 8.0 verouderd. Vanaf PHP 8.4 met libxml 2.13 is bovendien LIBXML_NO_XXE beschikbaar.

Een oude kwetsbaarheid die niet verdwijnt

XML external entities, kortweg XXE, gelden bij veel ontwikkelaars als een afgedaan onderwerp. PHP heeft het laden van externe entities met moderne libxml-versies standaard uitgeschakeld en de oude beschermingsfunctie is met PHP 8.0 als verouderd gemarkeerd. Toch duiken XXE-kwetsbaarheden bij controles nog steeds op.

De reden ligt bijna altijd in een van drie situaties: iemand heeft een optie ingesteld om een importprobleem op te lossen en daarmee de entity-vervanging weer ingeschakeld. Een bibliotheek doet dat intern. Of de applicatie draait op een oud systeem waarvan de PHP met een zeer oude libxml is gebouwd.

XXE is gevaarlijk, omdat de XML-parser daarbij inhoud nalaadt die in het document alleen wordt genoemd: lokale bestanden of adressen in het netwerk. Mogelijke gevolgen zijn het uitlezen van configuratiebestanden, verzoeken aan interne systemen vergelijkbaar met SSRF en overbelasting van de server.

Wat sinds libxml 2.9 en PHP 8.0 geldt

De PHP-handleiding beschrijft de situatie zo: vanaf libxml 2.9.0 is de vervanging van entities standaard gedeactiveerd. Externe entities hoeven daarom niet meer apart te worden uitgeschakeld, tenzij interne entityverwijzingen met bepaalde opties moeten worden omgezet. Daaruit volgt dit overzicht:

Optie Werking Risico bij vreemd XML
LIBXML_NOENT vervangt entities volgens de handleiding kan ze XXE-aanvallen vergemakkelijken
LIBXML_DTDLOAD laadt de externe subset activeert het laden van externe entities
LIBXML_DTDVALID valideert tegen de DTD kan XXE-aanvallen vergemakkelijken
LIBXML_NONET verbiedt netwerktoegang bij het laden beschermende aanvullende optie
LIBXML_NO_XXE schakelt externe entities bij de vervanging uit beschermend, vanaf PHP 8.4 en libxml 2.13.0
LIBXML_PARSEHUGE heft interne grenzen van de parser op vergemakkelijkt overbelasting, alleen voor vertrouwde gegevens

De misleidende naam van LIBXML_NOENT is een bekende valkuil: ze klinkt als "geen entities", maar doet het tegenovergestelde.

Voor ontwikkelaars betekent dit: riskant is niet het laden van XML op zich, maar elke plek die het standaardgedrag bewust wijzigt. Een zoektocht naar deze constanten in de code is daarom de snelste weg naar een eerste inschatting, nog vóór elke uitgebreide controle.

XXE in PHP veilig afweren: een patroon voor DOMDocument

Het volgende voorbeeld combineert meerdere beschermingslagen. Het stelt een entity-loader in die elke externe bron weigert, laadt zonder riskante opties en wijst documenten met een eigen DTD af:

<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Externe bronnen principieel weigeren
    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;
}

Volgens de PHP-handleiding blokkeert een retourwaarde null in de loader het laden van de externe bron. OWASP wijst erop dat het niet volstaat null rechtstreeks aan libxml_set_external_entity_loader() door te geven; er is een functie nodig die null teruggeeft. Voor SimpleXML en XMLReader gelden dezelfde opties. Waar mogelijk adviseert OWASP DTD's helemaal af te wijzen.

Tip

Kunt u het gegevensformaat zelf bepalen, dan is JSON voor de uitwisseling tussen eigen systemen meestal de eenvoudigere keuze. Voor vreemde formaten zoals SAML of Office-bestanden blijft XML onvermijdelijk.

Verborgen XML-invoerpunten vinden

Veel applicaties verwerken meer XML dan hun ontwikkelaars denken. Loop deze lijst door:

  1. Interfaces: SOAP-clients en -servers, XML-RPC, betaal- en verzenddiensten met XML-antwoorden.
  2. Aanmelding: Single sign-on met SAML is gebaseerd op XML-documenten die van buiten komen.
  3. Imports: productgegevens, feeds, sitemaps, catalogusformaten.
  4. Uploads: SVG-afbeeldingen zijn XML; Office-formaten zoals DOCX en XLSX bestaan intern uit XML-bestanden.
  5. Bibliotheken: Composer-pakketten voor documenten, spreadsheets of SAML die intern libxml gebruiken.

In de code helpen twee zoekopdrachten:

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

Vindplaatsen in vendor/ zijn geen reden tot paniek, maar wel een aanleiding om versie en beveiligingsmeldingen van de bibliotheek te controleren.

Bij SVG-uploads is XXE slechts een van meerdere risico's, want SVG-bestanden kunnen ook scripts bevatten. Heeft uw applicatie het formaat niet per se nodig, laat het dan helemaal niet toe voor uploads. Verwerk SVG anders alleen met het veilige patroon hierboven en lever de bestanden zo uit dat de browser ingesloten scripts niet uitvoert.

Let bovendien op foutmeldingen. Geeft een applicatie de meldingen van libxml ongefilterd terug aan gebruikers, dan kunnen daarin delen van de verwerkte inhoud staan. Fouten horen in het logboek, de gebruiker krijgt een algemene melding; in het voorbeeld hierboven zorgt libxml_use_internal_errors(true) ervoor dat waarschuwingen niet in de uitvoer terechtkomen. Beperk daarnaast de grootte van ontvangen XML-documenten voordat de parser ze überhaupt ziet.

Oude systemen: wanneer de standaardbescherming ontbreekt

De bescherming door het standaardgedrag hangt af van de libxml-versie waarmee PHP is gebouwd. Welke versie draait, toont php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' op de opdrachtregel of de uitvoer van phpinfo() in een beveiligde omgeving. Controleer de waarde voor elke PHP-versie op de server, want CLI en webserver kunnen verschillende builds gebruiken. Containerimages brengen hun eigen libxml mee; controleer daar in de draaiende container, niet op de host.

Bijzonder getroffen zijn systemen die sowieso op oude PHP-versies draaien. Voor PHP 8.1 en ouder zijn er na PHP2Shell geen officiële updates van het PHP-project meer; wat dat betekent, beschrijft PHP 8.1 en ouder: wat nu geldt. Een oud systeem met verouderde libxml en open interpreterkwetsbaarheden moet niet alleen in de code worden beveiligd, maar worden gemigreerd. Verwante risico's bij het verwerken van vreemde gegevens beschrijft Onveilige deserialisatie in PHP, het ophalen van interne doelen Server-side request forgery in PHP voorkomen.

Hoe helpt PHP SOS concreet?

  • Wij vinden alle plekken waar uw applicatie en haar bibliotheken XML uit vreemde bronnen verwerken.
  • Wij verwijderen riskante parseropties en bouwen een centrale, beveiligde laadfunctie in.
  • Wij controleren libxml- en PHP-versies op alle servers en plannen bij oude systemen de migratie.
  • Wij testen de wijzigingen met onschuldige testdocumenten en documenteren het resultaat.

De controle van eigen software beschrijft de dienst beveiligingsaudit en penetratietest.

Veelgestelde vragen

Zijn PHP-applicaties sinds PHP 8 automatisch beschermd tegen XXE?

In het standaardgeval grotendeels, omdat libxml vanaf versie 2.9.0 entities niet meer vervangt. De bescherming vervalt echter zodra code opties zoals LIBXML_NOENT, LIBXML_DTDLOAD of LIBXML_DTDVALID instelt of PHP met een zeer oude libxml-versie is gebouwd.

Moet ik libxml_disable_entity_loader nog aanroepen?

Nee. De functie is sinds PHP 8.0 verouderd, omdat het laden van externe entities standaard is uitgeschakeld. Voor extra bescherming adviseert de PHP-handleiding libxml_set_external_entity_loader en vanaf PHP 8.4 met passende libxml de constante LIBXML_NO_XXE.

Waar verwerkt mijn applicatie überhaupt XML?

Vaak op onverwachte plekken: SOAP-interfaces, single sign-on met SAML, RSS-feeds, sitemaps, productimports, SVG-uploads en Office-bestanden die intern uit XML bestaan. Ook bibliotheken kunnen XML verwerken zonder dat het in de eigen code zichtbaar is.

Wat is de billion-laughs-aanval?

Een document met genestelde entities dat bij het omzetten enorm groeit en geheugen en rekentijd verbruikt. Ze behoort tot dezelfde familie als XXE. Bescherming bieden het uitschakelen van entity-vervanging en het afwijzen van documenten met een eigen DTD.

Bronnen

Direct contact

Elk uur telt. Schrijf ons nu.

Beschrijf kort het systeem, de PHP-versie en wat u opmerkt. Wij nemen contact op, brengen de situatie in kaart en werken op afstand tot het incident is afgerond en gedocumenteerd.

Directe hulp