Description
This site was created in honor of harambe: http://problems.ctfx.io:7003
Problem author: omegablitz
Resolution
Les sources de l’épreuve sont à disposition, l’application permet de créer un compte et de récupérer les informations d’un compte existant si on connait son username et son password.
Le code source nous montre qu’a priori il n’est pas possible d’y avoir un compte disposant du même username :
for(User user : User.users) { if(user.getUsername().matches(username)) { return "FAILED: User with that name already exists!"; } }
Après un test on se rend compte que l’enregistrement avec un même username est possible, aucune erreur n’est levée.
Voyons voir la suite du code :
new User("[Member] " + username, password, realName);
et
public User(String username, String password, String realName) { this.username = username; this.master = password; this.realName = realName; users.add(this); }
Ici on voit que le username est transformé en « [Member] <username> » et non pas <username> puis enregistré tel quel dans la base de données, ce qui explique pourquoi nous n’avons jamais de collision.
Pour obtenir une collision, il faudrait demander comme username « [Member] <username> » après s’être déjà enregistré avec <username>.
Et maintenant, on fait quoi ? Un petit tour dans la partie du code s’occupant de la récupération du realname :
for(User user : User.users) { if(user.verify(username, password)) { return user.getRealName(); } }
et
public boolean verify(String username, String password) { return this.username.equals(username) && this.master.matches(password); }
Attendez, c’est quoi cette habitude d’utiliser .matches() au lieu de .equals() ? On a déjà vu ça dans la vérification de username existant et maintenant ici.
.matches() prenant en paramètre une regexp, cela veut dire qu’en passant « .* » comme password, nous pouvons utiliser celui que nous voulons et bypass la vérification. Il reste maintenant à trouver le username associé au realname intéressant.
On sait que l’on peut s’enregistrer qu’avec un username qui n’existe pas déjà et que celui-ci est vérifié via une… regexp, éssayons de nous enregistrer avec comme username \[Admin\] .*. Oh, le pseudo est déjà pris ? 🙂
Quelques manipulations manuelles nous ont permi d’en déduire que le username faisait 15 caractères, place au bruteforce pour le trouver :
<?php function test($username) { return shell_exec("curl --silent --data 'username=".rawurlencode($username)."&password=xyz&real_name=test' 'http://problems.ctfx.io:7003/users'"); } $chars = implode(range('a','z')); $chars .= strtoupper($chars); $len = strlen($chars); $buf = ''; for ($i = 0; $i < 15; ++$i) { for ($j = 0; $j < $len; ++$j) { echo "Trying {$chars[$j]}\n"; $ret = test('\[Admin\] '.$buf.$chars[$j].str_repeat('.', 14-$i)); if (preg_match('#^FAILED#', $ret)) { $buf .= $chars[$j]; echo "Found {$chars[$j]}\n"; continue 2; } } } echo "Final : $buf\n";
Au bout de quelques secondes, le username tombe :
Il ne reste plus maintenant qu’à récupérer le realname de [Admin] Arxenixisaloser via notre navigateur favori en utilisant le password passe-partout « .* » :
Le flag est : ctf(h4r4mb3_d1dn1t_d13_4_th1s_f33ls_b4d)