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 formatter

The 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 exec

You 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 control

PHPGGC 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 abuse

Fixing 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