# XML external entities (XXE) in PHP voorkomen

> XXE in PHP voorkomen: wat sinds libxml 2.9 en PHP 8.0 geldt, welke vlaggen riskant zijn, LIBXML_NO_XXE vanaf PHP 8.4 en hoe u XML-invoerpunten vindt.

Bron: https://phpsos.de/nl/blog/xxe-in-php/  
Stand: 25-10-2026  
Aanbieder: PHP SOS, PHP-noodhulp en serverbeveiliging. Directe hulp via WhatsApp: https://wa.me/491736494914?text=Hallo%20PHP%20SOS%2C%20wij%20hebben%20hulp%20nodig%20met%20onze%20PHP-server.%20Bedrijf%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**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
<?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:

```bash
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](https://phpsos.de/nl/php2shell/php-8-1-und-aelter/). 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](https://phpsos.de/nl/ratgeber/deserialisierung-php/), het ophalen van interne doelen [Server-side request forgery in PHP voorkomen](https://phpsos.de/nl/blog/ssrf-in-php/).

## 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](https://phpsos.de/nl/leistungen/sicherheitsaudit-penetrationstest/).


## 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

- [PHP-handleiding: libxml_disable_entity_loader](https://www.php.net/manual/de/function.libxml-disable-entity-loader.php)
- [PHP-handleiding: libxml-constanten](https://www.php.net/manual/de/libxml.constants.php)
- [OWASP XML External Entity Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html)
