0
Php

The Dangers of PHP’s unserialize and How to stay safe

As a PHP developer with over 16 years of experience, I have witnessed the evolution of PHP security practices. While PHP continues to be one of the most widely used programming languages for web development, its features can sometimes introduce subtle security risks that developers need to be aware of. One such feature is PHP’s unserialize() function.

Understanding unserialize() in PHP

The unserialize() function in PHP is used to convert a serialized string back into a PHP variable. While it seems like a simple and useful feature, it can become a potential security vulnerability when not used carefully.

Here’s a quick example of how unserialize() works:


$serializedData = serialize(['name' => 'John', 'age' => 30]);
$data = unserialize($serializedData);
print_r($data);

In this case, a simple array is serialized into a string and then unserialized back into an array. However, problems arise when user input is unserialized without validation or proper security checks.

The Dangers of Using unserialize() with Untrusted Data

The primary risk when using unserialize() with untrusted data is PHP Object Injection (POI). This occurs when an attacker provides a specially crafted serialized object that, when unserialized, triggers actions or accesses sensitive resources. Without proper safeguards, this can lead to significant security breaches, such as code execution, file deletion, or data leakage.

A Real-World Example of PHP Object Injection (POI)

Let’s look at a practical example of how PHP Object Injection (POI) works in a vulnerable application:

Imagine you have a PHP application that stores user settings in a serialized format, and the application unserializes this data when retrieving the settings. The code might look something like this:

class UserSettings {
    public $theme;
    public $language;
}

class Logger {
    public function log($message) {
        file_put_contents('log.txt', $message . PHP_EOL, FILE_APPEND);
    }
}

if (isset($_GET['data'])) {
    // Vulnerable unserialize
    $userSettings = unserialize($_GET['data']);
    echo "User theme: " . $userSettings->theme;
    echo "User language: " . $userSettings->language;
}

In this example, the UserSettings class is meant to represent user settings, but there’s a Logger class with a log() method that writes to a file. The application retrieves serialized data from the data parameter, unserializes it, and then prints the theme and language of the user.

An attacker could exploit this by crafting a malicious serialized object that includes the Logger class, which would execute code when unserialized.

The Attacker’s Malicious Payload

Here’s an example of how an attacker might craft a malicious payload to exploit the unserialize() vulnerability:

$maliciousPayload = serialize([
    new Logger(),
    'theme' => 'dark',
    'language' => 'en'
]);

// Serialized malicious payload
echo $maliciousPayload;

When the attacker sends this serialized data as a query parameter (data), it will unserialize into an object of type Logger. The Logger‘s constructor will be triggered, and it will log a message, potentially to an attacker-controlled file:

http://example.com/vulnerable.php?data=O:6:"Logger":0:{}O:11:"UserSettings":2:{s:5:"theme";s:4:"dark";s:8:"language";s:2:"en";}

This serialized string includes both the Logger object and the UserSettings object. When unserialized, the Logger‘s log() method is invoked, writing to a file, which could be exploited to execute arbitrary commands, delete files, or steal sensitive data.

Potential Consequences of PHP Object Injection

The attacker can leverage PHP Object Injection to:

  • Delete Files: If the malicious class includes a method that deletes files, the attacker could delete important files on the server.
  • Execute Arbitrary Code: The attacker could execute system commands or even run malicious code through PHP’s ability to call functions or system commands.
  • Data Exfiltration: Sensitive user data could be extracted from the server by exploiting the unserialization process.

How to Protect Your Application

So, how can you protect your PHP applications from the dangers of unserialize()? Here are some best practices:

1. Avoid Unserializing Untrusted Data

The best approach is to simply avoid unserializing untrusted data. If possible, use more secure formats like JSON or XML for data exchange. These formats are less prone to vulnerabilities like PHP Object Injection because they don’t support object instantiation.

For example, use json_decode() instead of unserialize():

$data = json_decode($jsonData, true);

2. Use allowed_classes Parameter

If you absolutely must unserialize user input, make sure to use the allowed_classes parameter, which was introduced in PHP 7.0. It allows you to specify which classes are allowed to be unserialized, preventing any malicious classes from being instantiated.

$userSettings = unserialize($serializedData, [

    "allowed_classes" => ["UserSettings"]

]);

This ensures that only objects of the UserSettings class (or any class you specify) can be instantiated, thus protecting your application from arbitrary class injection.

3. Whitelist Class Names

If your application requires the use of unserialization, you can implement a whitelist of allowed classes. By validating the class name before unserializing the data, you can block any potentially harmful objects from being loaded.

$whitelistedClasses = ['UserSettings'];

$unserializedData = unserialize($data);

if (!in_array(get_class($unserializedData), $whitelistedClasses)) {

    throw new Exception("Invalid class in unserialized data."); 

}

4. Validate and Sanitize Input

Before performing any unserialization, ensure the input data is properly sanitized and validated. You can use custom validation techniques to ensure the serialized data doesn’t contain any unexpected or dangerous objects.

if (preg_match('/^[\w:;,.{}()=]+$/', $serializedData)) {
    // Safe to unserialize
    $data = unserialize($serializedData);
} else {
    // Invalid input
    throw new Exception("Invalid serialized data.");
}

Conclusion

PHP’s unserialize() function can be extremely useful, but it also introduces a significant security risk when used improperly, especially when it comes to PHP Object Injection (POI). By following best practices such as avoiding unserializing untrusted data, using the allowed_classes parameter, whitelisting class names, and sanitizing input, you can significantly reduce the risk of a POI attack.

As PHP developers, it’s crucial to stay aware of such vulnerabilities and take steps to protect our applications. A secure application is not just about writing clean code, but about thinking ahead and mitigating potential attack vectors like PHP Object Injection.

Stay safe, and always validate before you deserialize.

Latest Blog

0
Php

PHP – 8.0 Match Expression

In PHP 8.0 there is a new feature or I should say a new language construct (keyword) going to be introduced,  which has been implemented depending […]

0
Php

New Union Type in PHP – 8

A new RFC has been finished voting and accepted for PHP – 8. That is “Union Types”, which is an extension of PHP’s type system. […]