skip to content

Serialization Security & Alternatives

Deserializing untrusted data can execute code through gadget chains, which is why filters exist and why JSON or protobuf are the recommended alternatives. Interviewers treat this as the main reason not to use native serialization at all.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

Why is Java's native serialization considered dangerous when reading data from untrusted sources?

level: middleimportance: must knowfreq 72%

answer

  1. readObject runs magic methods before validation
  2. gadget chain → RCE via classpath classes
  3. ysoserial / Commons Collections
  4. data drives control flow before you check it
  5. also DoS: nested/huge objects

basics

~20 s

When Java reads a serialized object, it rebuilds objects from the bytes before you can check them. An attacker can craft bytes that, while being rebuilt, run unexpected code or cause harm, so reading untrusted serialized data is risky.

solid answer

~50 s

Java native serialization (ObjectInputStream.readObject) reconstructs arbitrary object graphs from a byte stream. Crucially, deserialization invokes type-specific logic during reconstruction: the JVM instantiates classes named in the stream and calls magic methods like readObject, readResolve, and even readExternal before your application code ever validates anything. An attacker who controls the bytes controls which classes get instantiated (limited to those on the classpath) and the field values fed to them. By chaining together side effects of these methods across library classes already present (a 'gadget chain'), an attacker can reach dangerous operations such as reflection or process execution, leading to remote code execution. Even without full RCE, they can trigger denial of service (billion-laughs-style nested objects, huge allocations). The core problem: data drives control flow before validation, so you must never deserialize untrusted input with the native mechanism.

go deeper

for a junior

Knows that reading serialized objects from outside the app is risky and that you shouldn't deserialize untrusted data.

for a middle

Can explain that readObject instantiates classes and runs magic methods before validation, enabling RCE and DoS, and names gadget chains conceptually.

for a senior

Articulates the gadget-chain mechanism, classpath dependency, common payload tools (ysoserial), and multiple attack vectors (RMI, caches, queues), plus why post-hoc validation fails.

for a principal

Frames it as a class of 'data-driven control flow' flaws, reasons about systemic mitigations (allowlist filters, removing native serialization from architecture, dependency hygiene to shrink gadget surface), and sets org-wide policy.

## What 'serialization' means **Serialization** is turning a live in-memory object into a flat sequence of bytes so it can be stored or sent over a network. **Deserialization** is the reverse: taking those bytes and reconstructing the object in memory. Java has a built-in mechanism for this: a class implements the marker interface `java.io.Serializable`, you write it with `ObjectOutputStream.writeObject(obj)`, and you read it back with `ObjectInputStream.readObject()`. ## What actually happens during readObject This is the heart of the danger. `readObject()` does **not** just copy bytes into a struct. The byte stream contains the **class names** of the objects to rebuild. The JVM: 1. Reads a class name from the stream and looks that class up on the **classpath** (the set of classes your app has loaded). 2. Allocates an instance **without calling its normal constructor**. 3. Populates its fields from the stream. 4. Calls special **magic methods** if the class defines them: `private void readObject(ObjectInputStream)`, `Object readResolve()`, `void readExternal(...)`, and during garbage collection later, `finalize()`. So arbitrary code from those magic methods runs **automatically**, before your own application logic gets a chance to inspect the result. The attacker chooses, within the limits of what's on the classpath, *which* classes appear in the stream and *what field values* they hold. ## Gadget chains and RCE The attacker usually cannot put their own new class on your classpath. Instead they use classes **already present** — yours and, more often, those of common libraries (historically Apache Commons Collections, Spring, etc.). A **gadget** is a class whose magic method does something useful to the attacker as a side effect. A **gadget chain** strings several together so that deserializing one object triggers a cascade: method A calls method B calls method C, eventually reaching a 'sink' such as `Runtime.exec`, a reflective method call, or template evaluation. The result is **remote code execution (RCE)**: the attacker runs commands on your server just by sending you bytes. Tools like *ysoserial* generate ready-made payloads for many library combinations. ## Other harms besides RCE Even if no RCE chain exists on your classpath, deserialization of untrusted data enables **denial of service (DoS)**: deeply nested or self-referential objects, or a stream claiming to contain a huge array, can exhaust CPU or memory before validation. There is also **information disclosure** and unexpected object creation. ## Why this is structurally hard to fix The fundamental flaw is **data driving control flow before validation**: the bytes decide which code runs. You cannot reliably 'sanitize' a serialized blob after the fact, because the damage happens *during* reconstruction. This is why the guidance is to **not deserialize untrusted input with native serialization at all**, and, where you must, to constrain *which classes are even allowed* via an object input filter (see related questions). ## Where untrusted input sneaks in Request bodies, HTTP parameters, cookies, message-queue payloads, RMI, JMX, cached blobs in Redis/Memcached, and files uploaded by users are all common vectors. If any of these can reach a `readObject`, you have the vulnerability.

  • If my class has no dangerous code in its readObject, am I safe?
    No. The stream can name any Serializable class on your classpath, including library gadgets. Your own class's safety is irrelevant if a vulnerable gadget chain is reachable.
  • What is a 'sink' in a gadget chain?
    The final dangerous operation the chain reaches — e.g. Runtime.exec, a reflective Method.invoke, JNDI lookup, or template evaluation — that turns object reconstruction into code execution.

saying these in an interview costs you the question

  • Saying it's safe if the class implements Serializable correctly — the danger is the classes in the incoming stream, not yours
  • Believing you can validate the object after readObject returns — the harm happens during reconstruction
  • Thinking attackers must inject their own classes — they reuse gadgets already on the classpath
  • Confusing this with simple data parsing; deserialization instantiates and executes type logic

context

open as a page

Why are data formats like JSON or Protobuf preferred over Java native serialization, and what must you still watch for?

level: middleimportance: should knowfreq 55%

basics

~20 s

JSON and Protobuf describe plain data, not Java objects, so reading them doesn't automatically run code or rebuild arbitrary classes. They're also cross-language and more stable across versions. You still must avoid letting a library auto-create arbitrary types from the input.

open as a page

How do Java object input filters (JEP 290) mitigate deserialization attacks, and what are their limits?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Java lets you install a filter that checks each class in an incoming serialized stream before it is rebuilt, and you can reject any class not on an allowlist. It also caps things like depth and array size to stop resource-exhaustion attacks.

open as a page

You inherit a service that deserializes Java objects from a message queue. Lay out a strategy to make it safe.

level: principalimportance: should knowfreq 33%

basics

~20 s

First confirm whether the input can be untrusted. The best fix is to stop using Java native serialization and switch to a data format like JSON or Protobuf. Until then, add a strict allowlist filter and size limits, patch libraries, and shrink the classpath.

open as a page