Link Search Menu Expand Document

Unsafe Deserialization in PHP

PHP uses serialize() and unserialize() native functions to serialize and unserialize an object. For example, the following script creates an instance of the object FSResource, serializes it, and then prints the string representation of the object.

<?php

class FSResource {

    function __construct($path) {
        $this->path = $path;
        if (file_exists($path)) {
          $this->content = file_get_contents($path);
        }
    }

    function __destruct() {
        file_put_contents($this->path, $this->content);
    }

}

$resource = new FSResource('/tmp/file');
print(serialize($resource));

# Prints the following string representation:
# O:10:"FSResource":2:{s:4:"path";s:9:"/tmp/file";s:7:"content";s:0:"";}

The string representation can then be deserialized again to recreate the object instance and access its attributes.

$instance = unserialize('O:10:"FSResource":2:{s:4:"path";s:9:"/tmp/file";s:7:"content";s:0:"";}');
print($instance->path);

# Prints the path attribute:
# /tmp/file

Vulnerable Example

The exploitation of deserialization in PHP is called PHP Object Injection, which happens when user-controlled input is passed as the first argument of the unserialize() function. This is a vulnerable script.php:

<?php

$instance = unserialize($_GET["data"]);

To be exploitable, the vulnerable piece of code must have enough PHP code in scope to build a working POP chain, a chain of reusable PHP code that causes a meaningful impact when invoked. The chain usually starts by triggering __destroy() or __wakeup() PHP magic methods, called when the object is destroyed or deserialized, in order to call other gadgets to conduct malicious actions on the system.

If the class FSResource defined in the paragrah above is in scope, an attacker could send an HTTP request containing a serialized representation of an FSResource object that creates a malicious PHP file to path with an arbitrary content when the __destruct() magic method is called upon destruction.

http://localhost/script.php?data=O:10:%22FSResource%22:2:{s:4:%22path%22;s:9:%22shell.php%22;s:7:%22content%22;s:27:%22%3C?php%20system($_GET[%22cmd%22]);%22;}

The payload above decodes as O:10:"FSResource":2:{s:4:"path";s:9:"shell.php";s:7:"content";s:27:"<?php system($_GET["cmd"]);";} and, when deserialized, it creates the shell.php allowing the attacker to run arbitrary commands on the systems. More complex payloads can be built by chaining code from multiple classes or reusing public POP chains such as the ones included in the PHPGCC projects.

Prevention

Never use the unserialize() function on user-supplied input, and preferably use data-only serialization formats such as JSON. If you need to use PHP deserialization, a second optional parameter has been added in PHP 7 that enables you to specify an allow list of allowed classes.

References

Wikipedia - Serialization PHP - unserialize OWASP - PHP Object Injection POC 2009 - Stefan Esser - Shoking news in PHP exploitation BlackHat USA 2010 - Stefan Esser - Utilizing Code Reuse in PHP Application Exploits PHPGCC