Home » Demystifying PHP Object Injection
Hello readers, in this blog post, our consultant Aditya has discussed the PHP Object Injection vulnerability. He explains the vulnerability details, minimum requirements, vulnerability techniques, vulnerability chaining with other vulnerabilities, recommendations, practice labs, and much more.
PHP’s popularity has been consistent since its beginning, and statistics show that it is a preferred language for many web application developers. This is one of the reasons why PHP has long piqued the curiosity of the information security community. Another reason PHP is well-known in the information security field is because of its use in various Content Management Systems (CMS), the source codes for which are publicly available on platforms such as GitHub. Since the source codes are open source in nature, several vulnerabilities have been uncovered that are particular to applications and systems that use PHP as their preferred language.
The variety and number of vulnerabilities discovered in PHP-based applications are significant. However, not all of the discovered vulnerabilities are easy to comprehend and detect, and hence the majority of security professionals and developers overlook or ignore those vulnerabilities. PHP Object Injection is one such vulnerability that falls into this category, which we will discuss in this blog so that the reader has a better understanding of this obscure vulnerability, why it exists, and how an attacker might exploit it. In addition, we have created a compilation of four practice labs on our platform Vulnmachines where you may navigate and gain hands-on experience with various real-life scenarios.
Serialization is the process of transforming an object into a value representation to store it in a memory buffer or transfer it to a database, via a network, or to a file. When serialization is performed it is ensured that the state of the object is also maintained, which implies that the properties of the object, as well as their allocated values, are retained. The primary goal of serialization is to store an object’s state, so that it may be recreated when needed.
The ‘serialize()’ method in PHP accepts a single parameter which is the data we want to serialize and converts it into a storable representation of value that can be returned by the method as a serialized string. Other than resource-type data, such as references to external resources and certain objects, the ‘serialize()’ method can handle all types of values.
The table below outlines the basic structure of a PHP serialized string, which will assist in understanding the serialized strings explained in the subsequent code samples throughout the log post.
Annotation |
Description |
O:4 |
Represents an object with the length of class name as 4 |
s:4:”test” |
Represents the parameter name as a string of 4 characters i.e., “test” |
a:2 |
Represents an array of 2 elements |
i:2 |
Represents an element at index two |
b:1 |
Represents a Boolean |
d:1.1 |
Represents a Float |
The following table summarizes the technical details associated with the serialize() method:
Syntax |
serialize(value) |
Parameter Value |
Mandatory |
Return Type |
String |
Return Value |
A String of byte-stream representation of the specified parameter value, which could be stored anywhere. |
Example:
The preceding code snippet creates a ‘SerializationExample’ class object and then generates the serialized string representation of that object:
O:20:”SerializationExample”:1:{s:4:”data”;a:3:{i:0;s:3:”The”;i:1;s:6:”SecOps”;i:2;s:5:”Group”;}}
A better understanding of the above-serialized string representation of the object can be obtained from the below table:
Annotation |
Description |
O:20 |
Represents an object with the length of class name as 20 |
s:4:”data” |
Represents the parameter name as a string of 4 characters i.e., “data” |
a:3 |
Represents an array of 3 elements |
i:0 |
Represents an element at index zero |
Deserialization is the process of reassembling an object or data structure from a single serialized variable into a PHP value.
When a serialized string containing an object and its properties is passed to the ‘unserialize()’ method, the ‘unserialize()’ method will perform Object Instantiation, which is the process of recreating an instance of the provided initially serialized object in memory. Once the object has been recreated in memory, PHP will attempt to invoke the ‘__wakeup()’ magic method and execute the code in the ‘__wakeup()’ method; if a ‘__wakeup()’ method exists, the code will be performed in the existing method.
The ‘__wakeup()’ magic method can recreate any properties that the original object has had. The ‘__wakeup()’ magic method is meant to be used to restore any database connections that may have been lost during serialization and to execute additional reinitialization operations. When no reference to the deserialized object instance exists, the ‘__destruct()’ magic method of that class is invoked.
Example:
The preceding code snippet recreates the ‘SerializationExample’ class object from the serialized string representation of that object.
The following table summarizes the technical details associated with the ‘unserialize()’ method:
Syntax |
unserialize(string, options) |
Parameter String |
Mandatory (Serialized String) |
Parameter options |
Optional (Could be an array of class names, false for no classes and true for all classes) |
Return Type |
String, Boolean, Array, Float, Integer, or Object |
Return Value |
The unserialized value |
When the user-supplied input is not properly validated and sanitized before being sent to the ‘unserialize()’ PHP function, PHP Object Injection occurs. Since PHP supports object serialization, attackers could supply arbitrary serialized strings to a susceptible ‘unserialize()’ method, resulting in the injection of arbitrary PHP object(s) into the application. PHP Object Injection allows for the unrestricted alteration of object content, which must be unserialized using the PHP ‘unserialize()’ function.
The following prerequisites must be satisfied in order to properly exploit a PHP Object Injection vulnerability:
On the injection points, the application must not have a comprehensive input validation mechanism.
The application must not validate the serialized data’s integrity and must enable deserializing data from untrusted sources.
A class that implements at least one PHP magic method must be present in the vulnerable application (In the parts that follow, there will be further information about these).
All of the classes used during the attack must be declared when the vulnerable ‘unserialize()’ is being called, otherwise object autoloading must be supported for such classes.
The impact of PHP Object Injection depends largely on the context where it is discovered; however, it enables an attacker to execute a variety of malicious attacks, including but not limited to Code Injection, SQL Injection, Path Traversal, and Application Denial of Service, among others.
Since we now understand what PHP Object Injection is and what prerequisites must be satisfied for it to exist, and what impact it has, let us now look at the showstopper, i.e., the magic methods, which play a critical part in the existence of this exotic vulnerability.
PHP magic methods are methods that are automatically invoked when specific criteria are satisfied. As they are predefined methods, one cannot have functions with these names in any of the classes unless users desire the accompanying magic functionality. A magic method is easily distinguished from conventional methods since it begins with a double underscore in PHP.
PHP includes 15 magic methods that can lead to PHP Object Injection, all of which are listed in the table below:
Magic Method |
Details |
__call() |
When a non-existing or inaccessible method is called by an object, PHP will automatically invoke the ‘__call()’ magic method using the name of the method you requested to call and the argument you provided as parameters. The below-mentioned code snippet utilizes the ‘getAnyRandomData’ method, which is not declared anywhere in the above code snippet, and therefore the ‘__call()’ magic method is invoked. |
__callStatic() |
When an inaccessible static method of a class is called by an object, PHP will automatically invoke the ‘__callStatic()’ method. The below-mentioned code snippet utilizes the ‘getAnyRandomData’ static method, which is not declared anywhere in the above code snippet, and therefore the ‘__callStatic()’ magic method is invoked. |
__clone() |
When an object is cloned in PHP with the clone keyword, the ‘__clone()’ magic method is invoked. After the object has been cloned, the ‘__clone()’ magic method is used to manipulate the object’s state. The below-mentioned code snippet utilizes the ‘clone’ keyword as a result of which PHP invokes the ‘__clone()’ magic method. |
__construct() |
Due to the fact that it is used to configure a class when it is initialized, PHP’s ‘__construct()’ method is the most frequently used magic method. Classes with a constructor method utilize this method on newly formed objects, making it ideal for any initialization that the object may require before it is utilized. Classes with a ‘constructor’ method utilize this method on newly formed objects, making it ideal for any initialization that the object may require before it is utilized. The below-mentioned code snippet utilizes the ‘__construct()’ magic method which is invoked by PHP when the object of the ‘constructMagicMethod’ class is created. |
__debugInfo() |
The ‘__debugInfo()’ magic method is executed when an object is utilized inside the ‘var_dump()’ method to dump information for debugging purposes. The ‘__debugInfo()’ magic returns an array of variables that might be helpful while performing debugging. The below-mentioned code snippet utilizes the ‘__debugInfo()’ magic method which is invoked by PHP when the object of the ‘debugInfoMagicMethod’ class is used inside the ‘var_dump()’ method as a parameter. |
__destruct() |
PHP automatically invokes the ‘__destruct()’ magic method when the object is destroyed at the end of the program and no longer serves usage. The below-mentioned code snippet utilizes the ‘__destruct()’ magic method which is automatically invoked by PHP when the object of the ‘destructMagicMethod’ class is destroyed at the end of the program and no longer serves usage. |
__get() |
The ‘__get()’ magic method is invoked when an inaccessible private or protected variable, or a variable that does not exist, is used. The below-mentioned code snippet utilizes the ‘__get()’ magic method inside the ‘getMagicMethod’ class which is invoked by PHP when a non-existing variable is used. |
__invoke() |
When an object is treated as a function, the ‘__invoke()’ magic method is called. The major function of the ‘__invoke()’ method is that you may implement it if you wish to represent your objects as callable. The below-mentioned code snippet utilizes the ‘__invoke()’ magic method which is called automatically by PHP when the object of the ‘invokeMagicMethod’ class is treated as a function. |
__isset() |
The ‘__isset()’ magic method is triggered when a call is made to the ‘empty()’ or ‘isset()’ method on an inaccessible or non-existent object property. The below-mentioned code snippet uses the ‘isset()’ function on the Test property, which is absent from the ‘issetMagicMethod’ class, and thus the ‘__isset()’ magic method is triggered. |
__set() |
When an inaccessible variable, such as a private or public variable, or a non-existent variable, is edited or altered, PHP automatically invokes the ‘__set()’ magic method. The below-mentioned code snippet utilizes the ‘__set()’ magic method inside the ‘setMagicMethod’ class which is invoked automatically by PHP when a non-existing variable i.e., ‘$absentVariable’ is used or altered. |
__set_state() |
The ‘__set_state()’ magic method is a static method that works with the ‘var_export()’ function. When using this magic method to export classes, the ‘var_export()’ function must be specified in the class. In the below-mentioned code snippet PHP automatically invokes the ‘__set_state()’ magic method when the object of the ‘setstateMagicMethod’ class is passed to the ‘var_export()’ function. |
__sleep() |
The ‘__sleep()’ magic method is triggered when the ‘serialize()’ function is invoked on an object. Specific properties are saved during serialization under certain cases, and the ‘__sleep()’ magic method returns an array containing the names of properties that must be serialized. The below-mentioned code snippet utilizes the ‘__sleep()’ magic method which will be invoked when the object of the ‘sleepMagicMethod’ class will be passed to the ‘serialize()’ function and only the properties i.e., ‘firstName’, ‘middleName’, and ‘lastName’ will be preserved. |
__toString() |
When an object is represented or processed as a string, PHP automatically invokes the ‘__toString()’ magic method. Essentially, this magic method assists in expressing what must be showcased when the object is processed as a string. If the ‘__toString()’ magic method is not available and the class object is used by ‘print’ or ‘echo’, an error message will be displayed. The below-mentioned code snippet utilizes the ‘__toString()’ magic method, which is invoked automatically by PHP when the object of the ‘toStringMagicMethod’ is passed to ‘echo’ and therefore treated as a string. |
__unset() |
PHP invokes the ‘__unset()’ magic method when an ‘unset()’ function is used on an inaccessible private, protected, or non-existent object property. The code snippet below invokes the ‘unset()’ function on the ‘Test’ property, triggering the ‘__unset()’ magic method, which restores the ‘Test’ property’s original state. |
__wakeup() |
When the ‘unserialize()’ function is invoked on the object, the ‘__wakeup()’ magic method is intended to re-establish any connections that may have been lost during serialization and perform other reinitialization tasks. The code snippet below utilizes the ‘connectionmethod()’ function, which is used by the ‘__wakeup()’ magic method, to re-establish the connection when the ‘unserialize()’ function is implemented to the ‘wakeupMagicMethod’ class object. |
As we understand what PHP Object Injection is and how insecure deserialization and magic methods in PHP are the underlying cause, let us look at some vulnerable PHP Object Injection samples to get an understanding of what PHP Object Injection can lead to.
The following example demonstrates the PHP class ‘POI2IDOR’ with an exploitable ‘unserialize()’ method. In line 9 of the code snippet below, we observe that the data from the ‘inputparameter’ is deserialized in an unsecured way, which is then given to an ‘if’ conditional statement on line 12 that is subject to Type Juggling. When a specifically designed serialized object is supplied in this example, an attacker might be able to obtain an object reference for accomplishing an authentication bypass, for example, by accessing the following URL: ‘http://vulnerable.target.com/test.php?inputparameter=a:2:{s:15:”adminsecretcode”;N;s:4:”misc”;R:2;}’
Vulnerable Code Snippet:
The below-mentioned example demonstrates a PHP class ‘POI2PT’ with an exploitable ‘__destruct()’ magic method spanning from line 5 to line 10. When a specifically constructed serialized object is supplied in this example, an attacker might be able to perform Path Traversal, by browsing the following URL: ‘http://vulnerable.target.com/test.php?inputparameter=O:6:”POI2PT”:1:{s:12:”existingfile”;s:22:”../../../../etc\passwd“;} ‘
Vulnerable Code Snippet:
The following example demonstrates an exploitable unserialize method. When a specifically constructed serialized object is supplied using a POST request, an attacker will be able to perform an authentication bypass with the help of type juggling as the comparison statement on line 5 appears to be loosely comparing the data. The HTTP request for exploiting this misconfiguration will be similar to the one mentioned below:
Vulnerable Code Snippet:
POST Request:
As seen in the following example, the PHP class ‘POI2CI’ includes an exploitable ‘__destruct()’ magic method from line 7 to line 9. When a precisely configured serialized object is supplied in this code snippet, the application will seek references to the ‘receivedinput’ object, and once there are no more references, the ‘__destruct()’ magic method will be called, resulting in the execution of the injected command.
The attacker will be able to obtain command injection, by accessing the following URL: ‘http://vulnerable.target.com/test.php? inputparameter=O:6:”POI2CI”:1:{s:16:”arbitrarycommand”;s:2:”id”;}’
Vulnerable Code Snippet:
PHPGGC is a deserialization payload library that includes a tool for generating them from the command line or programmatically. This may be used to produce Property Oriented Programming (POP) gadgets from libraries that users have already discovered. While facing unserialize on a website for which you do not have the source code, or simply when attempting to develop an attack, this tool allows you to produce the payload without having to go through the time-consuming procedures of locating and assembling gadgets.
CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, WordPress, Yii, and ZendFramework are all supported by PHPGGC. Additionally, PHPGGC requires a PHP version greater than or equivalent to 5.6 for proper functioning.
The attributes associated with a gadget chain generated by the PHPGGC library are described in the table below:
Attribute |
Description |
Name |
Name attribute specifies the framework or library that the gadgets are intended for. |
Version |
Version attribute specifies the framework or library version for which the gadgets are intended. |
Type |
Type attribute describes the sort of exploitation the gadget chain would result in, such as Remote Code Execution, Arbitrary File Deletion, Server-Side Request Forgery, and so on. |
Vector |
Vector attribute describes the PHP magic method that will be used to initiate the chain following the unserialize operation on the specified input. |
Information |
Information attribute describes any extra detail associated with the gadget chain. |
At the time of writing, the PHPGGC library has 94 gadget chains associated with various frameworks and libraries that it could exploit. The list of available gadget chains in PHPGGC can be obtained by executing the below-mentioned command:
Depending on the application technology, we can also use the following command to filter the gadget chain available in the PHPGGC that is most suited for testing:
PHPGGC provides a ‘-i’ flag that can be used to retrieve additional information about the specified gadget chain using the command below:
On a high level, PHP Object Injection Exploitation Using PHPGGC includes the following activities:
Detection of the framework or library in use.
Identification of a possible injection point, such as a parameter, cookie, or Header.
Additional attack preparation, such as a reverse shell or a backdoor, which will be uploaded to the application server upon successful exploitation of the vulnerability.
Custom serialized object creation using PHPGGC
Over time, security experts worldwide have discovered several instances of PHP Object Injection vulnerability, some of which could be exploited to achieve Remote Code Execution, Authentication Bypass, etc. Some of the most significant cases of the PHP Object Injection vulnerability are described below, which will aid in acquiring a better understanding of this vulnerability and knowing some of the finest publicly known exploits.
WooCommerce is a WordPress e-commerce plugin. It simplifies the process of developing and administering an online store by providing decent levels of flexibility as well as various important features such as inventory and tax administration, secure payments, and shipping integration.
In this case, the security researcher was testing the order feature in the WooCommerce WordPress plugin and discovered that the PHP native unserialize method allows small ‘o’ as an option and treats it as a ‘StdClass’ object.
Vulnerable Code Snippet:
Using the obtained knowledge about the small ‘o’ as an option, the security researcher constructed a bypass that allowed them to tamper with any ‘order’ object. In addition, when paired with capital ‘S’, existing firewall rules failed, causing the firewall to be bypassed.
Proof of Concept Code:
ExpressionEngine is a robust and flexible open-source Content Management System. ExpressionEngine has a large community of developers making add-ons that extend system capabilities.
In this scenario, when the security researcher attempted to log in with the username ‘admin,’ the server responded with a cookie containing PHP serialized data. The researcher then downloaded and installed a copy of the application for further analysis. After walking through the code, the researcher discovered that a few checks were done before the cookie was parsed and subsequently unserialized. Further investigation revealed that a cookie integrity validation mechanism was in place, ensuring that the cookie was not tampered with. Since the application was written in PHP, a loosely typed language, using the loose comparison operator resulted in the comparison evaluating as true because ‘0ex’ will always be zero when cast to integers, a process known as Type Juggling.
The researcher then created the following proof of concept, which brute forces the ‘md5($payload.$this->sess_crypt_key)’ until an MD5 hash sum of the updated ‘$payload’ and ‘$this->sess_crypt_key’ that starts with ‘0e’ and ends in all digits was discovered:
Proof of Concept Code:
Output:
When the researcher compared the MD5 hash sum to any ‘$signature’ value, they discovered that the original ‘$payload’ was successfully updated, confirming the existence of Type Juggling and PHP Object Injection.
The researcher discovered a method that appears to be checking the database to see if the given ‘$username’ is locked out pre-authentication. Since the researcher got access to the value of ‘$username’ and he was able to inject a SQL query there, it resulted in SQL injection.
Following an additional inspection of the source code and multiple hit-and-try efforts, the researcher constructed the Proof of concept code shown below to insert the ‘sleep(5)’ command into the database:
Proof of Concept Code:
Output:
The researcher then submitted the below-mentioned payload through the ‘exp_flash’ cookie generated by the preceding proof of concept code and observed that the response was shown after 5 seconds, confirming the vulnerability:
Moodle is a learning platform that aims to give educators, administrators, and learners a unified, secure, and integrated system for creating personalized learning environments.
In this scenario, the researcher discovered that Moodle’s Shibboleth authentication module was vulnerable to Remote Code Execution through a PHP Object Injection vulnerability. The vulnerability was discovered in the logout handler in the ‘logout_file_session’ function spanning from line 17 to line 22, since the file handler traverses all session files. Each session file holds a single serialized session object. The handler handles this by reading each file and deserializing its data using the ‘unserializesession’ function. When a matching session ID in a session object is identified, the relevant file is unlinked, resulting in a ‘logged-out’ session, as shown in the below-mentioned code snippet:
The code fails to properly parse the serialized value of a session key if it contains an unexpected Pipe ‘|’ character, introducing the risk of the parser misinterpreting the beginning and end of a serialized value within the session object, leading to the deserialization of potentially user-controlled input.
The researcher sent the payload ‘xxx|O:8:”Evil”:0:{}’ which was saved in the ‘$_SESSION’ object of an unauthenticated guest user, which the session handler then serialized and stored on the filesystem in the form ‘USER|O:8:”stdClass”:…:{…} SESSION|O:8:”stdClass”:…:{… s:…:”filterfirstname”;s:19:”xxx|O:8:”Evil”:0:{}”;…}’, confirming the PHP Object Injection vulnerability which was then exploited for obtaining Remote Code Execution.
Being a team of Advanced Penetration Testers and Security Researchers, we strongly believe in a hands-on approach to cyber security and therefore we have published a compilation of four PHP Objection labs on our platform Vulnmachines for the readers to learn PHP Object Injection by practicing in labs that mimic real-life circumstances.
The practice labs relevant to PHP Object Injection are listed below and can be accessed by navigating to Vulnmachines:
Demystifying PHP Object Injection (Why So Serialize)
Deserializing user input should be avoided unless necessary.
When deserializing data from untrusted sources, avoid using the ‘unserialize()’ method with user-supplied input and instead use the ‘json_decode()’ and ‘json_encode()’ methods.
Implement a verification mechanism for serialized data before deserializing it from any untrusted source, such as user input.