This past weekend I had the pleasure of competing in the CSAW CTF alongside some very talented individuals. Our team ended up getting 57th overall, and placed 13th out of all qualifying teams, meaning that we get to send some Georgia Tech young’ns up to NYC for the finals!

Of all the challenges I attempted, I found Web 500 to be not only the most enjoyable but also the most realistic in terms of what I’ve seen in all the real web apps that I’ve tested. What follows here is a walkthrough of how we captured the Web 500 flag. Please note that while I did manage to capture some pictures during the competition, many of the pictures that follow are from saved Burp Suite state and look funky.

The Challenge

The Web 500 application was entitled “Weebdate,” and was a mock dating site that sported “superior security in the wake of the Ashley Madison hack.” The application used a Time-based One-time Password (TOTP) algorithm as a second factor of authentication for every account, meaning that to log in a user was required to supply a username, a password, and a value from their TOTP algorithm. The goal of the challenge was to compromise the TOTP secret and account password for Donald Trump’s Weebdate account, as he had been dabbling in undesirable things and we had to pwn him accordingly (or some such story)!

Local File Inclusion

Local file inclusion, or LFI, is a vulnerability where an application allows an attacker to read files that are on the same machine (ie: local to) as the application.

After we registered for an account, we poked around the Weebdate application and found the “edit profile” page at the following URL:

The edit profile page allowed users to (1) edit their profile tag line and (2) edit their profile picture. We messed around with the tag line editing and determined that the functionality wasn’t particularly interesting. We then turned our attention to editing the profile picture.

The edit profile picture field accepted a URL. After some testing, we determined that the application passed the URL to Python’s urllib.urlopen function, checked to see that the file was indeed an image, and set the user’s profile image field to that URL in the event that the image checked out. An HTML response for when a valid image URL was supplied to the field is shown below:

CSAW successful profile image upload
CSAW successful profile image upload

The interesting part about this functionality was what happened when the retrieved file was NOT an image, as shown in the picture below:

CSAW edit profile image failure
CSAW edit profile image failure

We had thrown a file containing the words “Hello there!” on a personal web server and passed the file’s URL to the application, resulting in the response shown above. This indicated that when the retrieved file was not an image, the file’s contents were dumped into the HTML response.

This looked promising, as Python’s urllib.urlopen function supported the “file” scheme. We then attempted to retrieve the /etc/hosts file from the server running the web application, as the application’s headers disclosed that it was running a version of Ubuntu.

We passed the following URL to the application as our profile image URL:

Part of the resulting HTML response is shown below:

CSAW profile image /etc/hosts LFI
CSAW profile image /etc/hosts LFI

Great success! Sure enough, passing file:// URLs to the edit profile image functionality allowed us to read files on the server. Taking another look at the application’s headers, we determined that the application was running on Apache:

HTTP response headers for CSAW Web 500
HTTP response headers for CSAW Web 500

Because the server was running Apache, we wanted to grab the Apache site configuration files. Luckily for us, the application was running under the standard “000-default.conf” configuration on the server.

We passed the following URL to the application as our profile image URL:

The server spit back the contents of the Apache site configuration file, and in the configuration file there was a reference to the following path:

We retrieved this file’s contents, which are shown below:

CSAW edit profile image WSGI contents
CSAW edit profile image WSGI contents

The file contained Python code which first added /var/www/weeb to the internal $PATH list, and then imported a variable named “app” from a module named “server”. This indicated that the server module might have resided in /var/www/weeb, and if it did it would be in a file named server.py. We then crossed our fingers and attempted to retrieve the file via the following URL:

It worked! We finally had access to the source code powering the Weebdate site, and boy oh boy was it vulnerable. The beginning of the server.py file is shown below:

Contents of server.py from CSAW Web 500
Contents of server.py from CSAW Web 500

As shown above, the server.py file imported something called “utils”. Another request to the file at /var/www/weeb/utils.py returned more code powering the server, the beginning of which is shown below:

Contents of utils.py from CSAW Web 500
Contents of utils.py from CSAW Web 500

Again, we needed to get the TOTP secret and password for Donald Trump’s account. Reviewing the code, we found the following functions in utils.py and server.py which were responsible for generating the TOTP secret:

Registration functionality for CSAW Web 500
Registration functionality for CSAW Web 500

The following function was responsible for creating a user’s record in the application database:

Register user function for CSAW Web 500
Register user function for CSAW Web 500

From all of the above, it was apparent that we needed to know the IP address that Donald Trump had registered from to generate his TOTP secret, and that the only place his password was stored was in the application’s database. We needed to find some way to dump that database…

SQL Injection

SQL injection, or SQLi, is a vulnerability that allows attackers to trick a database server into running database queries of the attackers’ choice.

We then found the following code in server.py:

Looking at the utils.py file, we found the utils.get_csp_report function:

Putting the two together, it was clear that the application was vulnerable to SQL injection at the /csp/view/<report_id> endpoint. We used sqlmap to dump the contents of the user table in the weeb database through the following command:

Sure enough, the donaldtrump account was one of the first accounts in the table:

Dumping users table for CSAW Web 500
Dumping users table for CSAW Web 500

Now all we needed was to generate the TOTP secret and crack his password hash.

TOTP Secret Generation

As was shown before, generating a user’s TOTP secret required the user’s username and registration IP address. Since we now had both of these pieces of information, I whipped up the following Python script to generate the TOTP secret based off the code found in server.py and utils.py:

The result of running the code is shown below:

Generated TOTP seed for CSAW Web 500
Generated TOTP seed for CSAW Web 500

We then devolved into raucous shouting and girly squeals. All that was left was to crack the password hash.

Password Cracking

From the register_user function shown above, it was apparent that the password hash stored in the application database was generated using both the account’s username and password:

Because of this, existing rainbow table lookup services wouldn’t offer us very much help. I wrote the following script to attempt to crack Donald Trump’s password hash using the top 10k most common passwords list:

The result of running the code is shown below:

Cracking the password hash for CSAW Web 500
Cracking the password hash for CSAW Web 500

Commence the raucous shouting and girly squeals once more.

Putting it All Together

The flag for the challenge was described on the CSAW competition site as follows:

Recognizing that this was a snippet of PHP code, I quickly put together my own local PHP script to execute this code with the inputs we had discovered:

The result of running the code is shown below:

CSAW Web 500 flag generation
CSAW Web 500 flag generation

Sure enough, this was the valid flag value. We shot and we scored +500 points which pushed us into the top 15 qualifying teams.

I hope you enjoyed this writeup! As always, feel free to drop me a line at @_lavalamp with any comments, questions, or just to chat!

L8z.