Treat any deserialization of data from outside your trust boundary as a finding until proven otherwise. Ask for a plain data format such as JSON, an integrity check before parsing, and an allow list of types that may be created. Then look for the language specific red flags below.
Why it is risky
The OWASP Deserialization Cheat Sheet says deserializing untrusted data can lead to denial of service, access control bypass or remote code execution. Native serialization formats can rebuild arbitrary object graphs, and attackers chain existing classes in your dependencies to reach dangerous behavior.
Core advice
| Advice | What it looks like in review |
|---|---|
| Prefer plain data formats | JSON or XML into simple data objects, not native object streams |
| Verify integrity first | A signature checked before any parsing |
| Allow list classes | An explicit list of types, not a block list, because new gadget classes keep appearing |
| Bound resources | Limits on depth, object count, array size and input size |
| Keep libraries current | No default polymorphic type handling left on |
Red flags by language
These come from the cheat sheet.
- Java: review readObject, readObjectNoData, readResolve, readExternal and readUnshared on ObjectInputStream. Use serialization filters with class allow lists. Overriding resolveClass alone is not enough. A stream header of AC ED 00 05, or rO0 in Base64, signals Java serialization.
- Python: never unpickle untrusted data. Be careful with jsonpickle decode and unsafe YAML loaders, and use yaml.safe_load for untrusted YAML.
- PHP: audit every unserialize call and how its input arrives. Replace it with json_decode for data exchange.
- .NET: do not use BinaryFormatter, which Microsoft states cannot be secured. Avoid TypeNameHandling other than None in JSON.NET, and do not let the data stream choose the target type.
# red flag
obj = pickle.loads(request.data)
# better: parse plain data, then build your own object
data = json.loads(request.data)
obj = Order(id=data["id"], qty=int(data["qty"]))
Steps for a review
- Search the diff for the calls above and for the formats they imply.
- Trace where the input comes from. Cookies, message queues, caches and uploaded files all count as input.
- If the source is trusted, ask how trust is enforced. A signature is stronger than a network boundary.
- If a native format is needed, ask for class allow lists and resource limits.
- Check dependency versions, because many deserialization exploits depend on library classes being present.
The dependency angle
Deserialization bugs often sit in a library in your tree, not in your own code, so a dependency bump that mentions deserialization in its notes is worth a read. Reachability matters here: whether your code actually reaches the vulnerable path decides how urgent it is.
Try it yourself
Open the reachability demo and compare the raw list of findings with the filtered one. Look at how reachability separates libraries that are present from vulnerable code your app calls.
Click through it at your own pace, or open it full screen.
Where Safeguard fits
Safeguard offers reachability analysis and dependency scanning, and you can try the free scan at gold.safeguard.sh/scan.
Quick check
Is a block list of dangerous classes enough?
No. OWASP says block lists are weaker because new gadget classes keep appearing. Use an allow list.
What replaces unserialize in PHP?
OWASP suggests json_decode for data exchange.
Sources
Last reviewed: 19 October 2026.