[BreizhCTF 2k18] Write-Up – Web : Mr. WorldWide

23
avril
2018
  • Google Plus
  • LinkedIn
  • Viadeo
Posted by: Yann C.  /   Category: BreizhCTF / Cryptologie / CTF / Events   /   Pas de commentaire

Présentation d’un write-up de résolution du challenge « Web – Mr. WorldWide » de la BreizhCTF 2018.

Durant la nuit du 20/04/2018 se déroulait la BreizhCTF 2018 sous forme d’un CTF Jeopardy. Ayant eu l’occasion et le temps d’y participer avec quelques collègues et amis, voici un write-up de résolution d’un des challenges.

  • Catégorie : Web
  • Nom : Mr. WordlWide
  • Description : Hire me muthafucka!
  • URL : https://148.60.87.243:25000
  • Points : 300

tl;dr; : Générer un « magic hash » sur la base de hash_hmac(« md5 ») pour exploiter une file disclosure.

Ce challenge nous a résisté durant de longues heures. Alliant web et crypto, nous avons dû employer du brute-force pour calculer des « magic hash ». Notre équipe a été la seule à réussir celui-ci jusqu’à la fin de du CTF :

MrWorldWide

MrWorldWide

En se rendant à l’URL principale, nous tombions sur le CV internationalisé / multilingue de Oussama Fayrir :

CV

CV

Ce CV s’avérait plutôt statique. En analysant la source, quelques commentaires attirèrent notre attention en bas de page :

Comment

Comment

Ces commentaires nous révèlent la présence de 3 variables GET avec lesquelles nous allons pouvoir jouer / interagir avec la cible :

  • path : un chemin vers un répertoire (de langue) ;
  • secret : une clé qui nous est encore inconnue ;
  • lang : la langue courante de traduction (fr, en…).

Clairement, « path » et « secret » sont liés (appel au sein d’un même traitement), et « lang » influe sur un hash qui sert à la comparaison.

On note également d’après ces commentaires, que le système d’internationalisation de la langue n’est pas encore au point / achevé ; et qu’une faiblesse de sécurité semble s’y trouver.

En jouant avec des valeurs arbitraires sur ces variables GET on obtient des hashs dans le code source. Hash de 32 caractères, surement du MD5 :

Hash generation

Hash generation

La langue « fr » nous génère le hash « fab2987fd25eae8a126c4f38eb3dc5e8 » et ce hash semble comparé au hash d’une combinaison de « path » avec « secret ». Hum.

Ce n’est pas un simple appel à la fonction « md5() » de PHP, car le hash md5 de la chaîne « fr » diffère de celui qui nous est retourné. Très certainement du « hash_hmac() » MD5. En typant nos variables GET en tant que tableaux, il est possible de produire un Full Path Disclosure (FPD) et de révéler la fonction utilisée via un Warning PHP :

FPD hash_hmac

FPD hash_hmac

Bingo, c’est bien du hash_hmac qui est employé. Petit rappel du prototype :

string hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] )

En conséquence, on peut s’attendre à ce qu’une comparaison de hash de type hmac-md5 soit réalisée et que nous devons satisfaire cette condition avant d’aller plus loin :

hash_hmac("md5", $_GET["path"], $_GET["secret"]) == hash_hmac("md5", $_GET["lang"], $SECRET_LANG)

Nous avons la main sur la variable GET « path » et « secret » du premier appel à hash_hmac() (supposition), ainsi que sur la valeur (lang) du second appel sans pour autant connaître le secret associé.

Essayons de jouer avec la variable « lang » :

Arbitrary lang

Arbitrary lang

Arf… Nous n’avons pas tant de liberté que ça sur la valeur de « lang ». Bon, il nous faut donc trouver quelle langue utiliser parmis celles disponibles dans la page. Afin de valider la condition de comparaison des deux hash_hmac() précédents, nous partons sur l’hypothèse qu’une simple comparaison « == » est en place et non pas une vérification du type « === ». En conséquence, une faiblesse de type « PHP type Juggling » est très certainement présente.

Pour rappel, ce type de vulnérabilité porte sur l’opérateur « == » de PHP, un peu laxiste en termes de comparaison puisque PHP va essayer de typer lui-même certaines valeurs. Ce qui se traduit par des égalités atypiques, par exemple les chaînes suivantes sont égales :

"0e1337" == "0e7331"

La notation sous forme de string « 0e » suivie de chiffres correspond à la notation scientifique d’un nombre. En tant que chaîne de caractère, PHP va donc convertir à la volée cette chaîne en nombre et la condition de comparaison sera valide. L’idée est donc de trouver un hash de « lang » respectant ce format « 0eXXXXXXXX […] XXXXXXXXXX ».

Commençons par extraire toutes les langues supportées par l’applicatif :

All lang...

All lang…

Automatisons la génération de tous les hashs correspondant via Burp et l’application :

Magic hash

Magic hash

YES ! Une des langues proposées dans l’application, la langue « mni« , génère bien un « hash magic » via l’appel de hash_hmac. Ce hash est bien au format « 0e » suivi de chiffre uniquement :

hash_hmac("md5", "mni", $SECRET_LANG) => 0e010787602300372264769241728411

Le chemin à emprunter pour résoudre ce challenge se précise, un tel « magic hash » n’est pas là par hasard.

Lang mni magic hash

Lang mni magic hash

Nous avons donc très certainement la bonne valeur de « lang » pour générer le magic hash. A présent, il nous faut trouver la bonne combinaison de valeur « path » et « secret » qui nous génère également un magic hash au format « 0e » suivi de chiffre uniquement, ainsi la combinaison initiale sera respectée :

hash_hmac("md5", $_GET["path"], $_GET["secret"]) == hash_hmac("md5", $_GET["lang"], $SECRET_LANG)

La source et notamment les message d’erreur nous affichent un joli « Are you trying to LFI??? » nous permettant d’émettre l’hypothèse que si l’égalité des hashs est respectée, un appel à include(), require() (_once), file_get_contents() ou équivalent sera réalisé par la suite.

Cherchons une valeur d’intérêt pour la variable « path », et calculons la valeur de « secret » correspondante pour générer un magic hash :

  • path=index.php (nous souhaitons récupérer le code source de cette page)
  • secret=??? (brute-force oblige…)
  • lang=mni (magic hash)

Fonction de brute-force (quick-n-dirty) :

<?php
$maxLength = 20;
$charSet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_';
$size = strlen($charSet);
$base = array();
$counter = 0;
$baseSize = 1;
$found = false;
$path = "index.php";

while($baseSize <= $maxLength && !$found) {
 $found=false;
 for($i=0;$i<$size;$i++) {
 $base[0] = $i;
 $str="";
 for($j=$baseSize-1;$j>=0;$j--) {
 $str.=$charSet[$base[$j]];
 }
 $hm=hash_hmac('md5', $path, $str);
 if ($hm=="0" && !$found){
 $found=true;
 echo "[+] YEAHHHH ! path=$path, secret=$str, hash_hmac_MD5=$hm";
 }
 }
 for($i=0;$i<$baseSize;$i++) {
 if($base[$i] == $size-1) $counter++;
 else break;
 }
 if($counter == $baseSize) {
 for($i=0;$i<=$baseSize;$i++) {
 $base[$i] = 0;
 }
 $baseSize = count($base);
 }
 else {
 $base[$counter]++;
 for($i=0;$i<$counter;$i++) $base[$i] = 0;
 }
 $counter=0;
}
?>

On démarre le brute-force et patientons (quelques minutes) :

Brute-force index.php

Brute-force index.php

Jackpot, un magic hash a été déterminé :

hash_hmac("md5", "index.php", "4PrOK") == hash_hmac("md5", "mni", $SECRET_LANG)
"" == "0e010787602300372264769241728411"
bool(true)

Générons l’URL et observons le résultat :

https://148.60.87.243:25000/?path=index.php&secret=4PrOK&lang=mni
Index.php with secret

Index.php with secret

Huhu, la condition est ok et le code PHP de l’index semble fuiter, consultons le depuis la source :

index.php source code leak

index.php source code leak

Nos hypothèses se confirment d’après le code source de l’index. Nous sommes bien en présence de deux fonctions hash_hmac() MD5 et le secret utilisé pour l’appel avec la variable « lang » est révélé « aabckklmoo« . C’est bien une simple comparaison « == » qui est visible entre les deux hashs, et si celle-ci est valide, un « file_get_contents() » du « path » indiqué (dans /var/www/html) est réalisé.

Très bien. Nous avons notre magic hash de la langue « mni« , nous avons le code source de l’index.php, nous avons notre « brute-forceur » (chronophage) pour déterminer le secret associé au « path », et nous avons notre exploitation de « file_get_contents() » pour récupérer des codes sources et contenu de fichier. Mais quel fichier inclure ?

Une multitude d’essais infructueux a été fait :

  • flag.php
  • Flag.php
  • FLAG.php
  • bzhctf.php
  • .htaccess
  • .htpasswd
  • envoie.php (référence à ce fichier dans un JavaScript chargé dans l’application)
  • Bazinga.php (puisque la photo du CV faisait référence à Sheldon Cooper)
  • […]

Aucun de ces fichiers ne semblaient exister. Revenons-en aux bases, chargeons le « /etc/passwd » :

  • path=../../../etc/passwd (remontée de répertoire puisque dans /var/www/html de base) ;
  • secret=??? (brute-force oblige…) ;
  • lang=mni (magic hash).

Après encore quelques minutes de brute-force, on récupère le secret correspondant permettant de générer un magic hash :

Brute-force /etc/passwd

Brute-force /etc/passwd

hash_hmac("md5", "../../../etc/passwd", "dqfdJ") == hash_hmac("md5", "mni", $SECRET_LANG)
"0e521691631272858454605463704898" == "0e010787602300372264769241728411"
bool(true)

URL :

https://148.60.87.243:25000/?path=../../../etc/passwd&secret=dqfdJ&lang=mni
/etc/passwd

/etc/passwd

/etc/passwd Burp

/etc/passwd Burp

YEAH ! Flagued !

bzhctf{Bru73f0rC1n9_hm4c_15n7_4_pUss35}

Chapeau à Estelle, Martin, Charles et Timothée pour celui-ci 🙂

Merci à toute l’équipe de la BreizhCTF pour l’organisation et la qualité des challenges !

Salutations à toute l’équipe, on remet ça quand vous voulez 😉 // Gr3etZ

  • Google Plus
  • LinkedIn
  • Viadeo
Yann C.

About the Author : Yann C.

Consultant en sécurité informatique et s’exerçant dans ce domaine depuis le début des années 2000 en autodidacte par passion, plaisir et perspectives, il maintient le portail ASafety pour présenter des articles, des projets personnels, des recherches et développements, ainsi que des « advisory » de vulnérabilités décelées notamment au cours de pentest.