Main menu

Stripe CTF Round Up

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive

So I was following along twitter and found out about the Stripe CTF challenge. 

Basicly, you are given a bunch of Pentest type challenges and you are required to complete them to move forward. 

Here I'll go over each challenge, and describe what I did to solve the puzzle.

Keep in mind, alot of those hours was sleep, work and weekends with the family ;)

You'll find my write ups some time after the CTF closes here:

More information on the design of the back end systems can be found here:

Level 0

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive


We'll start you out with Level 0, the Secret Safe. The Secret Safe is designed as a secure place to store all of your secrets. It turns out that the password to access Level 1 is stored within the Secret Safe. If only you knew how to crack safes...
You can access the Secret Safe at The Safe's code is included below, and can also be obtained via git clone

This one was simple enough - the brunt of this one laid on line 34 of level00.js.

    var query = 'SELECT * FROM secrets WHERE key LIKE ? || ".%"';
Since there was no cleaning of variables, a simple query of % showed us everything in the "vault". 


Level 1

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive


Excellent, you are now on Level 1, the Guessing Game. All you have to do is guess the combination correctly, and you'll be given the password to access Level 2! We've been assured that this level has no security vulnerabilities in it (and the machine running the Guessing Game has no outbound network connectivity, meaning you wouldn't be able to extract the password anyway), so you'll probably just have to try all the possible combinations. Or will you...?
You can play the Guessing Game at The code for the Game can be obtained from git clone, and is also included below.


The trick here lied in lines 12-16. 


      $filename = 'secret-combination.txt';
      if (isset($attempt)) {
        $combination = trim(file_get_contents($filename));
        if ($attempt === $combination) {

The Key is the extract after the $filename is set. This allows us to over ride the $filename variable in the $_GET. As such, we can tell the file_get_contents to get any file we want. For example /dev/null which would be null. If we also provide $attempt with a null (or empty string) we're passed. 

Simple as that. 



Level 2

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive


You are now on Level 2, the Social Network. Excellent work so far! Social Networks are all the rage these days, so we decided to build one for CTF. Please fill out your profile at You may even be able to find the password for Level 3 by doing so.
The code for the Social Network can be obtained from git clone, and is also included below.


This was fun, you have to change your mind set to a different attack from before. Key lines were lines 9-16:


    $dest_dir = "uploads/";
    $dest = $dest_dir . basename($_FILES["dispic"]["name"]);
    $src = $_FILES["dispic"]["tmp_name"];
    if (move_uploaded_file($src, $dest)) {
      $_SESSION["dispic_url"] = $dest;
      chmod($dest, 0644);
      echo "<p>Successfully uploaded your display picture.</p>";


The key here lied in the fact there was no checking of what kind of file you uploaded. As such, anyone could say, upload C99 or some other PHP shell. Thus, you were able to access the text file. 

Level 3

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive


After the fiasco back in Level 0, management has decided to fortify the Secret Safe into an unbreakable solution (kind of like Unbreakable Linux). The resulting product is Secret Vault, which is so secure that it requires human intervention to add new secrets.
A beta version has launched with some interesting secrets (including the password to access Level 4); you can check it out at As usual, you can fetch the code for the level (and some sample data) via git clone, or you can read the code below.

Ah, fortify the safe. Here took a little more effort, but relied on good ol' SQLi. Check out lines 86-99:

     query = """SELECT id, password_hash, salt FROM users
               WHERE username = '{0}' LIMIT 1""".format(username)

    res = cursor.fetchone()
    if not res:
        return "There's no such user {0}!\n".format(username)
    user_id, password_hash, salt = res

    calculated_hash = hashlib.sha256(password + salt)
    if calculated_hash.hexdigest() != password_hash:
        return "That's not the password for {0}!\n".format(username)

    flask.session['user_id'] = user_id

Again, not a whole lot of parameter sanitisation. Since we can control what $username and password is, and further in the code we define what the user ID is from the SQL query, we can just tell the database to respond with whatever we wanted, maybe A UserID, the hash and salt of our choosing by using a UNION:

' UNION ALL SELECT 2, "3e4e4a24cbb115d3a5d07005d248beca2b0e623d7973cb04526855abe0315868", "jason"'

At this point, the database is going to return 2 for "id", the hash for "password_hash" and jason for "salt". Since we also control what the password will be, and we know the HASH is SHA256 we can calculate and populate as required. 


Level 4

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive


The Karma Trader is the world's best way to reward people for good deeds: You can sign up for an account, and start transferring karma to people who you think are doing good in the world. In order to ensure you're transferring karma only to good people, transferring karma to a user will also reveal your password to him or her.
The very active user karma_fountain has infinite karma, making it a ripe account to obtain (no one will notice a few extra karma trades here and there). The password for karma_fountain's account will give you access to Level 5.
You can obtain the full, runnable source for the Karma Trader from git clone We've included the most important files below.


This one was a blast to do. The key lied not in the code but the hints in the text above. Specificly:


The very active user karma_fountain has infinite karma


and this part of text:


In order to ensure you're transferring karma only to good people, transferring karma to a user will also reveal your password to him or her.


This turned out to be a bot that actually ran. Given the following bit of code (Lines 155-161):

      unless username && password
        die("Please specify both a username and a password.", :register)

      unless username =~ /^\w+$/
        die("Invalid username. Usernames must match /^\w+$/", :register)

You couldn't use persistant XSS on the username, but you could use it on the password which would be revealed to the bot when you sent it some karma. 

Thus, it was just a matter of imputing data into the form on the page, and submitting on behalf of the bot using JS XSS. ;)

Level 5

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive
Many attempts have been made at creating a federated identity system for the web (see OpenID, for example). However, none of them have been successful. Until today.

The DomainAuthenticator is based off a novel protocol for establishing identities. To authenticate to a site, you simply provide it username, password, and pingback URL. The site posts your credentials to the pingback URL, which returns either "AUTHENTICATED" or "DENIED". If "AUTHENTICATED", the site considers you signed in as a user for the pingback domain.

You can check out the Stripe CTF DomainAuthenticator instance here: We've been using it to distribute the password to access Level 6. If you could only somehow authenticate as a user of a level05 machine...

To avoid nefarious exploits, the machine hosting the DomainAuthenticator has very locked down network access. It can only make outbound requests to other servers. Though, you've heard that someone forgot to internally firewall off the high ports from the Level 2 server.

Interesting in setting up your own DomainAuthenticator? You can grab the source from git clone, or by reading on below.

This one seems simple at first. It's just a matter of using a machine on the domain - and we all know from Level 2 that we can upload any file we want, right? 

Ahh, but there's a catch - just uploading a file on Level 2 allowed us to authenticate as a Level 2 user, not level 5 like the text above said.

The key here was that the code at line 88-89 pushes back the exact text in the auth request:

        return "Remote server responded with: #{body}." \
               " Authenticated as #{username}@#{host}!"

Thus, we could in theory point the authentication pingback to the auth script which referenced the Level 2 server like so:

This then would authenticate as a Level 5 user, but using the Text from Level 02. 


Level 6

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive
After Karma Trader from Level 4 was hit with massive karma inflation (purportedly due to someone flooding the market with massive quantities of karma), the site had to close its doors. All hope was not lost, however, since the technology was acquired by a real up-and-comer, Streamer. Streamer is the self-proclaimed most steamlined way of sharing updates with your friends. You can access your Streamer instance here:
 The Streamer engineers, realizing that security holes had led to the demise of Karma Trader, have greatly beefed up the security of their application. Which is really too bad, because you've learned that the holder of the password to access Level 7, level07-password-holder, is the first Streamer user.
 As well, level07-password-holder is taking a lot of precautions: his or her computer has no network access besides the Streamer server itself, and his or her password is a complicated mess, including quotes and apostrophes and the like.
 Fortunately for you, the Streamer engineers have decided to open-source their application so that other people can run their own Streamer instances. You can obtain the source for Streamer at git clone We've also included the most important files below.

This one was a real PITA - not from a concept, but from the protection put into place. It again was a persistent CSRF/XSS, but in this case, you couldn't use any quotes - and notice the text, the "level07-password-holder" uses quotes in his password (punk). 

This site: made all the difference in getting past the first barrier. It helps you encode your CSRF code so that it bypasses the quote restriction - of course, since the password you're trying to get has quotes in it, so the easy way I got past that was just string substitute the ' and " into something like 5 and 6 - poof, password achieved. 


Level 7

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive
Welcome to the penultimate level, Level 7.
WaffleCopter is a new service delivering locally-sourced organic waffles hot off of vintage waffle irons straight to your location using quad-rotor GPS-enabled helicopters. The service is modeled after TacoCopter, an innovative and highly successful early contender in the airborne food delivery industry. WaffleCopter is currently being tested in private beta in select locations.
Your goal is to order one of the decadent Liège Waffles, offered only to WaffleCopter's first premium subscribers.
Log in to your account at with username ctf and password password. You will find your API credentials after logging in. You can fetch the code for the level via
git clone, or you can read it below. You may find the sample API client in particularly helpful.

This one was a pain - not for the attack, but the fact that this was the first one I actually downloaded the code - and somehow, it messed up. Meaning I was banging my head against a wall, even though a re-download of the code cleared it. 

Anyway, There was two tricks to solving this one. When you made a few requests, you could see them in your own personal log of API requests. 

Trick one: You could also see everyone else's by just changing the User ID in the request - DOH!. 

This allowed you to see the VALID API requests and SHA1 signatures of those requests. 

Trick Two: SHA1 - there is an attack for that - Called a SHA1 Padding attack:

Here, go read up on that: I'll wait. 

Back? Good.

So basically, If you know a valid signature and known request, you can pad the known request to give the known signature. Simple. The attack happens by Setting the SHA1 registers to this known signature, and continuing the SHA1 hash from there giving you the correct hash for your attack. 

Now we could code this up, or google and find this: this not only desribes the attack again, but gives us a tool too. 

- You know the key length because you're given a key in the text.
- You know an original message and signature by using Trick 1
- You just need to add the text to append, the new waffle type.



Other Stripe CTF Write-ups

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive

There has been a few other write-ups for the CTF and as I find them, I'll add them here:

Ryan Linn @ Spider Labs -
droogie @ IOActive - 

I Have to highlight one particular young kid, Luke GB - Not only was he able to complete the CTF, but also worked with danopia to build a Bonus Level 8 Speed Round: -- He also has one of the fastest scripts to solve Level 8 which can be found here along with his notes on the CTF: