XXE
classAn XML parser that resolves external entities, turned into file read and SSRF.
01Does it accept a DOCTYPE at all
No network, no callback, no external anything. This is the first thing you send.
Before entities, before files, before out-of-band: does the parser even let you declare a DOCTYPE, and does it expand an entity you define yourself? An internal entity answers both, and it never leaves the target.
XXETEST comes back in the response, entities expand and you are in business.XXETEST... comes back | Entities expand. Go straight to the file read below. |
| Value empty, no error | Parsed, entity silently dropped. Try error-based or blind. |
| "DOCTYPE is disallowed" | Properly hardened. Stop, and check for XPath injection instead. |
| Parse error naming a line | It parses XML but rejected your syntax. Fix the shape and retry. |
| No change at all | Probably not being parsed as XML. Check the Content-Type. |
Why this before anything else — an internal entity proves the mechanism with zero footprint. If it works, you know the next payload will too. If a DOCTYPE is refused outright, you have saved yourself an hour of firing external payloads at a parser that was never going to resolve them.
02Read a file, in-band
The entity comes back in the response. Still no network needed.
< and & cannot break the parse.A file containing < or & breaks the XML parse and looks exactly like a failure. /etc/passwd is safe; almost any config file is not. If a read appears to fail, retry with /etc/hostname before concluding the parser is patched — and reach for the PHP base64 filter when you need a file that isn't XML-safe.
03Error-based
Nothing is reflected, but errors are verbose.
This is the step people skip. It needs no callback server, it works behind an egress filter, and a stack trace with the file contents in it is about as clean a proof as you can put in a report.
04Blind — out-of-band
Only once the three above have all failed.
Host evil.dtd on your own server:
Then send this to the target:
A newline in the file breaks the URL and the request never fires, so plain OOB exfiltration only works on single-line files. Wrap the read in php://filter/convert.base64-encode where you can, or use the error-based method above instead.
05Where to inject it
Half of all XXE lives in endpoints nobody thought were XML.
Content-Type: application/xml | The obvious one. |
| A JSON endpoint | Change the Content-Type to application/xml and send XML. Plenty of frameworks parse both. |
| File upload | .docx, .xlsx, .pptx, .svg are zip archives full of XML. Replace a part, re-zip. |
| SOAP | Old, and frequently unpatched. |
| SAML | The assertion is XML, and it is parsed *before* authentication. |
| RSS / sitemap importers | Anywhere the app fetches XML from a URL you supplied. |
06When entities are disabled
Not everything XML-shaped is XXE.
If external entities are off, check whether the values inside the XML reach an XPath query. Different bug, same request, and it is frequently unprotected because everyone was busy fixing XXE.