Choose your stack
Fix insecure deserialization in Node.js and Express
Treat the token as data: JSON.parse it and rebuild only an allowlisted type - map $type to one known class, never the class the payload names.
Open the fix card Python · FlaskFix insecure deserialization in Python and Flask
Do not rebuild objects from the token. Decode it as JSON and copy only the fields you expect into a fixed shape; pickle.loads runs code chosen by the bytes.
Open the fix card Java · Spring BootFix insecure deserialization in Java and Spring Boot
Do not deserialize untrusted bytes with ObjectInputStream: its readObject runs code chosen by the payload.
Open the fix card C# · ASP.NET CoreFix insecure deserialization in C# and ASP.NET Core
Do not let the payload choose the type. With Newtonsoft.Json keep TypeNameHandling at None and bind to a concrete class; a SerializationBinder blocklist is not enough and even quiets the analyzer.
Open the fix card Python · FastAPIFix insecure deserialization in Python and FastAPI
Do not rebuild objects from client data. Parse the token as JSON with a Pydantic model (Profile.model_validate_json(raw)), which keeps only declared fields of declared types; pickle.loads runs code chosen by the bytes.
Open the fix card Ruby · RailsFix insecure deserialization in Ruby on Rails
Do not rebuild objects from the token: Base64-decode it and JSON.parse the bytes, then copy only the fields you expect into a fixed shape; Marshal.load runs code chosen by the bytes.
Open the fix card PHP · LaravelFix insecure deserialization in PHP and Laravel
Do not rebuild objects from the token: base64-decode it and json_decode the bytes, then copy only the fields you expect; unserialize instantiates whatever class the string names and runs its __wakeup.
Open the fix cardStacks without a card
- TypeScript and Next.js: no card yet. The nearest is Fix insecure deserialization in Node.js and Express, in the same language.
- Python and Django: no card yet. The nearest is Fix insecure deserialization in Python and Flask, in the same language.
- Go and net/http: no card yet. The nearest is Fix insecure deserialization in Node.js and Express.
How it happens
- A format that embeds types or code is fed attacker data: Python pickle and PyYAML yaml.load build arbitrary objects and call arbitrary callables; Java ObjectInputStream runs each class's readObject; Newtonsoft.Json with TypeNameHandling reads a $type field and constructs it.
- The payload names a class already on the server's classpath whose side effects form a 'gadget chain': a property setter opens a process, a readObject writes a file, a finalizer runs a command. The object never has to be the type the code expected.
- In dynamic languages the object graph itself is the weapon. An unsafe recursive merge of a parsed JSON body copies a __proto__ or constructor.prototype key onto Object.prototype, so every object in the process gains the attacker's property (prototype pollution).
- Hand-made fixes fail. Blocklisting known gadget class or type names only stops the samples you have seen: a new gadget, a different package, a case or whitespace variant, or constructor.prototype instead of __proto__ walks straight past the list.
Fix principles
- Treat a serialized payload from a client as hostile data, not as an object. Parse it with a format that carries neither types nor code (JSON), into a fixed, concrete shape you declare.
- Validate the parsed data against a schema before use: known fields, known types, known ranges. Reject anything else.
- Never call pickle.loads, yaml.load (unsafe loaders), ObjectInputStream or TypeNameHandling.All/Auto on data a client can influence.
- When typed or polymorphic deserialization is genuinely required, allowlist the exact set of types that may be created (ObjectInputFilter, [JsonDerivedType], a type map) and reject everything else; do not blocklist.
- When merging a parsed object into your own, refuse the keys __proto__, constructor and prototype, or build onto a null-prototype object, or copy only an allowlist of known keys.
- Prefer signing or MACing any state you must round-trip through a client so it cannot be swapped for a crafted payload, and keep deserialization error text off the wire.
How the cards prove it
- Run each card's tests in three modes: the fixed implementation must pass, and the vulnerable and the plausible-but-wrong (blocklist) implementations must fail. run-all.mjs does this for every card.
- The exploit tests send a payload whose only job is to fire a harmless marker (a file written during deserialization, or a polluted prototype) and require that the marker never fires. Functional tests round-trip the legitimate seed profile and preferences.
- Static analysis is strong for the obvious sinks (pickle, yaml.load, ObjectInputStream, TypeNameHandling) and weak for hand-rolled type resolvers and unsafe merges; each card's scannerRules lists what was checked and what it found.
References
- OWASP Deserialization Cheat Sheet
- OWASP Top 10:2025 A08 Software or Data Integrity Failures
- CWE-502: Deserialization of Untrusted Data
- CAPEC-586: Object Injection
- OWASP Top 10:2017 A8 Insecure Deserialization (background)
Attack patterns:
Practice in the browser
- Pickle deserialization lab: Load a session token without pickle · Practitioner, Python
Guides on this weakness
Keep untrusted values out of executable syntax
Choose a safe interface for each interpreter and bound the work performed by parsers and converters.
Treat model releases as executable supply-chain inputs
Verify provenance, constrain loading and preserve the relationship between model weights, configuration and runtime dependencies.
Parse bounded JSON and XML into an explicit application schema
Separate parser capabilities from schema rules, reject ambiguous and coercible inputs, and implement the boundary in a complete Python project.
Case study: Log4Shell (CVE-2021-44228)
How a JNDI lookup in Apache Log4j became internet-wide remote code execution, why transitive and shaded dependencies made it so hard to patch, which mitigations held, and what it teaches about dependency inventory and SBOMs.
Threat pattern library for threat modeling
Thirty-two recurring threat patterns grouped by identity, authorization, parsers, concurrency, integrations, data exposure, supply chain, availability and AI agents, each with where it shows up on a data-flow diagram, mitigations, a verification step and STRIDE, CWE and CAPEC links, plus a YAML catalog and a script that lists patterns per diagram element.