Understanding Insecure Deserialization: Risks and Mitigations

Saniye Nur
10 min readMay 23, 2024

--

User-provided input has long been a significant source of vulnerabilities, presenting ongoing risks across many platforms and applications. Leveraging user input, from SQL injection to cross-site scripting, is a well-documented issue in securing web applications. Another less commonly understood but equally serious vulnerability related to user input is insecure deserialization.

Insecure deserialization attacks occur when an application trusts serialized data sufficiently to use it without verifying its integrity. This misplaced trust can result in severe consequences, as attackers can manipulate serialized objects to perform remote code execution, gain elevated privileges, or cause denial-of-service attacks. Such vulnerabilities are common in applications that serialize and deserialize complex data structures in various programming environments, like Java, .NET, and PHP, which often rely on serialization for remote procedure calls, session management, and other purposes.

Learning Objectives

In this article, you will thoroughly understand the following basic concepts:
The serialization and deserialization process
Potential threats to web applications
Exploitation methods
Prevention strategies
Mitigation strategies

  • Insecure deserialization is a security vulnerability that occurs when untrusted data is used to abuse the logic of an application by manipulating serialized objects. This can lead to various types of attacks, such as remote code execution (RCE), denial of service (DoS), and privilege escalation. To understand insecure deserialization and how to mitigate it, let’s break it down:

What is Serialization and Deserialization?

  • Serialization is the process of converting an object into a format that can be easily stored or transmitted (e.g., JSON, XML, binary).
  • Deserialization is the reverse process of converting the serialized data back into an object.

Why is Insecure Deserialization Dangerous?

When deserialization occurs without proper validation and control, an attacker can manipulate the serialized data to execute arbitrary code, escalate privileges, or disrupt the normal operation of an application. This is especially dangerous if the application performs deserialization on data received from untrusted sources.

Common Attack Scenarios

  1. Remote Code Execution (RCE): By crafting malicious serialized objects, an attacker can inject code that gets executed during the deserialization process.
  2. Denial of Service (DoS): An attacker can create data that causes excessive processing, leading to resource exhaustion and service downtime.
  3. Access Control Bypass: By altering the serialized data, an attacker might gain unauthorized access to sensitive functionalities or data.
  4. Privilege Escalation: Attackers can manipulate serialized data to gain unauthorized access to sensitive functions or data.

Example of Insecure Deserialization in Java

Consider a Java application that deserializes objects from user input without proper validation:

import java.io.*; // Import necessary classes for input/output operations

public class DeserializationExample {
public static void main(String[] args) {
try {
// Initialize FileInputStream to read bytes from the file “data.ser”
FileInputStream fileIn = new FileInputStream(“data.ser”);

// Wrap FileInputStream with ObjectInputStream to deserialize the bytes into an object
ObjectInputStream in = new ObjectInputStream(fileIn);

// Deserialize the object from the input stream and cast it to MyObject
MyObject obj = (MyObject) in.readObject();

// Close the ObjectInputStream
in.close();

// Close the FileInputStream
fileIn.close();

// Process the object (this is where you would add your processing logic)
} catch (IOException | ClassNotFoundException e) {
// Handle IO and ClassNotFound exceptions
e.printStackTrace(); // Print stack trace for debugging purposes
}
}
}

// A class that implements Serializable, which allows its instances to be serialized
class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
// Object properties and methods go here
}

The serialisation don’t allow only saving to a byte stream file.

In this example, if an attacker provides a malicious data.ser file, they could manipulate the deserialized object to execute harmful actions within the application.

Specific Incidents Involving Serialization Vulnerabilities

Let’s explore specific cases where serialization vulnerabilities were key factors in cybersecurity breaches or attacks, underlining the necessity of secure serialization practices. These incidents show how attackers exploit serialization flaws to achieve remote code execution, data leakage, and more.

Log4j Vulnerability CVE-2021–44228

Incident: The Log4j vulnerability, also known as Log4Shell, is a severe security flaw in the Apache Log4j 2 library, a popular logging tool used in Java applications. This vulnerability allows remote attackers to run arbitrary code on affected systems by exploiting the library’s insecure deserialization function.

Impact: This vulnerability enabled remote code execution, allowing attackers to run arbitrary commands on compromised systems. As a result, attackers could infiltrate critical infrastructure, leading to unauthorized access to sensitive information, service disruptions, and potential supply chain attacks.

WebLogic Server Remote Code Execution CVE-2015–4852
Incident: This vulnerability was related to the way Oracle WebLogic Server deserialized data sent via the T3 protocol. Attackers could transmit maliciously crafted objects to the server, which, when deserialized, led to remote code execution.

Impact: This vulnerability was extensively exploited to gain unauthorized access to systems, deploy ransomware, or steal data. It affected all versions of WebLogic Server that had not disabled the vulnerable service or applied the necessary patch.

Jenkins Java Deserialization CVE-2016–0792
Incident: Jenkins, a widely used automation server in software development, faced a critical vulnerability involving Java deserialization. Attackers could send specially crafted serialization payloads to the Jenkins CLI, which, when deserialized, allowed arbitrary code execution.

Impact: This flaw enabled attackers to execute shell commands, potentially taking over the Jenkins server. Given Jenkins’ broad access to the software development environment, this included source code, build systems, and potentially deployment environments.

Serialization Across Different Programming Languages

Serialization involves converting an object’s state into a format that can be easily stored or transmitted and later reconstructed. Different programming languages use various keywords and functions for serialization, but the underlying principle remains consistent. Whether using Java, Python, .NET, or PHP, each language implements serialization to leverage specific features and security measures inherent to its environment.

Unlike other vulnerabilities that exploit the immediate processing of user inputs, insecure deserialization involves a deeper interaction with the application’s core logic, often manipulating the fundamental behavior of its components.

Let’s examine how serialization is handled in different languages, focusing on its functionality, syntax, and unique features.

PHP Serialization
In PHP, serialization is performed using the serialize() function. This function transforms a PHP object or array into a byte stream that represents the object’s data and structure. This byte stream can encompass various data types, including strings, arrays, and objects, making it versatile. For example, let’s consider a notes application where users can save and retrieve their notes. We’ll create a PHP class called Notes to represent each note and manage serialization and deserialization.

class Notes {
public $content;

public function __construct($content) {
$this->content = $content;
}
}

In our notes application, when a user saves a note, we serialize the Notes class object using PHP's serialize() function. This action converts the object into a string representation that can be stored in a file or database. Here's a code snippet that serializes the Notes class object:

$note = new Notes(“Welcome to Medium”);
$serialized_note = serialize($note);

Python Serialization

In Python, we can consider the process of deserializing serialized data obtained from a data file. For example, we can proceed with a notepad application.

import pickle
import base64

# Serialize (Pickling)
notes = {‘title’: ‘My Notes’, ‘content’: ‘This is my first note.’}
serialized_notes = pickle.dumps(notes)
encoded_notes = base64.b64encode(serialized_notes).decode(‘utf-8’)

# Simulate sending serialized data over the network or storing in a file
# Let’s assume encoded_notes is transmitted or saved somewhere

# Deserialize (Unpickling)
received_encoded_notes = encoded_notes # Simulated received serialized data
received_serialized_notes = base64.b64decode(received_encoded_notes)
received_notes = pickle.loads(received_serialized_notes)

print(received_notes)

In this code, we first create a notepad data and serialize it using the pickle module. Then, we encode this serialized data into a string by using base64 encoding. This string can be thought of as transmitted over a network or saved to a file.

During deserialization, we decode the received encoded data using base64 to retrieve the original serialized data. Finally, we use the pickle.loads() function to convert this serialized data back into a Python dictionary object.

This example provides a simple demonstration of deserializing serialized data in Python. In real-world scenarios, deserialization processes can be used in various situations such as data exchange or state management. However, it’s crucial to handle them carefully due to security reasons. Serialized data from untrusted sources should be processed securely and properly validated.

To exemplify deserialization (unpickling) in Python, let’s continue with the note-taking application scenario. After receiving serialized data encoded in base64, the application needs to deserialize it to reconstruct the original Python object. Here’s how it’s done:

import pickle
import base64

# Simulated received serialized data
received_encoded_notes = “b’gANjcGllay5OdG9zCnEAKYFxAX1xAihYBgAAAG5vdGVzX3JlcXVlc3EDShjZG5vdGVzIHRvIGJlIHN0b3JlZCB0byByZXN1bHQgaW4gYSA2NC1iaXQgaW5jbHVkaW5nIGVuY29kaW5nLiBBbWF6b24gU3BhY2VzIHN1Ym1pdCBhcHBlYXJhbmNlcyBhbmQgY29ycmVzc2l2ZSB0byBtZSBoZWxwIG9yIHN0b3JlIHNlbmRpbmcgcGFnZSBvZiBlcXVhbC1zZW5kaW5nLg==’”

# Base64 decoding
received_serialized_notes = base64.b64decode(received_encoded_notes)

# Unpickling
received_notes = pickle.loads(received_serialized_notes)

print(received_notes)

In this code snippet:

  1. Base64 Decoding: The received base64-encoded string is decoded back into binary format using base64.b64decode(). This step reverses the encoding process and retrieves the original binary serialized data.
  2. Unpickling: The binary data, representing the serialized Notes object, is passed to pickle.loads(). This function reconstructs the original Python object from the binary stream, effectively performing deserialization.
  3. Displaying or Using the Deserialized Data: Once deserialization is complete, the application can access and utilize the deserialized data as needed. In this example, received_notes should contain the reconstructed Notes object, ready for further processing or display.

By following these steps, the application can safely deserialize serialized data received from external sources, ensuring compatibility with communication protocols and maintaining data integrity throughout the process.

Exploitation — Object Injection

Insecure deserialization is a vulnerability that occurs when an application deserializes data from an untrusted or manipulated source without proper validation, leading to potential security risks. One common form of exploitation associated with insecure deserialization is called “Object Injection.”

Object Injection occurs when an attacker manipulates serialized objects to inject malicious code or objects into the application during deserialization. This manipulation can lead to various attacks, including remote code execution, unauthorized access to sensitive data, or denial of service.

Here’s how Object Injection typically works:

1. Serialized Objects: Serialized objects contain data structures that represent the state of objects in the application. These serialized objects are usually stored or transmitted in formats like JSON, XML, or binary serialization.

2. Deserialization Process: When the application deserializes these objects, it reconstructs them into their original form based on the serialized data. During this process, the application may instantiate objects and execute any initialization code defined within them.

3. Manipulation by Attackers: Attackers exploit insecure deserialization by tampering with the serialized data to insert malicious objects or payloads. They may modify object properties, inject unexpected class names, or include code snippets disguised as legitimate data.

4. Execution of Malicious Code: Once the manipulated data is deserialized, the application may unwittingly execute the attacker’s code or instantiate malicious objects. This can result in various harmful consequences, such as executing arbitrary commands on the server, bypassing authentication controls, or escalating privileges.

5. Impact: The impact of Object Injection can be severe, depending on the context and functionality of the application. It can lead to unauthorized access to sensitive data, compromise of system integrity, and complete takeover of the affected system.

To mitigate the risks associated with Object Injection and insecure deserialization in general, it’s essential to implement robust security measures such as input validation, using secure deserialization libraries, applying whitelisting of allowed classes, monitoring deserialization activities, and keeping software dependencies up to date. By addressing these vulnerabilities, organizations can effectively reduce the likelihood of exploitation and protect their applications from malicious attacks.

Mitigation Strategies

  1. Validate and Sanitize Input:
  • Ensure that any data being deserialized originates from a trusted and authenticated source.
  • Implement thorough input validation to reject any unexpected or malicious input before deserialization.
  • Sanitize input data by removing any potentially harmful characters or payloads to prevent injection attacks.

2. Use Safe Deserialization Libraries:

  • Choose deserialization libraries that come with built-in security features to mitigate common vulnerabilities.
  • Look for libraries that provide features like input validation, output encoding, and secure default settings.
  • Regularly assess the security of chosen libraries and consider switching to more secure alternatives if necessary.

3. Implement a Whitelist:

  • Restrict deserialization to only classes or objects that are explicitly trusted and necessary for application functionality.
  • Maintain a whitelist of allowed classes or object types and reject any attempts to deserialize unauthorized data structures.
  • Regularly review and update the whitelist to ensure it reflects the current requirements and security posture of the application.

4. Monitor and Log Deserialization Activities:

  • Implement comprehensive logging mechanisms to track all deserialization activities within the application.
  • Monitor deserialization logs for any suspicious or unexpected behavior, such as attempts to deserialize unauthorized data or unusual patterns of usage.
  • Set up alerts or triggers to notify administrators of potential security incidents related to deserialization.

5. Apply Patching and Updates:

  • Regularly update deserialization libraries, frameworks, and dependencies to patch known vulnerabilities and security flaws.
  • Stay informed about security advisories and updates released by library maintainers and promptly apply relevant patches to the application.
  • Consider automating the process of vulnerability scanning and patch management to ensure timely updates and reduce the risk of exposure to known vulnerabilities.

By following these strategies, you can significantly reduce the risk of insecure deserialization in your applications.

From the perspective of a red teamer or pentester, it’s crucial to thoroughly inspect the application’s serialization methods to pinpoint potential vulnerabilities. This involves a meticulous review of the codebase to uncover any areas where deserialization or serialization occurs. Additionally, employing static analysis tools can help identify insecure deserialization risks, such as inadequate input validation or reliance on outdated libraries.

Dynamic analysis techniques, including fuzzing, are also valuable for generating unexpected input data to test how the application responds. During runtime, dynamic analysis tools can be used to monitor the application’s behavior, shedding light on any vulnerabilities that may arise.

Furthermore, assessing how the application handles errors during deserialization is essential. Examining error messages and stack traces can reveal valuable system details that attackers might exploit.

From the perspective of secure coding practices, it’s essential to steer clear of inherently insecure serialization formats like Java serialization. Instead, opt for safer alternatives like JSON or XML, coupled with robust validation mechanisms.

Avoiding functions like eval() and exec() is critical, as they can execute arbitrary code and introduce significant security risks. Implement stringent input validation to only accept expected data and utilize output encoding techniques to sanitize data before serialization.

References:

--

--