Modern Deserialization Attacks in 2025: How Gadget Chains Still Break Java, .NET, and PHP Apps
When you think about the “scariest” web vulnerabilities, you probably imagine RCE, SSRF, or some fancy cloud takeover. But there’s one bug category that quietly sits underneath all of this—one that has caused some of the biggest breaches in the past decade—insecure deserialization.
And guess what?
Even in 2025, it’s still popping up everywhere, especially in large enterprise stacks running Java, .NET, and PHP.
This article breaks down modern deserialization attacks in a human, practical, bug-bounty-friendly way. No academic fluff. Just how the attacks actually work, how researchers build gadget chains, and how you can detect and prevent them in real systems.
1. What Exactly Is Deserialization? (The Two-Line Explanation)
Applications often need to save complex objects—user profiles, session data, tokens, carts, preferences—into a transportable format. So they serialize them (convert to bytes/json/xml). Later, they deserialize them to rebuild the object in memory.
This is normal.
The danger comes when the app trusts untrusted data and passes it straight into the deserializer.
If an attacker can control that input, they can create objects that trigger dangerous code paths—sometimes even full remote code execution (RCE).
2. Why Is Deserialization Still a Problem in 2025?
Because enterprise codebases:
-
run old libraries,
-
depend on massive frameworks with hidden object classes,
-
allow user-controlled XML/JSON/Binary input,
-
use outdated “blacklisted class” filters,
-
and often assume “serialization is safe because we handle internal data.”
But “internal only” quickly becomes “external input” when:
-
a proxy flows data from user → microservice → legacy backend,
-
session objects get stored client-side,
-
API gateways simply forward serialized payloads,
-
developers expose “debug endpoints” or message brokers.
Attackers love complexity, and deserialization is complexity on steroids.
3. The Secret Weapon: Gadget Chains
A gadget is simply:
A method in an existing class that performs a sensitive action when an object is deserialized.
When you chain multiple classes together—each triggering another—you build a gadget chain.
Think of a gadget chain like a domino line:
-
You send a serialized object.
-
During deserialization, it's
readObject()method runs. -
That method triggers another class’s method.
-
That method triggers something dangerous.
-
At the end of the chain:
RCE, file writes, SSRF, or logic abuse.
Modern attacks no longer require injecting “evil classes.” Instead, the attacker uses classes already present in the server’s classpath.
That’s why even up-to-date servers can be vulnerable.
4. Real Examples From Java, .NET, and PHP
4.1 Java: The King of Deserialization Bugs
Java deserialization has been abused for years because:
-
Java apps use many libraries (Spring, Struts, Jackson, Commons-Collections, Groovy, XStream, etc.).
-
Each library provides thousands of classes.
-
Many of these classes have “magic” methods:
readObject(),readResolve(),finalize(),toString()…
The most famous chain is:
commons-collections → InvokerTransformer → Runtime.exec()
Even though some vendors patched it, variations still exist in:
-
Commons-Beanutils
-
Spring-core
-
Groovy
-
XStream
-
JBoss
-
WebSphere/ColdFusion style servers
In real-world bug bounty cases, you’ll often find:
-
JSON parsers accepting polymorphic types (
@class) -
XML parsers allowing external entity loading
-
Message brokers deserializing Java objects from queues
-
API gateways forwarding serialized binaries
4.2 .NET: Dangerous Auto-Binding & BinaryFormatter
By 2025, Microsoft will officially discourage using:
-
BinaryFormatter -
NetDataContractSerializer -
LosFormatter
But enterprise apps still use them, especially legacy ASP.NET systems.
Popular exploit features include:
-
ObjectDataProvider(WPF) -
Process.Start()triggers -
TypeConfuseDelegate gadget chains
Even new .NET 6/7 apps sometimes use insecure patterns when migrating old code.
4.3 PHP: The Magic of __wakeup() and unserialize()
PHP deserialization is simpler but just as dangerous.
When you call:
You’re basically telling PHP:
“Instantiate any class you want and run its magic methods.”
PHP gadgets use:
-
__wakeup() -
__destruct() -
__call()
Laravel, Symfony, SwiftMailer, and WordPress plugins have all had real-world gadget chains that allowed:
-
file deletion
-
SSRF
-
log poisoning → RCE
-
arbitrary file write via PHAR deserialization
5. How Hackers Actually Build Gadget Chains (Step-by-Step)
Let’s make this extremely clear and human:
Step 1: Identify an Untrusted Deserialization Sink
Examples:
-
/api/parseBinary -
/debug/restoreSession -
/import/settings -
AMQP/Kafka message consumers
-
unserialize()in PHP logs/metadata
You detect these using:
-
Burp Suite passive scanning
-
source code search (“BinaryFormatter”, “readObject”, “unserialize”)
-
fuzzing with invalid serialized blobs
Step 2: Enumerate Libraries in Classpath or File System
You look for common libraries:
-
Commons-Collections
-
Groovy
-
Spring
-
XStream
-
Fastjson
-
Jackson
In PHP:
-
Laravel
-
Symfony
-
PHPUnit
-
WordPress plugins
Often, you can fingerprint them by error messages.
Step 3: Use Existing Gadget Chains
There are public exploit chains:
-
ysoserial (Java)
-
ysoserial.net (.NET)
-
phpggc (PHP)
Example for Java:
You embed this payload into a request—if the app is vulnerable, the command runs.
Step 4: Build Custom Chains When Public Ones Fail
Modern apps patch known gadgets.
So researchers hunt for new gadgets, usually by:
-
scanning for classes with “magic” methods,
-
identifying methods that execute commands, write files, open URLs, or load templates,
-
chaining them based on the constructor → method → destructor flows.
Automation tools like GadgetInspector help map potential chains.
6. Detection: How To Know If the App Is Vulnerable
1. Look for Deserialization Exceptions
Send a corrupted payload (random bytes).
If the server throws:
-
StreamCorruptedException -
InvalidClassException -
SerializationException
…it’s likely deserializing something.
2. Look for Polymorphic Type Hints in JSON
If JSON uses something like:
You’re dealing with a dangerous parser (Jackson/Fastjson style).
3. Identify Magic Methods in Code
Search /src for:
-
readObject -
readResolve -
finalize -
__wakeup() -
__destruct()
4. Use safe probing payloads
Each ecosystem has “benign gadget chains” that don’t execute commands but prove invocation (e.g., DNS ping).
7. Mitigation: What Actually Works
Here’s the frustrating truth:
Blacklists do not work.
Developers tried blocking “CommonsCollections,” but attackers simply used Spring/Groovy/BeanUtils instead.
Real mitigations that work:
1. Completely avoid unsafe serializers
-
Java → avoid native Java serialization. Use JSON-B, Jackson (with type restrictions), Kryo (safely configured).
-
.NET → avoid BinaryFormatter entirely.
-
PHP → never use
unserialize()on user input.
2. Enforce strict allow-lists
Only deserialize specific, known, final classes.
3. Disable polymorphic typing
Especially in Jackson / Fastjson:
Disable the second line unless you absolutely need it.
4. Sign/Encrypt serialized data
If you must serialize to the client, ensure:
-
Signing keys rotate
-
Integrity checks exist
-
The client cannot modify objects undetected
5. Add WAF rules and IDS signatures
Detect serialized binary formats:
-
Java serialization magic bytes
-
.NET binary headers
-
PHP serialized object markers like
O:
8. Final Thoughts
Deserialization bugs are terrifying because the attack surface is hidden—buried deep inside the object graph, libraries, and frameworks that most developers never read.
For attackers, it’s a goldmine:
One gadget chain = full system compromise.
For defenders, the strategy is simple:
Don’t deserialize untrusted data. Ever.
If you’re a bug bounty hunter, learning gadget chains gives you a massive advantage—most researchers skip these because they look “too enterprise,” but that’s where the payouts usually are.
Comments
Post a Comment