PHP object injection
Learn about a common, critical vulnerability in PHP applications
What is object injection?
Serialization is the process of turning objects into strings, which may be stored easily or passed between applications. Object injection occurs when serialized data stems from user input and is then unserialized in a way that causes unexpected/unwanted behavior in the application. In the worst-case scenario, object injection can result in remote code execution on the server that performs the deserialization.
We also have a lesson available for deserialization of untrusted data.
About this lesson
In this lesson, you will learn about vulnerabilities stemming from object injection and how to protect your applications against them. We will step into the shoes of a hacker who discovers a PHP object injection in a piece of popular open-source software, and uses it to exploit thousands of websites!
WordPress is the most popular CMS on the internet. Some reports say that it powers 37% of all websites! In June 2022, an object injection vulnerability was discovered in WordPress versions before 5.8.3, it was assigned CVE-2022-21663.
Sam is a talented blackhat hacker who wants to leave their mark on the Internet. In his pursuit of this, they sets their sights on a popular open-source content management system for birdwatchers, BirdPress. A critical vulnerability in BirdPress would allow them to hack into hundreds of web servers around the globe.
They download the source code of BirdPress and start trawling through thousands of lines of code, searching for vulnerabilities.
Let's take a look at the
Right away, they recognize this as an object injection vulnerability, and construct the following payload for their test instance:
Now, Sam can execute commands on the server by running:
How did this happen? What was the blob of text that resulted in a webshell being created? How did Sam bypass the
str_replace()? All will soon be clear! Read on!
User class, the developer has created a constructor function:
This constructor function ensures that the
. characters are stripped from usernames before being saved. The username then forms part of the filename where the user details will be backed up, in the form
The developer presumably added this
str_replace() to stop an attacker from performing any kind of directory traversal or storing file names on their system with executable extensions (like
.php). The good news is it would work… if the constructor function was ever called.
In this case, we are actually receiving an object that is already constructed in serialized form, so the constructor function is completely bypassed. When
unserialize() is called, the
__wakeup() magic method is called instead. The relevant code is shown below.
We can see that the serialized data is received from a
GET parameter, and then
$this->backupFile is used as the location of the backup file without any kind of validation or sanitization. Additionally, the other user attributes are printed to the file, so we could use those variables to store some PHP code (such as a webshell) that would be executed later.
Sam can construct their own nefarious, serialized payload using the code below:
This will result in the following output:
In order to send this payload in a URL, we need to encode some key characters, so the final payload ends up looking like this:
Once executed, a file is created in the public web root
/var/www/html/shell.php, which contains code that would allow an attacker to send shell commands to the server and have them executed.
What is the impact of PHP object injection?
The impact of an object injection is determined by the functionality that the application allows in magic methods. In some cases like the one above, object injection may result in full remote command execution. In other instances, object injection may be used to bypass access controls, and generate illegitimate session tokens, poison log files, and more.
In almost every case, serialization can be avoided, and it should be. If you absolutely must use serialization, the golden rule is never to accept serialized data from a user. Instead, the application should accept each piece of data as a parameter, and the objects can be constructed on the server side. Plain old JSON data or even just
GET/POST parameters are a tried and true alternative.
The objection injection vulnerability in this module could be remediated by accepting plain old HTTP parameters instead of a serialized blob, like this: