vuln-class-deserialization
Insecure Deserialization, in plain English
When an application reads structured data sent over the network and the attacker can shape that data, "reading" can quietly turn into "running attacker-supplied code". The bug class behind dozens of headline RCEs.
What this is
Many applications send structured data over the network and then “read it back” on the other end. The technical word for “read it back” is deserialize. When the data is internal (a cache, a queue, a service-to-service call), that is fine. When the data arrives from somewhere the attacker can influence (a cookie, an HTTP header, a JSON Web Token, a request body), how the application reads it back becomes a security question.
The core issue: in many languages, “deserializing” is not just
“fill in fields”. The reader instantiates classes, calls
constructors, and runs hooks like readObject or __wakeup. If
the attacker can choose which classes get instantiated, they can
pick classes whose constructors trigger side effects: opening
files, executing shell commands, loading remote code. A chain of
such classes is called a “gadget chain”, and chains exist for
almost every popular runtime.
Where it happens
The languages and serializers most commonly involved:
- Java. The canonical home of deserialization vulnerabilities.
ObjectInputStream.readObject()plus the Apache Commons Collections library, or any of dozens of others on the classpath, has caused breaches at PayPal, Cisco, Jenkins, WebLogic, Confluence, and more. - Python
pickle. The documentation explicitly says “do not unpickle data received from untrusted sources”. Despite that,picklestill shows up in API endpoints, caches, and message queues regularly. - .NET
BinaryFormatter,NetDataContractSerializer,LosFormatter. Microsoft has formally deprecated all of these for security reasons. Legacy ASP.NET ViewState is the largest install base. - PHP
unserialize(). Same gadget-chain dynamic, attacking the many PHP frameworks (Laravel, Symfony, WordPress) by chaining classes loaded by Composer’s autoloader. - Ruby
Marshal.loadandYAML.loadwith default options. Rails CVEs every couple of years trace back to this. - Node.js is mostly safe by default, since JSON does not have
this problem, but a few unfortunate libraries (
node-serialize, olderserialize-javascriptversions) reproduce the issue.
What an attacker does
Once a deserialization bug is reachable, the rest is short:
- Identify the runtime (often visible in error pages or response headers).
- Pick a published gadget chain for that runtime plus the
libraries on the application’s classpath.
ysoserial(Java) andmarshalsec(Java) are the canonical tools; equivalents exist for .NET (ysoserial.net) and PHP (PHPGGC). - Generate the payload with a one-liner that runs whatever command the attacker wants.
- Deliver the payload at the deserialization point: Cookie, JWT, HTTP body, RMI port, JMX endpoint.
- Receive a reverse shell.
Severity for deserialization-driven remote code execution typically lands at 9.8 out of 10. EPSS scores for known-public chains stay high for years, because the same exploit binary works against every vulnerable instance.
How it gets fixed
The single best fix is don’t deserialize untrusted data with formats that allow arbitrary classes.
Replace the dangerous serializers with safe alternatives:
| Dangerous | Safer alternative |
|---|---|
Java ObjectInputStream |
JSON (Jackson configured strictly), Protobuf, Avro |
Python pickle |
JSON, msgpack with strict types |
.NET BinaryFormatter |
System.Text.Json, MessagePack-CSharp |
PHP unserialize |
JSON via json_decode() |
Ruby YAML.load |
YAML.safe_load, JSON |
JSON is not magic. There are also “polymorphic deserialization”
patterns in Jackson and Gson that can re-introduce the
class-instantiation risk. Use the strict configuration (no
enableDefaultTyping, no @JsonTypeInfo with class-name
resolution).
If you genuinely cannot replace the serializer (for example a legacy protocol you don’t control), apply layered defences:
- HMAC-sign the serialized blob with a server-side secret and reject anything that doesn’t validate. The attacker can no longer alter the bytes.
- Constrain class instantiation. Java’s
ObjectInputFilter(since JDK 9) lets you allowlist exactly which classes are accepted. - Patch every library on the classpath that has a known
gadget chain. The usual suspects are
apache-commons-collections,spring-core,snakeyaml, andehcache.
How blueredix surfaces deserialization findings
The scanner flags CVEs in third-party software whose description mentions deserialization, and runs templates that probe for known-vulnerable endpoints (Spring Cloud Gateway, Atlassian, WebLogic) where a specific exploit is well-understood. Findings link to the relevant CVE and back to How to read a CVE.
We don’t try to deserialize attacker payloads against your application. That is penetration-testing territory.