Magic Methods in Deserialization Attacks

Magic Methods in Deserialization Attacks

Written by

in

Magic methods are the reason deserialization can run code at all. They are methods a language runtime calls by itself at specific moments, without your program asking. During deserialization, several of them fire while an object is being rebuilt, before any of your own logic runs. That is the exact moment an attacker wants, because it gives a crafted object a foothold to start executing. This post explains which magic methods matter, why they are the entry points a chain starts from, and how the same idea repeats across languages.

What magic methods are

A magic method is a hook with a reserved name that the runtime invokes automatically. You do not call it; the language does, at a defined event such as object creation, string conversion, or cleanup. They exist for good reasons, like restoring a connection after an object is loaded or formatting an object for printing. The security problem is timing. When untrusted bytes are deserialized, the runtime rebuilds the described object and runs its magic methods as part of that process. The attacker chose the object, so the attacker chose which hook fires first.

The magic methods that start a chain

Every language has its own set, but they play the same role: an automatic entry point invoked during or right after rebuilding an object.

  • Java. readObject runs during native deserialization, with readResolve and the object’s finalize as related hooks. A gadget’s readObject is the classic starting gun.
  • PHP. __wakeup runs when an object is rebuilt, and __destruct runs when it is later cleaned up. __toString runs whenever the object is used as a string. All three are common chain entries, which is why they anchor PHP object injection.
  • Python. __reduce__ tells the pickle format how to rebuild an object, and the attacker can make it name any callable with any arguments. This single method is the heart of Python pickle remote code execution.
  • C# and .NET. Deserialization callbacks and constructors invoked while a typed object graph is rebuilt serve the same purpose.

A magic method is not the bug. It is the doorbell that tells the rest of the chain it is time to start running.

Why the entry point matters so much

A gadget chain is a sequence of method calls, but something has to make the first call. Your application code will not do it, because your code does not know the attacker’s object exists. The magic method is what bridges that gap. The runtime rebuilds the object and, entirely on its own, calls the reserved method. From there the attacker’s chosen fields steer control into the next gadget. Without an automatic entry point, the crafted object would just sit in memory doing nothing.

Here is the shape in Python, where one magic method does the whole entry step:

import os

class Exploit:
    def __reduce__(self):
        # the runtime calls this to learn how to rebuild the object
        return (os.system, ("id",))

When bytes describing this object are deserialized, the format honors __reduce__ and calls os.system("id"). No other code is needed, because the magic method both fires automatically and names the sink. In longer chains the entry method does less, just calling a method on a field, and the remaining work is spread across link gadgets. That property to property handoff is the subject of property oriented programming.

A two step example

Consider a made up PHP class in an app called Acme Notes. Its cleanup hook was meant to flush a log.

class Logger {
    public $handler;
    function __destruct() {
        $this->handler->flush();   // calls flush() on whatever we set
    }
}

The attacker sets handler to a different object whose flush method runs a command. When the deserialized Logger is cleaned up, __destruct fires automatically and calls flush on the attacker’s object. The magic method did not do anything dangerous by itself. It started the chain. This is the same pattern used everywhere, including the Java library classes behind the Commons Collections gadget chain.

How to reason about them safely

When you review code, the presence of a magic method is a signal, not a verdict. Ask two questions. Can untrusted bytes reach a deserializer that rebuilds this class? And does the magic method call a method on a field the attacker controls? If both are yes, you have an entry point into a possible chain. If untrusted input can never reach the deserializer, the same magic method is harmless. This is why the real work is tracing input flow across the app, not listing reserved method names. For the broader picture of how these footholds combine into full exploits, start with our hub on deserialization gadget chains, and see more input driven bugs in the injection and input category.

How to prevent abuse

  • Do not deserialize untrusted input into types that carry automatic methods. Plain data formats with a fixed schema produce values, not objects with hooks.
  • Use an allowlist of expected classes so an attacker’s gadget type, and its magic method, is never instantiated.
  • Keep magic methods simple and free of side effects that call into fields, so even an instantiated object has nowhere to go.
  • Sign serialized data you control and verify it before any object is rebuilt.

Magic methods turn a passive blob of bytes into a running sequence, which is why understanding them is understanding where a chain begins. For deeper background first, read our primer on insecure deserialization. Spotting whether a magic method is truly reachable from untrusted input is a source to sink reasoning task, and an autonomous researcher that learns how an app connects its inputs to its code is built to answer it. More on our about page.

Frequently asked questions

What are magic methods in a deserialization attack?

Magic methods are reserved methods the runtime calls automatically at set moments, such as when an object is rebuilt, cleaned up, or used as a string. During deserialization they fire before your own code runs, which gives a crafted object a place to start executing.

Which magic methods matter across languages?

Java uses readObject, readResolve, and finalize; PHP uses __wakeup, __destruct, and __toString; Python uses __reduce__; and .NET uses deserialization callbacks and constructors. Each plays the same role of an automatic entry point into a chain.

Why is the magic method the entry point of a chain?

A gadget chain is a sequence of method calls, and something must make the first call. Your application never calls the attacker’s object, so the runtime’s automatic magic method is what starts the sequence, after which chosen fields steer control to the next gadget.

How do you prevent magic methods from being abused?

Avoid deserializing untrusted input into types that carry automatic methods, use an allowlist of expected classes so the attacker’s gadget type is never built, keep magic methods free of side effects that call into fields, and sign and verify serialized data you control.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.