1

Hack.LU 2013 CTF Wannabe Writeup Part One: Web Exploitation

Introduction

I attended the Hack.LU conference which took place during 22-24th of October 2013. This conference is well-known for its excellent capture the flag contest created by fluxfingers, which was no different this year.  By the end of the conference, there were 21 published challenges within the fields of crypto, web, reverse engineering, exploiting, internals, and miscellaneous. After completing the fairly easy ‘Pay TV’ and ‘RoboAuth’ challenges during my limited free time on the first and second day (I was attending courses rather than dedicating time to the CTF), I started focusing on a harder challenge in the evening, according to its 400 points reward: ‘Wannabe’. It was categorized as ‘exploiting’ and came with the following description:

Wannabe (Category: Exploiting)

One of our informants met a guy who calls himself Elite Arthur, he is a real jackass, and he thinks he is the best hacker alive. We got reason to believe that the robots hired him to write the firmwares for their weapons. But to write such a firmware we need the key to sign the code. Luckily for us, our informant also found his website: …. your job is to hack the server, find the flag and show this little cocksucker how skilled he really is. We count on you.

Here is your challenge: https://ctf.fluxfingers.net:1317. Alternatively, you can reach the challenge without a reverse proxy but also without SSL here: http://ctf.fluxfingers.net:1339 (offline at time of writing)

It was one of the harder challenges in the list. When the CTF came to an end, 9 teams managed to capture the right flag (see https://ctf.fluxfingers.net/scoreboard). Unfortunately, I was not amongst them, since I did not manage to finish the challenge before the CTF deadline passed. Luckily, the Fluxfingers team kept all their CTF challenges online (only the case for the HTTPS version of Wannabe at the time of writing) and I was able to complete it properly.

There were two main phases in this challenge: the first one was exploiting a web application through a number of vulnerabilities in order to get a limited shell on the system (www-data); the second phase consisted of elevating privileges from this limited shell to another user by exploiting some buffer overflows in a x64 ELF binary, of which the source code was available. The first phase is covered in this blogpost, the second phase can be found here: Hack.LU 2013 CTF Wannabe Writeup Part Two: Buffer Overflow Exploitation.

First Impressions

When browsing to the target URL, the following web application was found:

Wannabe_Landing_page

Four cookies are initially set: ‘user_id’, ‘user_hash’, ‘user_bugs’ and ‘user_mac’. Their meaning will become clearer later on.
The pages behind the ‘Contact’ and ‘Bug bounty’ tabs were found to accept some user input:
contact
bugbounty

So the website had some user management, contact and upload functionality to be leveraged.

User enumeration

Attempting to login with login credentials test:test and admin:admin quickly revealed that user enumeration was possible and the user ‘admin’ was present:

admin_user_enumtest_user_enum

 

A bruteforce attack on the admin user, as well as attempting to reset this user’s password via the ‘forgot password’ functionality did not deliver any fruitful results.

File Upload

The file upload functionality clearly stated that only images under 100kb may be uploaded as accompanying proof for the bug that is reported. First, a valid image ‘image.jpeg’ titled ‘FileUploadTest’ was submitted and the corresponding request was inspected in our intercepting proxy:

 fileupload_success

The ‘download prove’ button is a link to address http://ctf.fluxfingers.net:1339/?site=bug&action=dl&id=19102 which triggered a download for file ‘414fb08431b1edee73094eea839e465.jpeg’ containing the same image as the uploaded image.jpeg file. Changing the value of the id parameter yielded an access violation error, basically informing us we don’t have access to download the resource corresponding with this id.

Cookies

When looking more closely to the request and response of the file upload handling, one can notice that, although four new cookie values were issued for the four cookies set earlier, only two of them actually contained a different value: user_bugs and user_mac. Assumption was made that user_id and user_hash cookies function as session cookies, so they must be maintained to keep track of the uploaded files of the current visitor of the website, while the user_bugs and user_mac cookies serve some other purpose. Further investigation pointed out that the ‘user_bugs’ cookie contains a serialized php object representing the images we are allowed to download. Base64-decoding the user_bugs file reveals the serialized PHP object containing the image id’s we are currently allowed to download:

However, tampering with any of the cookies led to a ‘Cookie has been modified’ error message. This was probably detected on the server-side of the web application by comparing the user_mac cookie value with the decrypted value of the user_bugs cookie, the user_id and user_hash values, and possibly other unknowns. Since finding a collision in an unknown MAC algorithm seemed a bit too far off (even for fluxfingers), this route was abandoned.

File Extension

Aiming for a quick win, attempts were made to upload script files that would possibly get executed on the server-side, by replaying the upload request and only changing the extension of the filename value parameter (originally image.jpeg), but this yielded no results:

FileUploadExtensionBruteforce

However, something interesting was identified in the upload handling function itself…

Web Vulnerabilities

SQL Injection

While replaying the upload request and adding a quote after the ‘rating’ POST, an SQL Error message was shown:

 

A couple of conclusions were made from this error message:

  • This is an INSERT message, saving information about the uploaded file in the database as a record.
  • The real location of the image file on disk relative to the website’s root directory is upload/ede750ccd02f872aacf679bec4a251d9.jpeg. This was verified by requesting http://ctf.fluxfingers.net:1339/upload/ede750ccd02f872aacf679bec4a251d9.jpeg, which indeed returned our uploaded image (will probably will not work anymore at this time, since the uploads are periodically removed and HTTP is offline at time of writing).
  • The rating variable is not escaped properly for database interaction, but quotes are escaped for html/javascript output to \’. This implies that we will not be able to use quotes while exploiting this vulnerability.
  • We control the ‘title’ and ‘path’ field of the inserted uploaded file database record.

Next to the usual database enumeration that an SQL Injection vulnerability allows, this particular flaw also allows us to download arbitrary files from disk, simply by replacing the path string. The following payloads shows how information can be retrieved from the database and the filesystem at the same time:

1,(SELECT @@version),0x2f696e6465782e706870,1)– –
Result:
SQLInjectFileDownloadVulnerability
One can see that the result of the subquery (SELECT @@version) is echoed as the title of the newly uploaded file, and the download link triggered a download of index.php – the ASCII value of 0x2f696e6465782e706870 (remember, we cannot use single nor double quotes since they are escaped for html output). We went ahead and downloaded all the web application’s custom files. You can download the treasure trove at the bottom of this post. The source code confirmed what we expected earlier: the user_mac cookie contains a hash of the other three cookies in order to protect them from tampering:

We leveraged this knowledge to construct cookies for the admin user, since no unknown value such as a cleartext password is necessary to create them. The user_id value for user admin was obtained by exploiting the SQL injection vulnerability once more, which gave ‘e62552ab44206edaee9d25e57f6dc220’ as result. Working our way down the chain, we created the following admin cookies:

By inserting these cookies in the browser, we were able to login as admin. The admin has access to the ‘control panel’, which is nothing more than a simple page where one can insert a news message. However, the cleartext password of the admin user was needed to execute this functionality, which we didn’t have. Further examination of the source code also revealed a pretty strong password policy combined with sha1, which made cracking the password an impractical approach.

adminlandingpage

 

admincontrolpanel

Preg_replace Remote Command Execution

Since the admin panel seemed a dead end, the source code was examined for other flaws. First of all, we checked whether php object injection exploitation was possible, since the user_bugs cookie is unserialized in the User.php constructor with a cookie value which introduces an injection flaw. Unfortunately, no ‘magic methods’ were found in the web application’s source code (including Twig framework), so this was also a no-go (check vagosec’s recent blogpost about a WordPress PHP Object Injection vulnerability for a more in-depth explanation of this kind of vulnerability). Additionally, an eval() call in the include/password-policy.php was investigated, but no arguments were under attacker control. Finally, a RCE bug was found in the extension/filter.php file:

The flaw was tested locally with the interactive php shell phpsh and found to be working:

So we went hunting for templates where the filter was used and we could control the source string. Both base.twig and lost.twig contained references to the filter, but the source string was hardcoded. Eventually, we figured out that upon previewing a newly created message as admin, the title and message parts were rendered by a twig renderer object containing the filter:

The trick here is to notice that the template is rendered twice: first some attributes such as title and text are inserted in the template, and hereafter a temporary renderer object again renders this page. Triggering a filter in Twig is done according to the following syntax: {{string | filtername}}, so if we supply {{“red: {$ {print(phpinfo ())}}” | makestatus}} as title or text, we can execute arbitrary PHP!

So far for the good news. Remember that in order to reach the preview functionality, we need the cleartext password of the admin user, which we cannot guess or crack. As it turned out, another vulnerability allowed us to reset the admin’s password.

Loose Comparison Magic

The users in the database were enumerated using the SQL Injection flaw. Two users were present in the system: admin and guest. When looking to the password reset functionality, it was noticed that it consists of two stages: upon entering a username, a random reset code for this specific user is generated and saved in the database. On the next page, the user must supply this reset code, as well as a new password. The first phase is disabled for the admin account:

The check here is done by comparing the username strings, which is not bypassable. However, the controller function that handles the next page (‘reset password’) checks this differently:

On line 38, the user corresponding with the provided reset code is looked up in the database. Only the guest user has a non-empty reset code in the database, so there’s no way to get around that. On line three, the id GET parameter is retrieved, which is subsequently compared with the id of the user fetched from the database. The comparison here is ‘==’. Upon inserting the new password on line 49, the same GET id parameter value is provided to the intval() function. Since the admin user’s parameter is equal to zero and the guest user is equal to one, the comparison on line 43, ‘1==$id’, should be true, while the ‘intval($id)’ value should be zero in order to reset the admin’s password instead of the one of the guest user. Turns out this is possible in PHP (great job Fluxfingers!). The vulnerability here is that the loose comparison of strings with integers in PHP is not the same as the intval() function’s ‘casting’ behaviour:

  • Intval() will yield zero for every string that does not start with a decimal base number or only has a trailing +/- sign and/or zeroes before hitting a non-decimal base character (even decimal points, hex/octal/binary/power specific characters), although none of this is formally stated by its Manpage (verified empirically). This is our primary requirement.
  • == will interpret the string as a number or float based on other rules, such as ‘looking as a float point’ or ‘being a hex value’ .

Floats

When supplying a string that “looks” like a float (containing a dot, e or E), the loose comparison will also convert the integer guest userid coming from the database (1) to a float. This can be leveraged in two ways:

  1. Supply a string that is interpreted as a float equal to one but only contains only leading zeroes before a float-specific character:
  2. Supply a string that is interpreted as a float close to one but only contains leading zeroes before a float-specific character. Due to precision limitations with comparisons of floats, this will also lead to an exploitable condition (order of 1.11e-16 according to PHP him-self):

Hex format

Albeit a rather undocumented PHP ‘feature’, the loose comparison converts a string containing a hex representation (x or X are accepted) of an integer to the corresponding integer value (‘literally’):

Oddly enough, other literal declaration options such as binary (1 == 0b00000001) or octal (83 == “0123”) don’t work with the loose comparison, so it’s unclear to me on what base the PHP developers made this decision.

Exploitation

Finally, we have all we need to execute commands on the underlying operating system of this webserver. We first go through the first password reset phase by entering the guest username, in order to generate a fresh reset code:

reset_guest_password

We grab the reset code for the guest user via the SQL Injection flaw (SELECT reset from 6karuhf843_user WHERE …), supply a new password of at least length 20 containing at least 4 numbers, 2 lower- and 2 uppercase characters, intercept the request and alter the id GET parameter value to 0.1e1:

reset_guest_password_2

GET /?action=update&site=lost&id=0.1e1&pass=*********************&pass2=******************&code=2e6611b3ca2ff7f18cc7af44e2fbafbfb58805 HTTP/1.1

We get the message ‘password reset successfully’. When providing the password upon previewing a new message, we succeed. By providing a text that leverages the preg_replace RCE flaw, we finally get our code execution:

RCE

This concludes my writeup for the first phase of the challenge. Since this post turned out a bit longer than expected, you can find the writeup of the second phase (buffer overflow on Linux x64) in this post: Hack.LU 2013 CTF Wannabe Writeup Part Two: Buffer Overflow Exploitation.

Downloads

Belgian. IT Security. Bug Bounty Hunter.

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *