XXE: The Complete Practical Guide to XML External Entity Injection
XML Entities Basics for XXE
The XML you need for XXE: internal vs external entities, the DOCTYPE and DTD, general vs parameter entities, and why SYSTEM plus a URL is the dangerous part.
Published
Every XXE payload is built from a small set of XML pieces. You do not need to know XML deeply, you need to recognize entities, the DOCTYPE, and the one keyword that turns a document into a file reader. This page is the vocabulary the rest of the cluster assumes.
What an entity is
An entity is a named placeholder that the parser expands into a value when it reads the document. XML ships with a few built-in ones (for example the entity that represents an ampersand), and a document can declare its own. The attack is entirely about declaring an entity whose value is something you should not be able to read, then getting the parser to expand it.
Internal versus external entities
There are two kinds, and the difference is where the value comes from.
An internal entity holds a literal value defined right there in the document:
<!DOCTYPE foo [ <!ENTITY greeting "hello"> ]>
<data>&greeting;</data>The parser replaces &greeting; with hello. Harmless.
An external entity uses the SYSTEM keyword and points at a resource outside the document:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<data>&xxe;</data>Now the parser fetches /etc/passwd and expands &xxe; into its contents. That single change, a literal value becoming a SYSTEM URL, is XXE.
The DOCTYPE and the DTD
Entities are declared inside a Document Type Definition, and the DTD lives in the DOCTYPE at the top of the document. An inline DTD is the square-bracket block you see in these payloads:
<!DOCTYPE foo [ ...declarations go here... ]>This matters for defense: because entities can only be declared in a DTD, refusing to process the DOCTYPE at all removes every place an external entity could be declared. That is why "disable DTD processing" is the one true fix.
General versus parameter entities
Two syntaxes, two jobs.
A general entity is referenced in the document body with &name;:
<data>&xxe;</data>A parameter entity is referenced inside the DTD with %name;:
<!ENTITY % file SYSTEM "file:///etc/hostname">General entities are enough for a simple reflected file read. Parameter entities are the workhorses of blind and out-of-band XXE, because parsers often still allow them in the DTD when the obvious general-entity path is closed, and because you can nest them to build new declarations at parse time. You will see %name; everywhere once the response stops reflecting your data.
Why SYSTEM plus a URL is the dangerous part
Strip everything else away and the vulnerability is one construct: SYSTEM followed by a URL or file path. That is the instruction that tells the parser to reach outside the document and fetch something. Change the target and you change the attack:
file:///etc/passwd read a local file
http://169.254.169.254/... SSRF into the cloud metadata service
http://attacker.oast/ an out-of-band callback you controlThe whole cluster is variations on pointing SYSTEM somewhere useful and getting the result back.
The one thing that must be enabled
XXE only works if the parser is configured to process DTDs and resolve external entities. Many modern libraries now disable this by default, which is why XXE is less common than it was, but legacy parsers, older stacks, and apps that explicitly re-enable entity processing are still everywhere. When you find an XML endpoint, your first question is simply whether it resolves an entity at all. Confirm that, and the rest of the techniques open up.
With the vocabulary down, start exploiting it. Move to in-band file read for the simplest case, then blind XXE when nothing comes back. Try it on a free live target.
← Back to XXE: The Complete Practical Guide to XML External Entity Injection