skip to content

questions

5

Insecure deserialization is usually taught as "don't accept a malicious byte blob". Give the definition of the bug class that also explains why a YAML document with type tags, a language's native object stream, and a JSON payload with polymorphic type binding all share the same defect — and why "validate the blob before deserializing it" is the wrong frame.

level: middleimportance: must knowfreq 58%

answer

  1. decoder = interpreter
  2. the wire picks the type
  3. parse-to-inspect paradox
  4. expressive power = attack surface
  5. bind into a code-chosen type

basics

~20 s

A deserializer is an interpreter. The defect is that the encoded input, not the code, chooses which types are constructed and which reconstruction hooks run. Validate-first fails because you must decode the data to inspect it, and decoding is the dangerous act.

solid answer

~60 s

The class is not "bad bytes"; it is **a decoder that takes instructions from the data it decodes**. Serialization formats differ in expressive power: some encode only values, some encode *type identity*, and some encode *behaviour*. The moment the wire can name a type, the decoder becomes a small interpreter whose program is attacker-controlled — it constructs objects of the attacker's choosing and runs whatever the runtime invokes during reconstruction (constructors, setters, lifecycle callbacks). The attacker writes no new code; they compose code already present, which is why impact tracks the type graph loaded in that process, not the payload's cleverness. The invariant to state is: **the code, not the wire, decides which types exist and which reconstruction code runs.** "Validate before deserializing" inverts the order of operations. The blob is structured, possibly nested and compressed, so understanding it means running the machinery you are trying to gate. Scanning for known-bad type names is open-world: it admits every dangerous type you have not enumerated, including ones a dependency upgrade adds tomorrow.

go deeper

for a junior

Be able to say that deserializing untrusted input can construct objects and run code the sender chose, and that the safe pattern is to parse into plain data and then map it into a type your code named.

for a middle

Explain expressive power (values / values+type / values+behaviour), name the invariant that the code and not the wire chooses types, and explain why pre-decode validation is structurally impossible.

for a senior

Add the second-order sinks (cache, queue, session, config) and the observation that exploitability depends on the type graph loaded in that specific process, so identical code differs in risk between services.

for a principal

Frame it as a boundary-design question: which components are ever allowed to accept a self-describing encoding, what the target-state codec is, and how you keep the allowed set closed as dependencies churn.

## The definition Insecure deserialization is the bug class in which **a decoder is permitted to take instructions from the data it is decoding**. It belongs to the same family as injection: a boundary exists where something that was supposed to be *data* is interpreted as *program*. In injection the interpreter is a query or command parser; here the interpreter is the object-reconstruction machinery of a runtime. ## Expressive power is the attack surface Sort formats by what they can express: 1. **Values only.** A grammar with a fixed, small set of value kinds — string, number, boolean, null, list, map. Nothing in the document names a type in your program. Decoding produces an inert tree. 2. **Values plus type identity.** The document can say "construct an object of type T with these fields". Now the wire chooses the class. 3. **Values plus behaviour.** The document contains an instruction stream, or names callables to invoke. The decoder is explicitly a virtual machine. Only level 1 is safe to point at untrusted input by default. Levels 2 and 3 hand the attacker the choice of *what runs*. This is why "our format is human-readable, so it's safe" is a non-argument: a text format with type tags is level 2, and a binary format that carries only field values is level 1. Textual versus binary is orthogonal to the defect. ## Reconstruction runs code you did not call Even at level 2, with no callables in the document, reconstruction is not passive. Depending on the runtime it may invoke constructors, property setters, custom deserialization hooks, object-resolution or lifecycle callbacks, finalizers, and — subtly — the hashing and equality methods of decoded objects when a decoded map or set is populated. The attacker's payload is therefore a *composition of code already present in the process*, commonly called a gadget chain. Two consequences follow: - The exploitability of one identical sink differs between two services because their loaded type graphs differ. Adding a library can arm a sink that was previously inert. - Removing one known-dangerous type does not remove the defect. It removes one path through an unbounded search space. ## Why "validate first" is the wrong frame Three reasons, and they generalise: - **The parse-to-inspect paradox.** Meaningful inspection of a structured blob requires parsing it, and parsing is the dangerous act. Any pre-check strong enough to be useful is itself a decoder. - **Open-world versus closed-world.** A rule that forbids known-bad type names is open-world: it constrains a list you wrote, while the space of admissible inputs stays infinite. A rule that permits only an enumerated set of types is closed-world: finite, owned by your code, and stable under dependency upgrades. - **Layering.** Blobs arrive nested, base64'd, compressed or embedded inside another format, so byte-level checks are trivially evaded, and length limits alone don't help against a compact payload. ## What restores the invariant Parse into a neutral tree with a level-1 decoder, then **bind** that tree into a type your code named at the call site. If genuine polymorphism is required, express it as a finite, code-owned map from a short tag in the document to a permitted type — the document may select from your list, never name a type directly. Add resource budgets (size, nesting depth, element counts, time) because a perfectly type-restricted decoder can still be made to exhaust memory or CPU. ## Where the class hides The dangerous decode is often not at the edge. Blobs get written to a cache, a queue, a database column or a client-held cookie and are decoded later by a different, frequently more privileged consumer — second-order deserialization. Configuration loaders, RPC codecs, session stores, plugin loaders and file-upload processors are all decoders. Inventory *decode sites*, not endpoints. ## How to say it in an interview "Deserialization is code execution with extra steps whenever the format lets the sender pick the type. The fix is not smarter validation of the input; it is removing the sender's ability to choose — parse to plain data, bind to a type the code chose, and if polymorphism is unavoidable, make the choice a finite allow-list the code owns."

  • If validating the blob first is impossible, what actually replaces it?
    Reverse the order: decode with a parser that has no type channel, producing an inert tree of strings, numbers, lists and maps, then bind that tree into a type your code named at the call site. Validation still happens, but on plain values after the dangerous step has been made safe. If polymorphism is genuinely required, the document may only supply a short tag that your code maps to a permitted type through a finite table.
  • Is a JSON parser therefore safe against this class?
    The JSON grammar has no type channel, so a parser that yields maps and lists is safe from type-driven reconstruction. The danger returns entirely at the binding layer: if the binder resolves a type name carried in the document, or a mapper has polymorphic type handling enabled globally, you have re-created the level-2 format inside JSON. Safety is a property of format plus binding configuration, never of the format alone.
  • Does a type allow-list also stop denial of service?
    No. Resource exhaustion needs no gadget: deep nesting, cyclic or self-referential graphs, huge collection sizes declared in a header, decompression bombs and hash-flooding all work against a decoder that only ever constructs permitted types. Type restriction and resource budgets are independent controls and you need both.

A level-1 format is a filled-in form: the fields were decided by whoever printed it. A level-2/3 format is a blank sheet on which the sender may also write which department must process it and what they must do — and the mailroom obeys.

saying these in an interview costs you the question

  • "Only certain legacy languages have this problem" — any runtime with type-directed reconstruction does.
  • "It's a remote-code-execution bug" as the whole story — tampering with decoded object state and denial of service need no code execution at all.
  • "We scan the payload for known dangerous class names before decoding" — an open-world blocklist, and it requires parsing to be reliable.
  • "The format is text/human-readable, so nothing can execute" — textual versus binary is orthogonal; type tags are the issue.
  • "We deserialize straight into our DTO, so the type is fixed" — only true if the binder cannot be steered to another type by the document.

context

open as a page

Rank the defences available when a service must accept serialized data from an untrusted producer, from strongest to weakest, and justify at least one adjacent pair. Where does a blocklist of known-dangerous types sit, and why does it sit there?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Structural separation first: a format with no type channel, bound into a code-chosen type. Where polymorphism is unavoidable, closed-world enumeration takes the top rung. Then validation — blocklists, size and depth caps — which is open-world heuristic. Detection last.

open as a page

A service decodes JSON with a general-purpose object mapper, and someone turns on the mapper's polymorphic type handling globally so that interface-typed fields round-trip. Explain what that configuration changes about which types an incoming document can cause to be constructed, why a global switch differs from declaring permitted subtypes on one field, what a YAML library's "safe load" entry point actually is in the same terms, and what a team has and has not achieved by migrating an endpoint from a native object stream to JSON.

level: middleimportance: should knowfreq 40%

basics

~20 s

Polymorphic type handling lets the document name the type to construct. Globally, every polymorphic field becomes an open-world type-selection channel; per-field with an enumerated subtype list is closed-world. A format migration that keeps the global switch changes syntax, not exposure.

open as a page

Two services run the same source code and the same call that rebuilds application objects from attacker-influenced serialized bytes, yet a security team rates one critical and the other low. Describe what an attacker actually achieves at such a decode point, and explain why the set of types the decoding process can construct — not the code — is what separates the two ratings.

level: seniorimportance: should knowfreq 38%

basics

~20 s

Outcomes split into exhaustion, state tampering, disclosure, code execution, and second-order pivot. Identical code differs in risk because of the capability surface: which types that process can construct. Adding a dependency can arm a sink that was inert for years.

open as a page

You inherit a large system that moves natively serialized objects between services, into a shared cache, and into client-held session blobs. How would you eliminate this bug class rather than patch instances, and what holds the line while the migration runs?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Inventory decode sites and their producers, set a target of schema-first codecs binding into code-chosen types, migrate behind a version tag, and delete the requirement where possible — client blobs become server-side references. Meanwhile: allow-list filters, budgets, least-privileged decoders, banned-API lint.

open as a page