DEV Community


Posted on

PicoCTF 2019: Cereal Hacker 2 (500p)

A couple of weeks ago, two of my friends and I participated in the yearly PicoCTF competition. As we're not students we participated in the global, open leaderboard and managed to climb to #112 out of 15817 participating teams (which was super exciting 🥳)

I thought I'd do some writeups on some of the challenges we solved.
So, first out:

Cereal Hacker 2

Category: Web Exploitation
Points: 500

Get the admin's password. or

Initial reconnaissance

When you first visit the linked webpage you're presented with this:

From the looks of it, a pretty regular login page for what , by the look of it, is supposed to look like your average corporate in-house application.

What information do we have this far?

  • The web server is either running PHP, or pretends to be running PHP.
  • We have access to a query parameter named file that seems to be pointing at the current php page.
  • Switching the file url to something else, like potato, produces the standard PHP 404 error message: Unable to locate potato.php
  • Inspecting the source (or DOM) yields nothing of particular interest
  • There are no client-side shenanigans in play

By knowing it's a php server, using the file parameter to render specific pages, is a great start and should help us in doing some additional enumeration.

Let's start by trying to figure out some of the available pages by just guessing common filenames for web pages and enter them as the value for the file key.

If we enter foot or head, the response is just an empty page, which means our "attack" actually does something and that indeed, these files seem to exist. Trying admin we get even closer to something tangible:



It's also possible to access the admin.php directly, without using the file parameter. However, this excludes all CSS, which confirms that the page is actually just rendered as a fragment or part inside index.php

Armed with this knowledge, it would be interesting to see whether we can do any path traversal or for that matter get the server to echo the raw php content.

Let's try traversals first:

Well, I expected as much. If that would have been possible, this problem wouldn't have deserved 500 points. And does not work either, too bad!

What if we encode the slash? The hex code for a forward slash is %2F, so let's try substituting the slashes with that:

Bingo! That renders a blank page instead of the usual message about the file not being found! So, it's actually allowing us to escape the web server root! But it's still not returning anything.

So, time to summarise our reconnaissance this far:

  • Path traversal using actual slashes are handled by the browser and are interpreted relative to the actual url path, not the file system path, which we're after.
  • Switching the slashes to their hex equivalent, %2F allows us to bypass this behavior and have the php engine interpret it instead, thus allowing us to do path traversal.
  • This exploit alone wont allow us to solve the challenge as it doesn't echo any file content. We need to dig further.
  • By removing one ..%2F at the time, we can deduce that we're three levels down from the root, as ..%2f..%2fetc%2fpasswd gives us the regular PHP file not found message.

Enter PHP Filters

If there where only a way to echo the raw php code back to the client... Turns out there is! There's a known vulnerability in php's local file inclusion allowing us to encode the content as base64 using the php://filter. This vulnerability has been around since PHP 5.0 and is described in detail here.

So, let's try that admin page again, but this time using php filter to encode it as base64:

Alt Text


Aha! Let's convert that to something a bit more.... uhm... readable. Other than the expected markup which we've already seen, we can find this sitting in the top:

  if(isset($perm) && $perm->is_admin()){
Enter fullscreen mode Exit fullscreen mode

That in and by itself won't get us anywhere, so we'll continue by looking at cookie.php



// I got tired of my php sessions expiring, so I just put all my useful information in a serialized cookie
class permissions
    public $username;
    public $password;

    function __construct($u, $p){
        $this->username = $u;
        $this->password = $p;

    function is_admin(){
        global $sql_conn;
            die('Could not connect');
        //$q = 'SELECT admin FROM pico_ch2.users WHERE username = \''.$this->username.'\' AND (password = \''.$this->password.'\');';

        if (!($prepared = $sql_conn->prepare("SELECT admin FROM pico_ch2.users WHERE username = ? AND password = ?;"))) {
            die("SQL error");
Enter fullscreen mode Exit fullscreen mode

Now how about that! Let's check the sql_connect.php file as well!

  $sql_server = 'localhost';
  $sql_user = 'mysql';
  $sql_pass = 'this1sAR@nd0mP@s5w0rD#%';
  $sql_conn = new mysqli($sql_server, $sql_user, $sql_pass);
  $sql_conn_login = new mysqli($sql_server, $sql_user, $sql_pass);
Enter fullscreen mode Exit fullscreen mode

And with that, we'll now be able to connect to the actual mysql database.
Let's fire up a shell and try it out:

Alt Text

Alt Text

And there we have it, folks - the flag!


If you enjoyed this write-up and would like me to continue writing more about CTF's and how me and my team solves them, let me know by smashing the heart button and following me.

Thanks for reading! 🙏🏼

Discussion (0)