PHP Object Injection

By | 2020-08-19

Object Injection vulnerability is an application security vulnerability in PHP where user data is passed to the unserialize() function. An attacker exploiting Object Injection passes a malicious string to unserialize(), which injects an object into the application. In the worst case, this can lead to an appsec vulnerability allowing a remote code execution attack.

Object Injection Example in PHP

Let’s look at an example Object Injection vulnerability in PHP.

public function unserializeUserData(string $unescapedUserData) : void
    return unserialize($unescapedUserData);

In this example, unescaped user data is passed to PHP’s unserialize() function. If, for example, an attacker passed the string O:8:"stdClass":0:{}, they would cause a new stdClass object to be created by the serialiser. But what kind of object could be injected that might cause remote code execution?

The answer lies in either of two magic methods: __wakeup() and __destruct(). __wakeup() is called whenever an object is unserialize()‘ed. Whenever the Garbage Collector decides the object won’t be used again in the code, it invokes the __destruct() magic method. By injecting an instance of a class with either of these methods, the attacker can cause some code to be executed.

Let’s look at the possible consequences in the worst case. Imagine that this class exists somewhere in the application:

class ThingThatDoesCaching
    private string $cachingLocation = '/tmp/cache.txt';
    private string $cacheableData = '';


    public function __destruct()
        file_put_contents($this->cachingLocation, $this->cacheableData);

This class caches part of its state to disk. The significant aspect from a security perspective is that an attacker can now inject an object which executes code doing filework. Check out this malicious string, which could be passed to unserialize():

O:20:"ThingThatDoesCaching":2:{s:15:"cachingLocation";s:25:"/var/www/public/index.php";s:13:"cacheableData";s:25:"<?php echo "Hello world"";}

This is a serialize()‘ed instance of ThingThatDoesCaching, above. The $cachingLocation property is set to /var/www/public/index.php. This could have been any location on disk where PHP code can be executed by subsequent HTTP requests. The following payload is written to the file:

<?php echo "Hello world";

The attacker has now injected an object into the PHP application. When it is no longer being used, the Garbage Collector triggers __destruct() on the object. The injected object then writes arbitrary code to the system. An attacker can now send another HTTP request to the application, and will see Hello world printed to their browser. In the real world, the attack payload could have been a remote shell. The attacker has now gained full arbitrary code execution on the server.

For there to be an Object Injection vulnerability in the application, it requires two things:
– unserialize() being called on user data
– a class existing in the system which has a magic __destruct() or __wakeup() method defined.
The possible consequences depend on the contents of the relevant magic method.

History of Object Injection in PHP

The Object Injection attack vector was first discussed by Stefan Esser, an appsec researcher from Germany. Esser’s object injection talk was given at the Black Hat conference in 2010. At the time, PHP open source projects such as phpBB2 and Magento made use of the serialiser. There was a time when the PHPUnit codebase contained a class which performed filework in a __destruct() method, but this has since been removed.

Ruslan Habalov has since found other security issues with PHP’s serialiser.

Appsec Tips for PHP Developers

Avoid calling unserialize() on user data. Object Injection isn’t the only appsec vulnerability ever to affect PHP involving the serialiser, but there isn’t really anything that can be done in terms of patches in PHP internals that is going to fix this. It’s also worth avoiding using __destruct() or __wakeup() where possible, and make sure you never use them to do filework. You never know what else someone might add to your application later, and it is better from an appsec perspective not to leave these things lying around in your codebase. They could one day form a working attack vector.