Insecure Deserialization: From Serialized Blob to Remote Code Execution
Magic Methods and Gadget Chains
How gadget chains work: lifecycle and magic methods that run during deserialization, property-oriented programming, and why you attack libraries not the app.
Published
A gadget chain is how deserialization becomes code execution without you writing a single malicious class. You assemble existing classes, hooking one lifecycle method into the next, until the chain reaches a dangerous sink. This page explains the technique that underlies every language's exploits. Practice on a free live target.
What a gadget is
A gadget is an existing class whose deserialization behavior performs one step you can use. Alone it is usually harmless: it just calls a method on one of its properties. The attack is in the chaining. You set that property to another gadget, whose behavior calls a third, until the last one does something dangerous. The classes were never meant to be malicious. You compose benign steps into a harmful whole.
The magic methods that start a chain
Every language has lifecycle hooks that run automatically during deserialization, and those are the chain's entry point:
PHP __wakeup(), __destruct(), and __toString() when an object is used as a string
Java readObject(), readResolve(), readExternal() (Externalizable), validateObject()
Python __reduce__() (which is the whole payload by itself)
.NET constructors and property setters invoked by the formatterThe entry hook fires, uses a property you control, and the chain is off.
Property-oriented programming (POP)
Building a chain from existing classes is called property-oriented programming. Instead of supplying instructions (like shellcode), you supply data: a graph of objects whose properties point at each other so that, when the outer object's hook runs, control flows through the graph to a sink. You are programming with object properties rather than code.
Outer gadget (hook fires)
-> property is Gadget B, whose method is called
-> B's property is Gadget C, whose method is called
-> C reaches a sink: file write, reflection, process execYou build this by nesting serialized objects: the outer object's property value is itself a serialized gadget, and so on.
Why you attack the libraries
The sink only rebuilds classes it can load, and the reliable gadgets live in common libraries (Commons Collections, Spring, ROME on Java, and framework classes on PHP and .NET). Those are present across many targets, so a chain is reusable and can be targeted by library version. You only need to know which library the target ships, not its business logic.
Finding chains
Known-gadget tools ysoserial (Java), ysoserial.net (.NET), PHPGGC (PHP) ship ready chains
Source review grep the app and its libraries for __wakeup/__destruct/readObject and dangerous sinks
Gadget databases PHPGGC catalogues PHP framework chains; ysoserial catalogues Java/.NET
Property paths trace from an entry hook to a sink, noting which properties you must controlPHPGGC is the PHP counterpart to ysoserial, with chains for Laravel, Symfony, Monolog, and more.
Edge variants
__toString entry a gadget that fires when the object is concatenated or echoed
nested POP graph several gadget objects nested as properties
partial chain + app sink a library chain that ends by calling an app method you can abuseFixing it
- Remove the trigger: do not deserialize untrusted data, so no entry hook ever fires on attacker input.
- Allow-list classes so the deserializer cannot rebuild the gadget classes a chain needs.
- Trim and patch libraries to shrink the available gadget surface.
Full guidance is in preventing insecure deserialization. Next, trigger PHP deserialization with no explicit call in PHAR deserialization, or build a chain on a free lab.
← Back to Insecure Deserialization: From Serialized Blob to Remote Code Execution