PHP Deserialization
Last updated
Last updated
The native methods for PHP serialization are serialize()
and unserialize()
. If you have source code access, you should start by looking for unserialize()
anywhere in the code and investigating further.
PHP uses a mostly human-readable string format, with letters representing the data type and numbers representing the length of each entry. For example, consider a User
object with the attributes name
and isLoggedIn
:
The basic structure of a PHP serialized string is data type:data
. In terms of data types:
b
: boolean
i
: integer
d
: decimal (float)
s
: string
a
: array
O
: object instance of a particular class
Some of these types get followed by additional information about the data, as described here:
The output of our example code is:
Following the rules, we can interpret the serialized data:
O:4:"User"
- An object with the 4-character class name "User"
2
- the object has 2 attributes
s:4:"name"
- The key of the first attribute is the 4-character string "name"
s:6:"carlos"
- The value of the first attribute is the 6-character string "carlos"
s:10:"isLoggedIn"
- The key of the second attribute is the 10-character string "isLoggedIn"
b:0
- The value of the second attribute is the boolean value false
If the serialized object isn't encrypted or signed, anyone can create a customized User
object.
One possible way of exploiting a PHP object injection vulnerability is by manipulating variables in the object. In the above example, we have $user->isLoggedIn = false
. We can create our own User object and set $user->isLoggedIn = true
:
Most languages that frequently suffer from insecure deserialization vulnerabilities have equivalent proof-of-concept tools. For example, for PHP-based sites you can use "PHP Generic Gadget Chains" (PHPGGC).
It is important to note that the vulnerability is the deserialization of user-controllable data, not the mere presence of a gadget chain in the website's code or any of its libraries. The gadget chain is just a means of manipulating the flow of the harmful data once it has been injected. This also applies to various memory corruption vulnerabilities that rely on deserialization of untrusted data. In other words, a website may still be vulnerable even if it did somehow manage to plug every possible gadget chain.
PHP magic methods are method names in PHP that have special properties. If the serialized object's class implements any method with a magic name, these methods will have magic properties, such as being automatically run during certain points of execution, or when certain conditions are met. There are 4 relavant magic methods:
__wakup()
__destruct()
__toString()
__call()
__wakeup()
The __wakeup()
method is used during instantiation when the program creates an instance of a class in memory, which is what unserialize()
does: it takes the serialized string, which specifies the class and the properties of that object, and uses that data to create a copy of the originally serialized object. It then seraches for the __wakeup()
method and executes code in it. The __wakeup()
method is usually used to reconstruct any resources that the object may have, reestablish any database connections that were lost during serialization, and perform other reinitialization tasks. It's often useful during a PHP object injection attack because it provides a convenient entry point to the server's database or other functions in the program.
__destruct()
The program then operates on the object and uses it to perform other actions. When no references to the deserialized object exists, the program calls the __destruct()
function to clean up the object. This method often contains useful code in terms of exploitation. For example, if a __destruct()
method contains code that deletes and cleans up files associated with the object, the attacker might be able to mess with the integrity of the filesystem by controlling the input passed into those functions.
__toString()
Unlike __wakeup()
and __destruct()
, which always get executed if the object is created, the __toString()
method is invoked only when the object is treated as a string. It allows a class to decide how it will ract when one of its objects is treated as a string. For example, it can decide what to display if the object is passed into an echo()
or print()
function.
__call()
A program invokes the __call() method when an undefined method is called. For example, a call to $object->undefined($args)
will turn into $object->__call('undefined', $args)
.
So far, we've looked primarily at exploiting deserialization vulnerabilities where the website explicitly deserializes user input. However, in PHP it is sometimes possible to exploit deserialization even if there is no obvious use of the unserialize()
method.
PHP provides several URL-style wrappers that you can use for handling different protocols when accessing file paths. One of these is the phar://
wrapper, which provides a stream interface for accessing PHP Archive (.phar
) files.
The PHP documentation reveals that PHAR
manifest files contain serialized metadata. Crucially, if you perform any filesystem operations on a phar://
stream, this metadata is implicitly deserialized. This means that a phar://
stream can potentially be a vector for exploiting insecure deserialization, provided that you can pass this stream into a filesystem method.
In the case of obviously dangerous filesystem methods, such as include()
or fopen()
, websites are likely to have implemented counter-measures to reduce the potential for them to be used maliciously. However, methods such as file_exists()
, which are not so overtly dangerous, may not be as well protected.
This technique also requires you to upload the PHAR
to the server somehow. One approach is to use an image upload functionality, for example. If you are able to create a polyglot file, with a PHAR
masquerading as a simple JPG
, you can sometimes bypass the website's validation checks. If you can then force the website to load this polyglot "JPG
" from a phar://
stream, any harmful data you inject via the PHAR
metadata will be deserialized. As the file extension is not checked when PHP reads a stream, it does not matter that the file uses an image extension.
As long as the class of the object is supported by the website, both the __wakeup()
and __destruct()
magic methods can be invoked in this way, allowing you to potentially kick off a gadget chain using this technique.