Deserialization vulnerabilities in Java
Understanding serialization and deserialization vulnerabilities is the first step toward building secure applications. For most developers, it’s a challenge to find the right balance between coding securely and meeting other objectives like tight deadlines. This often results in products that are vulnerable to deserialization attacks which would be otherwise difficult to stage.
What is serialization and deserialization?
Serialization is a process that involves converting the state of an object into a byte of streams to store it in a database, file, memory buffer, or transfer over a network. Deserialization, on the other hand, is the reverse of the serialization process, and involves recreating a clone of the original object from the byte stream.
What are its main effects, and why is it important?
Deserialization and serialization are undoubtedly fundamental processes, especially when working with APIs and microservices. These two procedures allow us to serialize objects into streams, transfer them over a network, and recreate them if need be.
Serialization is also an essential part of implementing Remote Method Invocation (RMI) using Java, allowing developers to build highly distributed applications that invoke methods in remote Java objects. Serialization is synonymous with marshaling in Ruby and Pickling in Python, with each language offering native support and different methods to serialize objects.
But serialization and deserialization are not without their shortcomings, especially when trying to implement encapsulation to the letter. When using encapsulation, we aim to hide the implementation details of methods and objects. Encapsulation is also known to make our programs safer and help us easily write more elegant code.
On the other hand, serialization violates the benefits of encapsulation by potentially exposing our objects’ internal state and implementation details. When performing serialization to recreate objects in a different computing environment, we cannot interfere with the state of the objects. Any attempt to modify the state of objects poses the risk of failing to recreate a perfect clone of the objects from the byte stream.
Serialization in Java – Java serialization format
In Java, serialization is also the process of writing the state of an object into a byte stream, while deserialization is the exact opposite, as defined earlier. Java provides native binary serialization, and as such you do not need any external library to serialize or deserialize data.
However, to serialize an object, the parent class should implement the marker interface, Serializable. This provides run-time information to the JVM about the objects we want to serialize using the ObjectOutputStream and the ObjectInputStream classes. The ObjectOutputStream class provides the writeObject() method, which allows you to serialize objects into the desired stream. The ObjectInputStream class also provides you with the readObject() method to read and recreate an object from the byte stream.
The OWASP deserialization cheat sheet recommends using pure data formats such as JSON and XML to reduce the chances of a deserialization attack. Many factors need to be considered when choosing a suitable format, such as readability, speed factor, storage concerns, and the complexity of the data.
JSON and XML are two of the most commonly used formats. The former is used extensively due to its readability, extensibility, and simplicity, while the latter is preferred for editability and nested textual format. Other popular formats include YAML, BSON, and CSV.
Attack vectors – how attackers can use deserialization to exploit systems and networks
While both serialization and deserialization are generally useful processes that allow you to safely transfer data, deserialization is notorious as a target for attackers looking to execute malicious attacks. Insecure deserialization can lead to a DoS attack when data is corrupt, or to remote code execution attacks in more aggravated scenarios. The results of such attacks may be unauthorized access to resources or manipulation of the application’s underlying code.
In most scenarios, insecure deserialization arises when developers naively deserialize user input, posing a severe security risk. Modern applications tend to make heavy use of external libraries, and attackers can leverage vulnerabilities in these libraries to chain an attack that will eventually lead to a deserialization attack in your application.
Another common mistake that makes systems open to deserialization attacks is overconfidence in data integrity, or running integrity checks when data has already been serialized. Attackers can exploit this and create hostile and corrupt objects, which make your system vulnerable when deserialized. This may result in a full-fledged deserialization attack setting your application up for arbitrary remote code execution or data corruption.
Although deserialization is somewhat difficult to execute, that doesn’t mean it’s impossible. In the past, insecure deserialization was listed in the OWASP Top Ten, where the following attack scenarios were listed:
Scenario #1: A React application calls a set of Spring Boot microservices. Being functional programmers, they tried to ensure that their code was immutable. The solution they came up with is serializing the user state and passing it back and forth with each request. An attacker notices the “R00” Java object signature and uses the Java Serial Killer tool to gain remote code execution on the application server.
Scenario #2: A PHP forum uses PHP object serialization to save a “super” cookie, containing the user’s user ID, role, password hash, and other state:
An attacker changes the serialized object to give themselves admin privileges:
Identifying insecure deserialization
It is challenging to execute a successful deserialization attack; and it is equally challenging to identify one. Insecure deserialization can be identified using black-box or white-box testing techniques. Methodologies such as black-box penetration testing allow pentesters to test the serialized payload for the presence of any abnormalities that can lead to an attack. Tools such as Ysoserial can be used to generate serialized payloads. Nevertheless, you should be able to identify serialized data before you can begin to trace insecure deserialization.
Developers with adequate technical knowledge can also spot vulnerabilities simply by looking at certain key functions used during deserialization/serialization that can be exploited. Alternatively, by using a secure deserialization library you can test all the classes and safelist only the properly configured classes (objects) that will be allowed to deserialize. Monitoring tools such as Detectify return notifications when vulnerable components are deserialized.
Possible ways to exploit this vulnerability
There are multiple ways to exploit deserialization vulnerabilities. One commonly used approach is to identify the serialized data and make changes to attribute values. Once you have completed the necessary modifications to a target object, you can pass this malicious object for deserialization. It is relatively difficult to identify serialized data when using the binary serialization format, particularly in Java. However, patterns such as AC ED 00 05 in Hex and rO0 in Base64 could suggest that the data is serialized.
Besides simply editing objects, it is also possible to exploit insecure deserialization by injecting malicious objects into available serializable classes. This is possible because deserialization methods such as readObject() in Java do not know how much data they will read, and thus cannot determine the number of objects to expect. Attackers are also known to exploit ‘magic methods’ to gain control of member fields of objects.
The exploitation of deserialization vulnerabilities can also be done through gadget chains. It is possible to invoke methods that can pass dangerous input into another gadget by chaining multiple gadgets together. The process usually begins with the attacker passing malicious input into the kicker-off gadget during deserialization. The input is then sequentially passed into other gadgets and finally into the sink-gadget. Alternatively, you can also use pre-built gadget chains such as “Ysoserial” to detect and exploit deserialization vulnerabilities.
Best practices to prevent and avoid exploitations
- One of the best and most basic preventative actions is to avoid deserializing data from unknown sources. Suppose you have to use data from an untrusted source. In that case, you can use digital signatures to run integrity tests before deserializing data to ensure that it has not been tampered with.
- It is recommended that you keep all libraries up to date because updates often include security fixes, not just new or enhanced features.
- Vulnerability databases are an essential source of information on workarounds for deserialization vulnerabilities and other new vulnerabilities and how to mitigate them. Some of these are the ISS X-Force database and the National Vulnerability Database.
- Unless absolutely necessary, avoid deserializing user input and validate the user and the data before deserializing.
- Monitor and restrict networks from serialization servers and containers.
- Monitor and log exceptions that may occur during deserialization.
- Make sure your team is well trained to identify and mitigate security issues by training with platforms such as Avatao.
- You can also use Ysoserial, a proof of concept tool that allows you to exploit deserialization vulnerabilities and fix them before a malicious actor finds them.
A deserialization attack can significantly impact your company’s operations and the business’s reputation if customer data is disclosed. As organizations handle more and more data, and as more tools are developed to detect this vulnerability, there’s no doubt that deserialization attacks will be prevalent in the future as well. Therefore, organizations need to prioritize training their developers to code more securely and to use the right tools to combat these attacks.
Share this post on social media!
Every year, Ruby is becoming more and more popular thanks to its elegance, simplicity, and readability. Security, however, is an issue we can’t afford to neglect.
Python is a high-level, flexible programming language that offers some great features. To be as effective as possible, it is important to possess the knowledge to make the most out of coding with Python.
For most companies, security is considered a side quest, which is partly related to the daily processes. In reality, security ought to be a strong foundation of any organization. To ensure the defense of the enterprise, the relevant teams need strong security knowledge and abilities.
Copyright © 2022 Avatao