On se rend vite compte que la vérification de qui gagne se fait par une requête ajax.
function send() { var ajax = $.ajax({ type: "POST", url: "ajax/send.php", data: "card="+$('.card:last').attr("alt")+"&sign="+$('.card:last').attr("id") }); ajax.done(function(rep) { $('.card > img:last').remove(); var json = eval('(' + rep +')'); $('#score').html('Score : ' + json.score); $('.cheater_card').html('<img alt="'+json.adv_card+'" src="img/'+json.adv_card+'.png">'); if ($('.card > img').length == 0) { $(location).attr('href','end.php'); } $('.resultat').html(json.msg); }); }
Chaque carte possède un token pour la valider côté serveur. On remarque assez facilement que ce token est généré en fonction du nom de la carte. Par exemple, le token du 5 de coeur est NHGrjegtFmHu2
, son nom est 05HEART
.
php > var_dump(crypt("05HEART","NH")); string(13) "NHGrjegtFmHu2"
Il nous faut trouver une carte qui pourrait battre l'as de pique du bot. Lorsqu'on envoie n'importe quoi comme nom de carte, on a comme réponse {“msg”:“Error : Unknown card”}
. Il y a donc une vérification sur le nom de la carte avant de vérifier le token.
La carte qui viendrait à l’esprit de tout le monde est bien évidemment le Joker. On met JOKER
comme nom de carte et on reçoit comme réponse {“msg”:“Error : sign not match”}
. Nice, la carte que l'on cherche est bien JOKER
!
php > var_dump(crypt("JOKER","NH")); string(13) "NHMTkwCoDsRC6"
On renvois la requête qui va bien
POST /ajax/send.php HTTP/1.1 Host: 109.232.238.5:8001 User-Agent: Mozilla/5.0 Gecko/20100101 Firefox/13.0.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive Cookie: PHPSESSID=2sk41kuhqosdg27ikft6j2h0a3 Content-Type: application/x-www-form-urlencoded Content-Length: 29 card=JOKER&sign=NHMTkwCoDsRC6
Et … on choppe un {“msg”:“Error : sign not match”}
…
Si c'est pas le bon token, c'est que des caractères ont été rajoutés à la fin de JOKER
. On remarque que l'on peut ajouter autant de caractère que l'on veut derrière une carte classique, le token sera toujours le bon. La taille des noms des cartes classiques sont tous de la même taille : 7 chars. JOKER
fait que 5 chars! Donc 2 chars ont été rajoutés derrière! Il ne nous reste plus qu'à tester toutes les possibilités (pour cela, python!)
#!/usr/bin/env python # encoding: utf-8 import sys import httplib from crypt import crypt def main(): headers = {"Cookie":"PHPSESSID=2sk41kuhqosdg27ikft6j2h0a3;", "Content-Type":"application/x-www-form-urlencoded"} card = "JOKER" for char1 in range(31,127): for char2 in range(31,127): conn = httplib.HTTPConnection("109.232.238.5:8001") conn.request("POST","/ajax/send.php","card={card}&sign={0}".format(crypt(card+chr(char1)+chr(char2),"NH"),card=card),headers) rep = conn.getresponse() html = rep.read() if html.count("Error") < 1: print crypt("JOKER"+chr(char1)+chr(char2),"NH") print chr(char1)+chr(char2) print char1, char2 sys.exit(0) else: print html print chr(char1)+chr(char2) if __name__ == '__main__': main()
$ ./warcrypt.py {"msg":"Error : sign not match"} ... {"msg":"Error : sign not match"} EP ... NH3J7Hldb9sQg P4
Well! Les deux caractères ajoutés après JOKER
sont P4
!
On se débrouille pour avoir un score de 27 (27 cartes gagnées) et on se rend sur end.php
(redirection automatique normalement) et on choppe le flag
WIN the flag is H0UR4P0URL3C4SS0UL3T
On valide avec md5(“H0UR4P0URL3C4SS0UL3T”) = 9b808110b3986a27019f2e153998e1e6