Hacking Google CTF - Episode 0

Hacking Google CTF - Episode 0

ยท

9 min read

Yesterday, I found out about the H4CK1NG GOOGL3 security CTF on Hackernews. It is a gamified set of hacking challenges. In each challenge, you are presented with a piece of software or system that can be used to access some secret "flag" in its closed infrastructure.

I had never participated in a CTF (Capture The Flag) before but this one seemed like a good opportunity since it did not have a deadline and the challenges are more about typical security issues than finding some exploits in very involved algorithms. That being said, I am by no means a security expert. However, I always found this topic very interesting and once found a severe but interesting security flaw in a production web application (I might publish a story about this eventually).

This blog post outlines how I solved the challenges together with a friend. If you haven't solved them yet for yourselves, I would not recommend reading the whole article - however, I will provide some more hints at the beginning of each chapter. If you don't have the time to try your luck in solving these challenges yourself, you might find this article interesting. I am not sure whether I find the time (and have the skills) to solve all challenges, but after 6 hours I was able to finish episode 0, which contains the two challenges presented in this article.

Challenge 01: Hacker Chess

The hacker chess website

Hints:

  • You don't have to play chess very well (I don't)
  • Don't get distracted
  • Level the playing field

The first challenge is presented as a website where one can play chess against some AI. The first thing to catch my eye was the "Master Login" button in the lower right corner that jumps into the eye. Could this be the classic SQL injection scenario?

But let's first have a look at the chess game itself: At the beginning, the game plays like normal, but at move seven, something weird happens:

The chess AI cheats at the game

All enemy pawns somehow became queens - the AI is cheating! As if I had a chance against a chess AI anyways... At this point, it became quite obvious that I was not able to beat the opponent without some help.

Let's hit "F12" and inspect the HTML source code of this website. I made the following notes:

  • The chessboard is made of an HTML table... ๐Ÿ™ƒ
  • Clicking on the table cells calls the current page with the parameter ?move_start=<clicked cell>
    • E.g. clicking on square E3 will open the page ...com/index.php?move_start=e3
    • This page then highlights the allowed moves and when clicking another square will something like ...com/index.php?move_end=YToyOntpOjA7czoyOiJkMiI7aToxO3M6MjoiZDQiO30
  • There is a difficulty switch... Options are "Impossible", "Unbeatable" and "Invincible"
    • Not sure which is the easiest, but all settings seem to result in pretty good moves
  • The game loads automatically, but there is also a "Start" button
    • This button executes the load_baseboard() JavaScript function
    • This function makes a request to load_board.php passing a filename(baseboard.fen) in the request and reloads the page

Chess Piece Teleportation

So many possibilities! First I tried the obvious: Telling the backend to teleport my chess pieces so that I can checkmate the enemy! For that, I needed to understand how the ?move_end=... parameter works.

The value of the move_end parameter looked suspiciously like a base64 encoded string, since it only contained characters and numbers and is often used in URL parameters. Decoding the value results in a:2:{i:0;s:2:"d2";i:1;s:2:"d4";}, which contained my move (from square D2 to D4). Since it does not look like JSON, my initial guess was that this is some sort of array data structure. Later, I found out that this guess was correct and this is apparently how PHP serializes its data structures.

I tried changing the values in that string to teleport my chess pieces, but the chess app detected that as an illegal move.

Setting a custom starting board... or not?

Remember the load_board.php endpoint? After some tinkering, we found that you can actually call this one by setting the file value to baseboard.fen at any time to generate a new PHP session. This PHP session is identified by a PHPSESSID cookie! My friend also recognized the .fen extension as a file format that is used to specify chess boards.

So what if we could load a different .fen file where we are a few moves away from winning the game? We somehow have to inject our custom file into the filesystem somehow... Maybe you can guess the solution? We could not figure that one out just yet! But we discovered something even better: A LFI vulnerability! When calling the load_board.php endpoint with a GET request, the endpoint actually returns the loaded data in its body. But we could also specify index.php as file (or any other file in the local file system) which would print the raw source code of the index.php file!

This revealed a couple of interesting things:

  • The AI cheating can be turned off by admins
  • The difficulty setting is only a distraction and does not do anything in the backend
  • The script uses the chess PHP library together with the stockfish chess engine
  • The flag is stored in an environment variable and revealed as soon as the player wins the game: echo "<h1>ZOMG How did you defeat my AI :(. You definitely cheated. Here's your flag: ". getenv('REDIRECT_FLAG') . "</h1>";

And yes, we could now also print the source code for the admin.php file:

  • The login is susceptible to SQL injection
  • Admins can turn off cheating and change the thinking time of the AI

Hacking the Master Login

We now knew that we did not have to hack the admin page, since you only have to win the game in order to obtain the flag. However, it seemed like turning off cheating and changing the thinking time can help us win the game.

the login form

The master login on the admin.php page was a simple username and password login page. From our previous exploit, we now know that the login is vulnerable to SQL injection attacks:

sprintf("SELECT username FROM chess_ctf_admins WHERE username='%s' AND password='%s'", $_POST['username'], $_POST['password']);

After fiddling around with some attack attempts we were able to log in by entering ' or ''=' as username and password.

the admin interface

While turning off cheating helped quite a bit, the thinking time parameter did not seem to do much for us. According to the documentation of the stockfish engine, setting it to -1 is supposed to turn off thinking altogether and just use the next best move. While this might actually be the case, I was still too bad at chess to beat the AI (however, I heard from others that without the cheats they were able to win the game and get the flag).

Custom starting boards... This time for real!

After some thinking, a really funny thought came to my mind. What if the file loading functionality of the load_board.php script actually supports loading from URLs? Might be worth a shot. And indeed, looking at the source code we see this:

if (isset($_POST['filename'])) {
  $fen = trim(file_get_contents($_POST['filename']));
  # XXX: Debug remove this
  echo 'Loading Fen: '. $fen;
}

Thanks to Mr. XXX, who forgot to remove the debug code, we were able to see the source code of this function in the first place. But now, we can actually validate that this script uses file_get_contents, which supports loading from URLs.

So to actually attempt this hack, we used this tool to build our own .fen file. This was then uploaded to an anonymous pastebin and put into the filename parameter. Now by calling this script with the aforementioned parameters, we initialize a new session. When we now load the index.php file with the session cookie from load_board.php we are presented with our custom board!

Interestingly we weren't able to beat the AI: As soon as we set the opponent "mate" (so that his king cannot move anymore), the AI didn't react anymore but we also didn't get any winning message. Turns out, I didn't quite remember what a checkmate was. Luckily the chess PHP library is pretty well written and explains the conditions very well:

    public function inCheck(): bool
    {
        return $this->kingAttacked($this->turn);
    }

    public function inCheckmate(): bool
    {
        return $this->inCheck() && \count($this->generateMoves()) === 0;
    }

I actually set the opponent in a stalemate, which is not handled in the code. After adding additional pieces to our starting board so that we are attacking the opponent's king, the winning message containing the flag (a URL) popped up!

Later we discovered that we just could have used the load_board.php script to extract the flag from the /proc/self/environ file.

Challenge 02: Aurora

the search engine

Hints:

  • Look at the code
  • You only need one file

The second challenge of episode 00 presents a search engine that can be used to see the lines of several pre-determined logfiles that contain keywords (consisting of at least four characters).

A quick look into the HTML source code reveals a JavaScript function that performs GET requests in the form of: https://aurora-web.h4ck.ctfcompetition.com/?file={filename}&term={term}.

An interesting comment in the last line of that file also catches my eye: <!-- /src.txt -->. This leads us to the source code of the backend, which is written in Perl: aurora-web.h4ck.ctfcompetition.com/src.txt

I have zero experience with Perl, so let's hope that we do not have to find an exploit in the code itself! My first assumption (and hope) was that I could exploit the search tool to find some information about the system. I even wrote a small script to search important keywords through all files. But after spending way too much time slowly revealing the contents of the files, I gave up (and should have sooner).

Another friend of mine actually gave me a hint:

The Perl open() function is very powerful.

Turns out the open function can be used to execute Linux commands! Oh, dear a RCE challenge!

With that info, I now could run any command on that system (well not quite, Google designed the challenge with care, and only some stuff actually works)! So let's have a look at the file system with ls:

We still need to make sure that the response contains four characters that contain our search term. This is easy: We just echo a string, which we then search for: echo owned: $(ls).

Now to execute it in the open() command we need to wrap it in some Perl syntax: ; echo owned: $(ls /) |. After encoding this with the JavaScript escape() function, we are ready to go! The get request looks like this:

GET https://aurora-web.h4ck.ctfcompetition.com/?file=%3B%20echo%20owned%3A%20%24%28ls%20/%29%20%7C&term=owned

Resulting in:

owned: bin boot dev etc flag home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var web-apps

Wait a minute! /flag is not typically found on a Linux file system! This flag file contains a URL that leads to the hacking challenge's website and marks the challenge as completed.

Conclusion

This was a very fun challenge! It's well-designed - with a lot of distractions and hints. But this is only the first episode of five, containing three challenges each. You can read my write-up for the next episode over here.

image.png

Episode Overview:

ย