Exploiting PHP deserialization vulnerabilities without unserialize().
Last time, we talked about PHP’s unserialize(), and how careless implementation of unserialize() leads to critical vulnerabilities. (This article assumes that you are familiar with how PHP object injection vulnerabilities work. So be sure to review if you are fuzzy!)
The unserialize() vulnerability, summarized
When an attacker controls a serialized object that is passed into unserialize(), she can control the properties of the created object. This will then allow her the opportunity to hijack the flow of the application, by controlling the values passed into magic methods like __wakeup().
The attacker can then execute the code contained in the magic methods using the parameters specified by her, or use the magic methods as a means of starting a POP chain.
POP stands for Property Oriented Programming, and the name comes from the fact that the attacker can control all of the properties of the deserialized object. Similar to ROP attacks (Return Oriented Programming), POP chains work by chaining code “gadgets” together to achieve the attacker’s ultimate goal. These “gadgets” are code snippets borrowed from the codebase that the attacker uses to further her goal.
A new attack vector
But as developers catch on to the vulnerability and static code scanners become more advanced, these vulnerabilities are becoming fewer and further between.
Today, let’s talk about a relatively new vector that allows attackers to perform PHP object injection attacks: phar deserialization.
What is phar?
To understand phar deserialization, we have to first understand what phar is.
“Phar” stands for PHP Archive. Like other archive formats such as tar and zip, it’s a way to group several files into a single file for simple distribution.
And like all archive formats, phar files contain metadata about the files in the archive. In a phar file, metadata is stored in a serialized format.
Achieving object injection
If you are familiar with how PHP object injection works, you might already know what the problem is. Essentially, PHP object injection vulnerabilities pop up when users are able to control input passed into an unserialize() function.
In this case, if a file operation is performed on a phar file via the phar:// wrapper, the phar file’s metadata would be unserialized. This means that even without an unserialize() function operating on user-controlled data, an attacker can perform PHP object injection as long as she can upload and include a crafted phar file.
Exploiting phar deserialization
To exploit a phar deserialization vulnerability, an attacker must first craft the malicious file. This is actually not complicated or difficult in any way, as PHP provides a convenient way to do it: the Phar::setMetadata method. Using this method, an attacker could set the metadata of any phar file to any PHP object that can be serialized.
So, crafting an exploit file could be as simple as:
// create phar file $phar = new Phar('exploit.phar'); // add a dummy file to stay within phar specifications $phar->addFromString("test.txt", "test"); // add any object as metadata $phar->setMetadata($object);
After the malicious file is crafted, there are two prerequisites to achieving PHP object injection using a phar file.
- First, the attacker must be able to plant a crafted phar file on the server via a file upload functionality (the phar:// wrapper cannot operate on remote files). This is made simpler by the fact that phar files are extension independent.
- And second, the attacker must be able to control the full file path in any file operation (to be able to specify the phar:// wrapper).
Using the phar:// wrapper will trigger unserialize() in any file operation, such as file(), file_exist(), file_get_contents(), fopen(), rename(), unlink() and include(). So a phar deserialization vulnerability would allow attackers to escalate a file inclusion or enumeration vulnerability into remote code execution.