You inherited a service that accepts Java-serialized objects over HTTP. How do you prioritize remediating the deserialization risk, and why is filtering not the first answer?
answer
- native deser of untrusted = code-execution surface, not parsing
- strategic fix = data-only format (JSON/Protobuf) into KNOWN types
- filtering = fastest containment, not the conceptual fix (mitigates, can drift)
- phases: allowlist+patch+authn -> shrink gadgets -> migrate format -> verify (ysoserial)
- beware Jackson default/polymorphic typing = same hole re-created
basics
~20 sThe best fix is to stop using Java native serialization for untrusted input and switch to a data-only format like JSON or Protocol Buffers parsed into known types. Class filters (JEP 290) and gadget removal are good extra layers, but they only reduce the risk; they don't remove the dangerous code-execution surface.
solid answer
~50 sI treat native deserialization of untrusted input as a code-execution surface, so the strategic fix is to *eliminate* it: replace the wire format with a data-only protocol (JSON, Protobuf, Avro) deserialized into explicit, expected types — these don't instantiate arbitrary classpath classes or run hook methods. That's a contract change, so I sequence it: (1) immediate containment — apply a strict JEP 290 allowlist (`!*` + resource limits) per-stream and via `jdk.serialFilter`, patch/remove gadget-bearing libraries, and add WAF/network controls; (2) reduce the gadget surface — dependency audit, drop unneeded Serializable usage; (3) the real fix — migrate the endpoint to a safe format behind a versioned API and authenticate/authorize the caller; (4) verify with ysoserial-style testing and monitor filter rejections. Filtering isn't first *conceptually* (it's mitigation of an inherently dangerous mechanism, and allowlists can be too broad), but it *is* the fastest containment while the format migration ships.
go deeper
Knows the safest move is to not deserialize untrusted Java objects and to prefer JSON; may not sequence the rollout.
Can apply JEP 290 filters and patch gadget libraries, and knows JSON into fixed types is safer than native serialization.
Distinguishes containment (filters/patching) from the real fix (format migration), and avoids re-introducing the bug via Jackson polymorphic typing.
Owns a phased remediation across teams/contracts, balances immediate containment vs. format migration, accounts for incidental deser paths (RMI/JMX), and sets policy + monitoring + verification (ysoserial) to sustain it.
## Reframe the problem Native Java deserialization applied to attacker-influenced bytes is not 'parsing' — it is a **code-execution surface**: `ObjectInputStream.readObject()` lets the *stream* choose which classpath classes to instantiate and runs their hook methods (`readObject`/`readResolve`/`readExternal`) during reconstruction, enabling **gadget-chain** RCE and DoS bombs. A senior+ remediation plan is organized around *removing the surface*, with filtering as containment — not as the destination. ## Why filtering is not the first (conceptual) answer - **It mitigates, it doesn't eliminate.** A JEP 290 `ObjectInputFilter` gates *which classes* deserialize, but the dangerous mechanism (instantiate-by-stream + run hooks) is still active. An allowlist that's a little too broad, or a permitted class that itself can be abused, re-opens the door. - **Allowlists drift.** They need maintenance as DTOs change; a sloppy edit (missing trailing `!*`) silently leaks. - **It can't vet class internals.** The filter sees names and stream metrics, not behavior. The format change (data-only protocol into known types) **removes** the instantiate-arbitrary-classes-and-run-hooks behavior entirely — JSON/Protobuf parsers map fields into types *you* nominate; they don't pick classes from the stream or invoke serialization hooks. That's why it's the strategic answer. BUT: a format migration is an externally-visible contract change (clients send different bytes), so it takes time. **Therefore filtering is the right *first action chronologically* for containment**, even though it's not the *conceptual* fix. Distinguishing 'fastest containment' from 'correct end state' is exactly the judgment this question probes. ## A prioritized plan **Phase 0 — Contain now (hours/days)** - Apply a strict **JEP 290 allowlist** at every deserialization site: `setObjectInputFilter` per-stream and a process default via `jdk.serialFilter`, ending in `!*`, with `maxdepth/maxrefs/maxbytes/maxarray` limits to stop bombs. - **Patch or remove** known gadget-bearing libraries (Commons Collections, vulnerable Spring/Groovy/etc.); upgrade the JDK. - Add **network/WAF** controls and ensure the endpoint requires authentication/authorization (an unauthenticated deserialization endpoint is the worst case). - Log/alert on filter **rejections** to detect probing. **Phase 1 — Shrink the surface (weeks)** - Dependency audit to enumerate gadget sources; remove unused libs. - Stop making classes `Serializable` unless required; mark secrets `transient`; add `readObject` validation where native serialization must stay internally. **Phase 2 — Eliminate the surface (the real fix)** - Introduce a **versioned endpoint** using a data-only format (JSON/Jackson with explicit types and `@JsonTypeInfo` locked down, Protobuf, Avro). Deserialize into **expected, concrete types**, validate inputs, and never enable polymorphic type handling on untrusted JSON (that re-creates the same gadget problem — e.g. Jackson 'default typing' CVEs). - Migrate clients off the serialized endpoint; then **decommission** it. **Phase 3 — Verify & sustain** - Red-team with **ysoserial**/automated payloads against staging; confirm rejections. - Add CI dependency scanning, keep the allowlist as belt-and-suspenders even after migration, and monitor. ## Pitfalls to call out - Swapping native serialization for **Jackson polymorphic/default typing** on untrusted data just moves the vulnerability — disable it or pin allowed subtypes. - A blocklist of known gadgets is not a strategy; gadgets keep being discovered. - 'We authenticate the caller' helps but isn't sufficient if any authenticated principal could be malicious or compromised. **Bottom line:** containment first (allowlist + patch + authn), strategy second (replace native serialization with a data-only format into known types). Filtering buys time; removing the surface fixes the class of bug.
- Doesn't switching to JSON make me safe automatically?Not automatically. JSON into fixed types is safe, but enabling polymorphic/default typing (e.g. Jackson activateDefaultTyping or unrestricted @JsonTypeInfo) on untrusted input re-creates a deserialization-gadget vulnerability. Deserialize into known concrete types and disable/whitelist polymorphism.
- If the format migration is the real fix, why apply JEP 290 filters at all?Because the migration is a client-facing contract change that takes time; filters are immediate containment. They also remain valuable as defense-in-depth afterward and protect incidental native deserialization paths (RMI/JMX/session state).
saying these in an interview costs you the question
- Treating a JEP 290 filter as the complete fix rather than containment.
- Migrating to JSON but enabling polymorphic/default typing on untrusted data.
- Relying on a blocklist of known gadgets.
- Assuming authentication alone neutralizes a deserialization endpoint.