What is PHP Object Injection? Ways to Exploit, Examples and Impact

Learn how PHP Object Injection works, the danger of unserialize(), and how to prevent RCE and POP chains in your PHP applications.

What is PHP Object Injection? Ways to Exploit, Examples and Impact

PHP Object Injection is a high-severity vulnerability that occurs when an application untrustingly deserializes user-supplied data. This flaw can lead to a variety of malicious outcomes, including Remote Code Execution (RCE), arbitrary file manipulation, and even full server compromise. In this guide, we will break down the technical foundations of PHP serialization, explore how attackers leverage magic methods to build exploit chains, and discuss practical ways to defend your infrastructure.

Understanding PHP Serialization and Deserialization

To understand PHP Object Injection, we must first understand how PHP handles data persistence. Applications often need to store complex data structures, such as objects or arrays, in a format that can be easily saved to a database, a file, or passed through a URL parameter. This process is known as serialization.

In PHP, the serialize() function converts an object into a plain string representation. Conversely, the unserialize() function takes that string and reconstructs it back into a live PHP object.

Example of Serialization

Consider a simple User class:

class User {
    public $username;
    public $role;

    public function __construct($username, $role) {
        $this->username = $username;
        $this->role = $role;
    }
}

$user = new User("alice", "editor");
echo serialize($user);

The output of the serialize() call would look like this:

O:4:"User":2:{s:8:"username";s:5:"alice";s:4:"role";s:6:"editor";}

Let's break down this string:

  • O:4:"User": An Object with a class name of 4 characters ("User").
  • :2:: The object has 2 properties.
  • s:8:"username": The first property name is a string of 8 characters.
  • s:5:"alice": The value of the first property is a string of 5 characters.
  • s:4:"role": The second property name is a string of 4 characters.
  • s:6:"editor": The value of the second property is a string of 6 characters.

When unserialize() is called on this string, PHP recreates the User object with these specific values. The vulnerability arises when an attacker can provide their own serialized string to the unserialize() function.

What is PHP Object Injection?

PHP Object Injection occurs when an application passes user-controlled input directly into the unserialize() function without proper validation. While simply recreating an object might seem harmless, PHP's object-oriented nature includes "Magic Methods"—special functions that are automatically triggered during certain events in an object's lifecycle.

If an attacker injects a serialized string representing a class that is already defined in the application's codebase (or included via libraries), they can manipulate the properties of that class. When PHP triggers a magic method on that injected object, it may execute malicious logic based on those manipulated properties.

The Role of Magic Methods in Exploitation

Magic Methods are the engine of a PHP Object Injection attack. They are methods starting with a double underscore (__) that PHP calls automatically. Common magic methods used in exploits include:

  1. __wakeup(): Called immediately when unserialize() is executed on an object. It is often used to re-establish database connections or initialize resources.
  2. __destruct(): Called when an object is destroyed or the script ends. This is a very common target for attackers because it is guaranteed to run.
  3. __toString(): Called when an object is treated as a string (e.g., via echo $obj).
  4. __call(): Called when an undefined method is invoked on an object.
  5. __get() / __set(): Called when reading from or writing to inaccessible or non-existent properties.

An attacker looks for these methods within the application's source code to find "gadgets"—snippets of code that perform potentially dangerous actions like writing to files, executing system commands, or querying a database.

How to Exploit PHP Object Injection

Exploitation typically follows a specific workflow: identifying the injection point, finding useful classes (gadgets), and crafting the payload.

Scenario 1: Arbitrary File Deletion

Imagine an application has a class named FileHandler used for temporary logging. It uses the __destruct() method to clean up files.

class FileHandler {
    public $logfile;

    public function __destruct() {
        if (file_exists($this->logfile)) {
            unlink($this->logfile);
        }
    }
}

// Vulnerable Code
$data = $_GET['data'];
$user_obj = unserialize($data);

An attacker can craft a serialized FileHandler object and set the $logfile property to a sensitive file, such as .htaccess or a configuration file.

Payload: O:11:"FileHandler":1:{s:7:"logfile";s:9:".htaccess";}

When the script finishes, PHP destroys the injected FileHandler object, triggers __destruct(), and deletes the .htaccess file, potentially disabling security restrictions on the server.

Scenario 2: Remote Code Execution (RCE)

In a more severe case, a class might use a property as part of a system command. Consider a DatabaseExport class:

class DatabaseExport {
    public $command;

    public function __wakeup() {
        system($this->command);
    }
}

If an attacker can reach an unserialize() call, they can inject this class with a malicious command.

Payload: O:14:"DatabaseExport":1:{s:7:"command";s:20:"rm -rf /var/www/html";}

Upon deserialization, __wakeup() is triggered, and the system() function executes the attacker's command, leading to full server compromise.

Property Oriented Programming (POP) Chains

In modern, complex applications, a single magic method might not be enough to achieve the attacker's goal. Instead, attackers use Property Oriented Programming (POP). This involves chaining multiple "gadgets" together.

For example:

  1. unserialize() triggers __wakeup() in Class A.
  2. Class A's __wakeup() calls a method on an object stored in one of its properties.
  3. The attacker populates that property with Class B.
  4. Class B's method performs a dangerous action, like calling eval() or include().

Building a POP chain requires deep knowledge of the application's codebase. Tools like phpgg (PHP Generic Gadget Chains) provide pre-built chains for popular frameworks like Laravel, Symfony, and Monolog, making it easier for attackers to exploit known libraries.

Real-World Impact

The impact of PHP Object Injection is almost always critical. Because attackers can control object properties and trigger logic within the server's context, the consequences include:

  • Remote Code Execution (RCE): The ultimate goal, allowing the attacker to run any command on the server.
  • Sensitive Data Exposure: Attackers may be able to read configuration files (like wp-config.php) or access database credentials.
  • Privilege Escalation: By injecting a User object with an isAdmin property set to true, an attacker might bypass authentication logic.
  • Denial of Service (DoS): Deleting critical system files or triggering resource-intensive loops can take an application offline.

How to Detect PHP Object Injection

Detecting these vulnerabilities requires a combination of automated tools and manual review.

Static Analysis (SAST)

Search your codebase for any instance of the unserialize() function. If the input to this function originates from $_GET, $_POST, $_COOKIE, or even data retrieved from a database that was previously user-influenced, it is a potential vulnerability.

Dynamic Analysis (DAST)

Security scanners can attempt to inject serialized payloads into various input fields to see if the application behaves unexpectedly. However, DAST often struggles with object injection because the exploit depends heavily on the specific classes available in the backend.

Manual Code Review

Reviewing the logic of magic methods (__destruct, __wakeup, etc.) is essential. Even if unserialize() isn't used directly, third-party libraries might use it internally. Monitoring your external attack surface with Jsmon can help identify exposed endpoints that might be processing serialized data.

Prevention and Mitigation Strategies

Preventing PHP Object Injection is straightforward in principle: never pass user-supplied data to unserialize().

1. Use JSON Instead

If you need to pass structured data between the client and server, use json_encode() and json_decode(). JSON is a data-only format; it does not support objects or magic methods, making it inherently safe from injection attacks.

// Safe alternative
$data = $_GET['data'];
$user_data = json_decode($data, true); // Decodes as an associative array

2. Implement Data Validation

If you absolutely must use unserialize(), use the allowed_classes option introduced in PHP 7.0. This restricts the types of objects that can be instantiated.

// Only allow the 'User' class to be deserialized
$user_obj = unserialize($data, ["allowed_classes" => ["User"]]);

By setting allowed_classes to false, you prevent any objects from being created, only allowing scalar types like strings and arrays.

3. Sign Your Serialized Data

If you must store serialized data in a cookie or a hidden field, use a Message Authentication Code (MAC), such as HMAC, to sign the data. Before calling unserialize(), verify the signature to ensure the data hasn't been tampered with by the user.

Conclusion

PHP Object Injection remains one of the most dangerous vulnerabilities in the PHP ecosystem. By understanding how serialization works and how magic methods can be weaponized into POP chains, developers and security professionals can better protect their applications. The transition from unserialize() to safer formats like JSON is the most effective way to eliminate this entire class of vulnerability.

To proactively monitor your organization's external attack surface and catch exposures before attackers do, try Jsmon.