0x55aa
โ† Back to Blog

Deserialization Attacks: How Pickled Data Can Pickle Your App ๐Ÿฅ’

โ€ข8 min read

Deserialization Attacks: How Pickled Data Can Pickle Your App ๐Ÿฅ’

You know what's worse than a SQL injection? A vulnerability that lets attackers literally run ANY code they want on your server. Welcome to the wild world of insecure deserialization! ๐ŸŽญ

As someone who's spent years building production systems and lurking in security communities, I can tell you: this vulnerability is sneaky, powerful, and way more common than you'd think.

What The Heck Is Deserialization? ๐Ÿค”

Think of serialization like freeze-drying food. You take a complex object (a steak) and turn it into something you can easily store or transport (freeze-dried steak bits).

Deserialization is the reverse - you take those bits and turn them back into a steak.

In programming:

  • Serialization: Convert an object โ†’ string/bytes (to save or send)
  • Deserialization: Convert string/bytes โ†’ object (to use again)

Sounds harmless, right? WRONG. ๐Ÿšจ

The Problem: Objects Can Execute Code

Here's the scary part: When you deserialize data, you're not just creating a passive object. In many languages, objects can have magic methods that run automatically during deserialization.

It's like ordering a pizza and it comes with a free burglar hiding in the box. ๐Ÿ“ฆ๐Ÿฅท

Real-World Example: The Cookie Monster Attack ๐Ÿช

Let me share a story from my early days. I was reviewing a Laravel app where someone thought it was clever to store user preferences in a serialized cookie.

The "clever" code:

// Storing user preferences
$preferences = serialize($userPrefs);
setcookie('prefs', $preferences);

// Loading them back
$preferences = unserialize($_COOKIE['prefs']);

What could go wrong? EVERYTHING.

An attacker can craft a malicious serialized object, stick it in the cookie, and boom - remote code execution when you call unserialize().

How Bad Can It Get? ๐Ÿ’ฃ

In my experience building systems with Laravel and Node.js, I've seen deserialization attacks lead to:

  1. Remote Code Execution (RCE) - Run ANY command on the server
  2. Data exfiltration - Steal your entire database
  3. Denial of Service - Crash the app with malicious objects
  4. Privilege escalation - Turn a regular user into admin

Real Talk: This is an OWASP Top 10 vulnerability. Companies like Equifax got hacked partly due to deserialization flaws. Not theoretical - VERY real!

Language-Specific Danger Zones ๐ŸŽฏ

PHP: The Unserialize Nightmare

BAD (never do this):

// User input goes STRAIGHT into unserialize. Yikes!
$data = unserialize($_POST['data']);

GOOD:

// Use JSON instead - it's just data, no code execution
$data = json_decode($_POST['data'], true);

// Or if you MUST use serialize, validate the hell out of it
$allowed_classes = ['User', 'Preference'];
$data = unserialize($input, ['allowed_classes' => $allowed_classes]);

Python: Pickle Is Not Your Friend

I learned this the hard way while working on a data pipeline project.

BAD:

# pickle.loads() can execute arbitrary code!
import pickle
data = pickle.loads(user_input)  # ๐Ÿ”ฅ DANGER ๐Ÿ”ฅ

GOOD:

# Use JSON for untrusted data
import json
data = json.loads(user_input)  # Safe! Just data.

# Or if you need complex objects, use safer alternatives
import jsonpickle
data = jsonpickle.decode(user_input)  # More restrictions

Node.js: node-serialize Gotcha

BAD:

const serialize = require('node-serialize');
// User controls this input? RCE incoming!
const obj = serialize.unserialize(req.body.data);

GOOD:

// Just use JSON.parse - it's built-in and safe
const obj = JSON.parse(req.body.data);

// For complex needs, validate schemas
const Ajv = require('ajv');
const ajv = new Ajv();
const validate = ajv.compile(schema);
if (validate(JSON.parse(data))) {
    // Safe to use
}

Java: The Serialization OG

Java's been dealing with this since the '90s!

BAD:

// Classic vulnerability
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject();  // Dangerous!

GOOD:

// Use a whitelist of allowed classes
class SafeObjectInputStream extends ObjectInputStream {
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException {
        if (!allowedClasses.contains(desc.getName())) {
            throw new InvalidClassException("Unauthorized class");
        }
        return super.resolveClass(desc);
    }
}

The Laravel Way: How I Handle It ๐Ÿ›ก๏ธ

In my Laravel projects, I follow these rules religiously:

// โŒ NEVER deserialize user input
$bad = unserialize($request->input('data'));

// โœ… Use JSON for data transfer
$good = $request->json('data');

// โœ… Laravel's signed cookies (can't be tampered with)
Cookie::make('prefs', json_encode($data))->withSigning();

// โœ… Encrypted cookies for sensitive stuff
Cookie::make('session', json_encode($data))->withEncryption();

Pro Tip: Laravel encrypts session data by default. Use that! Don't roll your own serialization for sessions.

Red Flags: When To Worry ๐Ÿšฉ

Working with various teams, I've learned to spot these danger patterns:

  1. Seeing unserialize(), pickle.loads(), readObject() in code

    • Especially if the input comes from users!
  2. Serialized data in cookies or URLs

    • Why would you do this?! Use JSON!
  3. "Trust me, only admins can access this endpoint"

    • Authorization bugs happen. Defense in depth!
  4. Custom serialization formats

    • Unless you're a crypto expert, stick to JSON

How To Protect Yourself โœ…

From my years in production environments, here's what works:

1. Just Use JSON

Seriously. 99% of the time, JSON is enough.

// Instead of serialize/unserialize
json_encode($data);
json_decode($data, true);

Why it's safe: JSON is pure data. No code execution, no magic methods.

2. If You MUST Deserialize...

Use strict allowlists:

// PHP 7+
$data = unserialize($input, [
    'allowed_classes' => ['App\Models\User']
]);
# Python: Use safer alternatives
import jsonpickle
jsonpickle.set_encoder_options('json', sort_keys=True)

3. Sign/Encrypt Serialized Data

// Laravel makes this easy
$encrypted = Crypt::encryptString(serialize($data));
$data = unserialize(Crypt::decryptString($encrypted));

If attackers can't modify the serialized data, they can't inject malicious objects.

4. Input Validation & Type Checking

// Validate structure before deserializing
$validator = Validator::make($request->all(), [
    'data' => 'required|json|max:1000',
]);

$data = json_decode($request->input('data'), true);

// Check it's what you expect
if (!isset($data['name']) || !isset($data['email'])) {
    abort(422, 'Invalid data structure');
}

The Security Community Perspective ๐Ÿ”

In security communities like the ones I'm active in, deserialization attacks are considered "high severity" because:

  • Easy to exploit once you find the vuln
  • Hard to detect in code reviews (looks innocent)
  • Critical impact - often leads to RCE
  • Common in legacy code - PHP especially

Fun fact: There's even a tool called "ysoserial" that generates malicious Java serialization payloads. Hackers LOVE this vulnerability.

Testing Your App ๐Ÿงช

Here's what I do in production:

# 1. Search your codebase for danger functions
grep -r "unserialize\|pickle.loads\|readObject" .

# 2. Check dependencies
composer show --direct  # PHP
pip list               # Python
npm ls --depth=0       # Node.js

# Look for packages with known deserialization issues

# 3. Use security scanners
# For PHP:
./vendor/bin/phpstan analyse
./vendor/bin/psalm

# For Python:
bandit -r .

# For Node:
npm audit

Real-World War Story ๐ŸŽ–๏ธ

Last year, I was doing code review for a team migrating from an old PHP app. They had this "clever" caching mechanism:

// Store rendered HTML in cache (serialized for "efficiency")
$cache->put($key, serialize($renderedView));

// Later...
echo unserialize($cache->get($key));

I nearly fell off my chair. The cache was Redis - accessible to multiple services. An attacker compromising ANY service with Redis access could inject malicious serialized objects.

The fix: Changed it to just store the plain HTML string. No serialization needed!

// Much better
$cache->put($key, $renderedView);
echo $cache->get($key);

Sometimes the best security fix is removing "clever" code. ๐Ÿ˜…

Your Action Plan ๐Ÿ“‹

Do these NOW:

  1. โœ… Search codebase for deserialization functions
  2. โœ… Replace with JSON where possible
  3. โœ… Add allowlists if you must deserialize
  4. โœ… Encrypt/sign serialized data in storage
  5. โœ… Never deserialize user-controlled data
  6. โœ… Update dependencies (old libs = known exploits)
  7. โœ… Add input validation before deserialization

The Bottom Line ๐ŸŽฏ

Deserialization attacks are scary because:

  • They turn data processing into code execution
  • They're hard to spot in code reviews
  • They often lead to complete server compromise
  • They're in the OWASP Top 10 for a reason!

Protection is simple:

  • Default to JSON (it's just data)
  • Never trust serialized input from users
  • Use framework protections (Laravel's encrypted cookies, etc.)
  • Validate EVERYTHING before deserializing

Think of it this way: Accepting serialized data from users is like accepting a "gift" package from a stranger at the airport. Just don't do it! ๐ŸŽ๐Ÿ’ฃ


Got security questions? Hit me up on LinkedIn or check out my projects on GitHub. As someone passionate about security and active in security communities, I love discussing this stuff!

Want more security deep-dives? Follow this blog! Next up: I'm thinking about diving into prototype pollution attacks in JavaScript. ๐ŸŽญ

Stay secure out there! Remember: JSON is your friend, pickles are for sandwiches! ๐Ÿฅ’โœจ