Content-Type: text/plain

(Part of a series of writeups from HSCTF 2019.)

The challenge text reads:

md5-- == md4

Note: If the link above doesn't work, try

The challenge page is a PHP file that outputs its own contents.

$flag = file_get_contents("/flag");

if (!isset($_GET["md4"]))

if ($_GET["md4"] == hash("md4", $_GET["md4"]))
    echo $flag;
    echo "bad";

As it's very unlikely for the md4 hash of a string to be the same as the string itself, presumably PHP's type juggling system can be abused instead.

Even when comparing strings PHP will still convert them to other types if it feels like it. This is one of PHP's most surprising features.

php > var_dump('00000000000012345' == '12345');

They are both strings but they are still converted to integers and then considered equal. This behaviour can lead to some inadventently insecure code if not careful.

Here's more surprising behaviour.

php > var_dump("0e1111111111" == "0e2222222222");
php > var_dump((int)"0e1111111111", (int)"0e2222222222");

Those "strings" are actually zero as integers, which is how they end up being considered equal.

This means the input needs to be a string starting with 0e containing only numbers that hashes into a hash that also starts 0e and only contains numbers.

Brute forcing is the way to solve this one.

$i = 0;
$c = 0;

while (true) {
    if ((++$c % 1000000) == 0) {

    $n = "0e" . $i++;
    $h = hash('md4', $n);

    if ($n == $h) {
        printf("\nFound: $n\n");

A counter $i is increased each iteration, and the generated 0e-prefixed string is hashed and compared. The string starts at 0e1 and continues sequentially until a match is found. A dot is printed to the screen each million iterations to mark progress.

Running this and waiting for a bit soon yields the first correct input.

Found: 0e251288019

Inputting this into the challenge reveals the flag.