Application & code
Educational12 minInsecure Deserialization
An application reconstructs objects from untrusted serialized data using a deserializer that can instantiate arbitrary types and run code during reconstruction, so a crafted payload drives the program’s own classes into a malicious computation — often remote code execution.
Classification
- Domain
- Application & code
- ROOTVIEL support
- Educational — Recognized concept, documented for learning — not detected by ROOTVIEL.
- Analysis family
- code-src
- CWE
- CWE-502
- OWASP
- A08:2021-Software and Data Integrity Failures
Definition
Insecure deserialization is the reconstruction of application objects from attacker-controllable serialized data using a mechanism that grants the data too much power. A rich deserializer does not merely copy field values into a known structure; it can decide which classes to instantiate, invoke constructors and lifecycle callbacks (such as readObject-style hooks), and set fields that later trigger behaviour. When the serialized bytes come from an untrusted source, the attacker effectively chooses which of the program’s classes run and with what state.
The severe outcome is remote code execution via a gadget chain: the attacker does not supply code, but strings together classes already present on the application’s classpath whose combined deserialization side effects reach a dangerous operation. Lesser outcomes include denial of service, authentication bypass (forging a trusted object), and data tampering. The defect is trusting serialized input and using a deserializer powerful enough to be abused.
Context
This arises wherever serialized objects cross a trust boundary and are rebuilt by a type-aware deserializer: session tokens or cookies holding serialized state, message-queue and cache payloads, inter-service RPC, file imports, and any endpoint that accepts a serialized blob. Native binary formats (Java serialization, Python pickle, Ruby/PHP object serialization, .NET BinaryFormatter) are the classic danger; some data formats become dangerous only when configured to encode arbitrary types (polymorphic/typed JSON or YAML that can name classes to construct).
- ▸Session state or tokens stored as serialized objects in cookies or server-side stores fed by the client.
- ▸Message queues, caches, and RPC that pass native-serialized objects between components.
- ▸Import/restore features that accept a serialized file (pickle, Java ser, PHP serialize).
- ▸JSON/YAML parsers configured for polymorphic type handling, where the payload names the type to instantiate.
- ▸Any place the data’s integrity is not cryptographically guaranteed before it is deserialized.
Technical fundamentals
Serialization turns an object graph into bytes; deserialization rebuilds it. The safety of the operation depends entirely on how much the input is allowed to control. A data deserializer that reads a fixed, known schema into plain data (numbers, strings, lists, a predefined struct) is safe because the input cannot choose types or trigger behaviour. An object deserializer that reconstructs arbitrary types — reading class names from the stream and running constructors, setters, and deserialization callbacks — is dangerous because the input selects which code paths execute.
A gadget chain exploits this: the attacker picks classes already on the classpath whose deserialization hooks perform operations (reflection, I/O, process launch, template evaluation) and arranges their nested state so that reconstructing the graph performs a harmful action. No attacker code ships in the payload; the application’s own libraries are the weapons. This is why the only robust defence is to not deserialize untrusted data with a type-permissive mechanism — restrict to known-safe data formats, and if typed deserialization is unavoidable, bind it to a strict allow-list of expected classes and verify integrity first.
- Data deserializer
- Reads a fixed schema into plain data; cannot choose types or run behaviour. The safe choice.
- Object deserializer
- Reconstructs arbitrary types from the stream, running constructors and callbacks. The dangerous sink.
- Gadget chain
- A sequence of existing classes whose deserialization side effects combine to reach a dangerous operation (e.g. code execution).
- Type allow-list
- A fixed set of classes the deserializer is permitted to instantiate; everything else is rejected.
Root cause
The root cause is handing untrusted bytes to a deserializer that is allowed to instantiate arbitrary types and execute code during reconstruction. The developer treats deserialization as a neutral inverse of serialization — "just turn the bytes back into my object" — not realising that a type-aware deserializer executes logic chosen by the bytes. The structural fix is to reduce the deserializer’s power (parse into known data, or allow-list the types) and to establish the data’s integrity before trusting it.
A second cause is integrity-blindness: even an otherwise acceptable typed format becomes an injection vector if the payload can be modified in transit or by the client and is deserialized without an authenticity check. Signing or MAC-ing serialized data (and verifying before deserializing) is part of the Software and Data Integrity Failures theme that A08 names.
How the attack works, step by step
- 1The application accepts a serialized blob from an untrusted source (a cookie, an upload, a queue message) and passes it to a type-aware deserializer.
- 2The attacker crafts a payload describing an object graph of classes already present on the application’s classpath — chosen for their deserialization side effects.
- 3During reconstruction the deserializer instantiates those classes and runs their constructors and callbacks in the order the graph dictates.
- 4The chained side effects reach a dangerous operation — reflective method invocation, a process launch, a template evaluation — executing the attacker’s intent.
- 5Because the deserializer trusted the stream to name types and trigger behaviour, the application’s own code carried out the attack with no attacker-supplied code.
Not every outcome is RCE: a forged object can impersonate an authenticated principal, a deeply nested or self-referential graph can exhaust CPU/memory (a deserialization bomb), and tampered state can corrupt application logic.
Vulnerable example
A synthetic, illustrative handler that rebuilds a session object from a client-supplied, type-aware serialized blob. The deserializer can instantiate arbitrary types:
synthetic (Python-style pseudocode) — do not use; illustrates the flawimport pickle, base64 # UNSAFE: pickle reconstructs arbitrary objects and runs __reduce__ hooks. def load_session(request): blob = base64.b64decode(request.cookies["session"]) session = pickle.loads(blob) # sink: attacker chooses the types + behaviour return session # An attacker crafts a pickle whose __reduce__ returns a call to a # dangerous function; pickle.loads() executes it during reconstruction. # No attacker code is "in" the app — the deserializer runs it.
Corrected example
The fix is to stop using a type-permissive deserializer on untrusted data: parse a known schema into plain data, and verify integrity first. If typed deserialization is genuinely required, bind it to a strict class allow-list.
synthetic — known schema, integrity check, no arbitrary typesimport json, hmac, hashlib KEY = load_secret() # server-held signing key ALLOWED_FIELDS = {"user_id", "role", "exp"} # SAFER: a data format (JSON) into a known schema, with an integrity check. def load_session(request): raw = request.cookies["session"] # format: base64(payload).hex(mac) payload_b64, mac_hex = raw.rsplit(".", 1) # Verify authenticity BEFORE parsing/trusting anything. expected = hmac.new(KEY, payload_b64.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, mac_hex): raise ValueError("tampered session") data = json.loads(base64_decode(payload_b64)) # plain data, no types to choose if set(data) - ALLOWED_FIELDS: raise ValueError("unexpected fields") return Session(user_id=int(data["user_id"]), role=str(data["role"]))
Limit of the fix: JSON/YAML are only safe while configured to produce plain data — enabling polymorphic/typed handling reintroduces the danger, so disable it for untrusted input. A class allow-list helps but is brittle: new gadget classes appear as dependencies change, so prefer a data format outright. An integrity check (signing/MAC) stops tampering but does not make a type-permissive deserializer safe — it only ensures the bytes came from you.
Impact
- ▸Remote code execution via a gadget chain — frequently the highest-severity outcome, giving control of the process.
- ▸Authentication/authorization bypass by forging a trusted serialized object (e.g. an admin session).
- ▸Denial of service through deserialization bombs (deeply nested or self-referential graphs).
- ▸Data tampering and logic corruption when reconstructed state is trusted downstream.
Detection
The structural signal is a taint path from an untrusted source to a type-permissive deserialization sink (native object deserializers, pickle, BinaryFormatter, typed-enabled JSON/YAML). Static analysis can flag use of these dangerous APIs on request-reachable data; it cannot by itself enumerate whether an exploitable gadget chain is present. Dependency inventory matters: known gadget classes in the classpath raise exploitability. Dynamic testing confirms it by delivering a benign marker payload (e.g. one that triggers an out-of-band callback or a measurable delay) and observing the side effect. The most reliable review check is simply whether untrusted bytes ever reach an object deserializer.
Where ROOTVIEL fits
This entry is reference material. ROOTVIEL’s verified, execute-and-prove engine is focused on the AI/agentic and Web3 domains, where a finding is confirmed by running the attack in a sandbox against a deterministic check. Insecure deserialization is an application/code weakness that ROOTVIEL does not currently settle with a verified engine, so a finding here is educational unless it is independently demonstrated against the target’s code and dependencies.
Support: educational. Use of a dangerous deserializer on tainted input is detectable by pattern/dataflow, but proving an exploitable gadget chain is dependency-specific. ROOTVIEL does not promote a verified deserialization finding; treat any match as a hypothesis to confirm.
Mitigation
Do not deserialize untrusted data with a type-permissive mechanism; reduce the deserializer’s power and verify integrity.
- ▸Preventive: use a data-only format with a fixed schema (plain JSON into known fields, Protocol Buffers, a validated struct) for anything crossing a trust boundary.
- ▸Preventive: avoid native object serializers (Java serialization, pickle, BinaryFormatter) for untrusted input; if a typed deserializer is unavoidable, bind it to a strict allow-list of expected classes and disable polymorphic type handling.
- ▸Preventive: cryptographically sign or MAC serialized data you control and verify before deserializing (A08 integrity).
- ▸Defence in depth: keep dependencies patched to remove known gadget classes, enforce resource limits to blunt deserialization bombs, and run with least privilege.
- ▸Detective: flag dangerous deserialization APIs on request-reachable paths in static analysis and code review.
Verifying the mitigation
Verify by confirming no untrusted input reaches a type-permissive deserializer, that typed handling is disabled (or allow-listed) for external data, and that integrity is checked before deserialization. Then attempt exploitation with marker payloads — an out-of-band callback gadget, a resource-exhaustion graph, a forged-object impersonation — and confirm none succeed. Report as "these attempts failed with the current dependencies and inputs", recognising that a new gadget can appear when dependencies change.
A clean result is tied to the current classpath. Because gadget chains depend on installed libraries, re-verify after dependency upgrades, and prefer removing the type-permissive sink entirely over chasing allow-lists.
Common mistakes
- ▸Believing a signature/MAC alone makes a native object deserializer safe — it stops tampering, not the power of the deserializer if the key leaks or a trusted producer is compromised.
- ▸Switching to JSON/YAML but enabling polymorphic/typed deserialization, which restores arbitrary-type instantiation.
- ▸Maintaining a class allow-list that drifts out of date as dependencies add new gadget classes.
- ▸Assuming "we control the producer" when the serialized data still traverses a client or an untrusted channel.
- ▸Treating absence of a known public gadget as proof of safety rather than as "no gadget found today".
References
- [1]MITRE CWE-502 (Deserialization of Untrusted Data)
- [2]OWASP Top 10 — A08:2021 Software and Data Integrity Failures
- [3]OWASP Cheat Sheet — Deserialization
- [4]PortSwigger Web Security Academy — Insecure deserialization
References point to public, well-known technical sources for further study.
Related articles
Another path to the attacker controlling execution.
Path TraversalCan select attacker-chosen code for a deserializer to load.
Hardcoded SecretsA leaked signing key defeats integrity checks on serialized data.
Insecure Output HandlingTrusting upstream-produced content without re-validation.
Taint analysis: source to sink