ASVS 5.0 V1.5.2 requires that deserialization of untrusted data enforces safe input handling, for example an allowlist of object types or a ban on client defined types, and that mechanisms documented as insecure are never used on untrusted input. It is a Level 2 requirement.
Why it matters
Native object deserializers rebuild objects by calling code in the classes named in the data. If the attacker picks the class, the attacker picks the code that runs. Java object streams, Python pickle, PHP unserialize, .NET BinaryFormatter and unsafe YAML loaders have all led to remote code execution in real applications.
The dangerous part is often not your code. A "gadget" class sitting in a dependency on the classpath can be chained into an attack. That makes the dependency list part of the risk.
How to implement it
First choice: do not deserialize native objects from outside. Accept plain JSON, validate it against a schema, and build your own objects from the fields.
import json
from pydantic import BaseModel, Field
class Order(BaseModel):
sku: str = Field(pattern=r"^[A-Z0-9]{6,12}$")
quantity: int = Field(ge=1, le=1000)
order = Order.model_validate(json.loads(body))
Never call pickle.loads on data from a user, a queue you do not fully control, or a cache another tenant can write to. For YAML, use yaml.safe_load.
If you must use Java serialization, install an allowlist filter and reject everything else:
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.lang.String;java.util.ArrayList;!*");
ObjectInputStream in = new ObjectInputStream(stream);
in.setObjectInputFilter(filter);
For Jackson, do not enable default typing for untrusted input. If you need polymorphism, use an explicit PolymorphicTypeValidator that lists the allowed base types.
Add size and depth limits too, so a small payload cannot expand into a huge object graph.
How to test it
Search the code for the dangerous calls: pickle.loads, ObjectInputStream, unserialize, BinaryFormatter, yaml.load. Each hit should be justified in review or removed. Then send a payload with an unexpected type name or a type outside your allowlist and confirm the request is rejected with a generic error and nothing is instantiated. Run a dependency scan to find libraries with known deserialization gadget issues, and patch them.
Gadgets, libraries and your dependency tree
A deserialization attack needs a class that does something dangerous when it is constructed or when one of its methods is called by the framework. Those classes are rarely yours. They sit in common libraries, which is why the same payload can work against many unrelated applications. Updating a library removes known gadgets, but new ones are found regularly, so patching helps without being enough on its own.
Reachability matters here. A vulnerable library that your code never loads is a lower priority than one that sits on the path of a deserializer fed by an external request. Reachability analysis in Safeguard helps you rank findings that way, so you fix the exploitable ones first.
Signed and encrypted blobs
Teams sometimes keep native serialization and add a signature, for example on a cookie or a view state value. That works only if the key stays secret, the signature is verified before any parsing, and the algorithm cannot be changed by the sender. If the key leaks, the old problem returns in full. Prefer a format that does not execute anything on load.
Message queues, caches and files
Internal does not mean trusted. A message queue shared between services, a cache that several tenants can write to, and files restored from a backup are all places where data may have come from a less trusted source than you assume. Apply the same rule: deserialize only types you expect, in a format that cannot carry code.
Common mistakes
- Treating internal queues and caches as trusted when they are not.
- Using a denylist of bad classes. New gadgets appear faster than lists are updated.
- Verifying the signature after deserializing instead of before.
- Leaving old libraries on the classpath that nobody calls.
Try it yourself
Dependencies are where gadgets hide. Check yours with the free in-browser dependency scan at https://gold.safeguard.sh/scan, and see how an inventory looks in the demo.
Click through it at your own pace, or open it full screen.
Quick check
Why is a denylist of dangerous classes weak?
You can only list the gadgets you already know. An allowlist of the few types you expect keeps working when a new gadget is found.
What is the safest replacement for native serialization?
A schema validated data format such as JSON, mapped by your own code into plain objects.