Object injection is an application vulnerability that occurs when an application deserializes untrusted data.
If an attacker manages to inject a malicious object, he can exploit its properties to execute arbitrary code, steal data, modify the application’s behaviour or manipulate files remotely. In other words, this vulnerability can lead to a total compromise of the targeted system.
All languages that support object deserialization are potentially vulnerable, but PHP is particularly exposed. In this article, we will examine the principles of serialization to explain how object injection works. We will also look at examples of exploitation and security best practices for preventing object injection.
Comprehensive Guide to Object Injection
Understanding the Principles of Serialization and Deserialization
To understand object injection, it is essential to understand the concept of serialization and its usefulness.
Serialization enables an object to be converted into a text string for storage or exchange between applications. Unlike traditional data such as text or files, an object is more complex: it exists in memory, has specific attributes and methods, and can change during its lifecycle. With serialisation, these characteristics are preserved in a form that can be read and transmitted.
When an application receives this string, it can deserialise it to recreate an object identical to the original. This mechanism is very practical for sharing data between systems, but it can also be abused for malicious purposes, as we shall see.
Let’s take the example of two applications sharing the same disk and wishing to exchange an object A, defined as follows:
Application1 serializes the object and saves it in a ‘store’ file.
Application 2 then retrieves this file, deserializes it and recreates the original object. However, for this operation to work, it needs to know the definition of the object’s class (via a file such as A.php
).
Object A will be serialized as follows:
O:1:"A":1:{s:10:"objectData";s:6:"blabla";}
Let’s break down this structure:
O
indicates that it is an object.1:"A"
means that the name of the class contains 1 character and is calledA
.1:{…}
indicates that the object has 1 attribute.s:10: "objectData"
specifies that the attribute is a 10-character string (s
for string) namedobjectData
.s:6: "blabla
” shows that this attribute contains a string of 6 characters, named"blabla"
.
It is important to note that static properties, including methods, are not included in serialization. This is why application 2 must know the A class in order to reconstruct the object.
What is Object Injection?
Object injection is a vulnerability that exploits the deserialization of a malicious object. By injecting modified attributes, an attacker can cause unexpected behaviour in the application.
However, deserialization does not include static methods (such as functions). So the attacker cannot simply add malicious code to a function and expect it to execute. Instead, they have to exploit non-static attributes that the application uses in an insecure way.
The problem often arises because the application blindly trusts the protected or private attributes, or assumes that a serialized object cannot be modified – which is not true.
One of the most common attack vectors is the injection of objects exploiting ‘magic’ functions. These functions, which are specific to certain languages, are automatically executed at different stages in the object’s lifecycle.
Examples of magic functions in PHP:
__construct()
→ called when the object is initialised.__destruct(
) → executed when deleting the object__wakeup()
→ used during deserialization__toString()
→ triggered when an object is treated as a string__invoke()
→ executed if the object is called as a function
By exploiting these mechanisms, an attacker ensures that his code will be executed sooner or later, because the application will end up calling one of these functions.
Examples of Object Injection Vulnerability Exploitation
Object injection can be exploited in a number of ways, in particular to execute arbitrary code or bypass security mechanisms. Here are two concrete scenarios illustrating these attacks.
Exploiting an object injection via arbitrary code execution
Let’s take a vulnerable application where the serialized object is transmitted via a GET parameter:
Object A now has a private internal
attribute and a magic __destruct()
function:
Here, the __destruct()
function is a critical vulnerability: it arbitrarily executes the contents of the private internal
attribute via eval()
.
As this attribute is private, it cannot be modified directly, but an attacker can alter it by exploiting deserialization.
Object A serialized normally:
O:1:"A":2:{s:10:"objectData";s:6:"blabla";s:11:"Ainternal";s:12:"echo 'test';";}
Format details:
O:1: "A"
→ Object of classA
(1 character name).s:10: "objectData"
→ Public attributeobjectData
containing‘blabla’
.s:11: "Ainternal"
→ Private attributeinternal
, prefixed byA
in the serialization.
An attacker can therefore inject a malicious payload into internal
by sending this request:
https://application2.com?input=O:1:"A":2:{s:10:"objectData";s:6:"blabla";s:11:"Ainternal";s:10:"phpinfo();";}
During deserialization, internal
is set to "phpinfo();"
, which causes it to be executed when __destruct()
is called:
eval("phpinfo();");
This simple example executes phpinfo()
, but an attacker could inject any PHP code, leading to a complete compromise of the server.
Exploiting object injection using SQL injection
In some cases, the exploitation of an object injection is more subtle. Insecure deserialization can be used indirectly to inject malicious code elsewhere in the application.
Here, object A
contains a private username
attribute. The application then uses this object to construct an SQL query.
The username
attribute is used directly in an unprotected SQL query, making the application vulnerable to SQL injection.
An attacker could exploit this vulnerability by sending the following query:
https://application2.com?input=O:1:"A":1:{s:8:"username";s:15:"test1' OR 1=1--";}
This is interpreted as follows by the application:
SELECT * FROM users WHERE username = 'test1' OR 1=1--
The OR 1=1--
operator transforms the query into an always true query, which causes all the users in the database to be displayed.
These examples show how an object injection flaw can be exploited to:
- Execute arbitrary code via magic functions (
__destruct()
,__wakeup()
, etc.). - Bypass protections and inject malicious code into other parts of the application (such as an SQL query).
In real applications, these vulnerabilities can be more difficult to detect, particularly if the source code is not accessible. In-depth analysis is therefore required to identify these vulnerabilities and prevent them from being exploited.
How to Prevent Object Injection Vulnerabilities?
Attacks based on object injection demonstrate that deserializing objects from an untrusted source is extremely risky. If this mechanism is poorly mastered, it can lead to critical compromises, ranging from arbitrary code execution to SQL injections.
So how to prevent these attacks?
Avoid deserialization when not strictly necessary
Before implementing serialization/deserialization, it is essential to ask a fundamental question: Do we really need to deserialize objects?
In many cases, a more secure approach can be considered, such as:
- Using standardised data formats such as JSON or XML, with strict parsing.
- Storing only the necessary data rather than complete objects.
If deserialization is unnecessary or avoidable, it is preferable not to use it at all.
Check and filter data
If deserialization is really required, it is essential to validate each piece of data received:
- Use whitelists to restrict the classes that can be deserialized.
- Check data types and formats before use.
- Implement a strict validation mechanism for deserialized attributes.
In PHP, the allowed_classes
option in unserialize()
prevents arbitrary classes from being deserialized:
$data = unserialize($_GET['input'], ["allowed_classes" => false]); // Deserialization of native types only
If you need to deserialize objects, explicitly define which classes are allowed:
$data = unserialize($_GET['input'], ["allowed_classes" => ["MySecureClass"]]);
Avoid magic functions in serializable objects
Magic functions (__wakeup()
, __destruct()
, etc.) are the first targets of attackers. It is best to avoid implementing these methods in classes that are likely to be deserialized.
Always treat deserialized objects as user input
A common mistake is to believe that a deserialized object is ‘safe’.
Instead, it should be treated like any other potentially malicious user input.
Apply the usual best security practices:
- Never use
eval()
with deserialized data. - Properly escape data before using it (SQL, files, commands, etc.).
- Restrict access and permissions of instantiated classes.
Author: Renaud CAYOL – Pentester @Vaadata