Deserialization Prevention
Objects Without Surprises
Secure web development is not about extra friction, but about better defaults in design, code, and release flow.
With Deserialization Prevention, the greatest gains come from safe defaults that are automatically enforced in every release.
This makes security less of a separate afterthought check and more of a standard quality of your product.
Immediate measures (15 minutes)
Why this matters
The core of Deserialization Prevention is risk reduction in practice. Technical context supports the choice of measures, but implementation and assurance are central.
Defense
General principles
1. Never deserialize untrusted data with type information
This is the golden rule. If you receive data from a user and that
data determines which type of object gets created, then you have a
problem. Use formats that do not contain type information (JSON
without TypeNameHandling, protobuf with a fixed schema) or
strictly validate which types are allowed.
2. Whitelist classes
If you absolutely must deserialize with type information, use a whitelist of allowed classes. Not a blacklist -- that is always incomplete.
Java (serialization filter, JEP 290):
// Only allow specific classes
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.myapp.model.*;!*"
);
ObjectInputStream ois = new ObjectInputStream(stream);
ois.setObjectInputFilter(filter);.NET:
// Use System.Text.Json instead of Newtonsoft.Json
// Or if you must use Newtonsoft:
var settings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.None // NEVER Auto/Objects/All
};PHP:
<?php
// Use allowed_classes parameter (PHP 7+)
$obj = unserialize($data, ['allowed_classes' => ['User', 'Product']]);
// Or better: do not use unserialize() on user input at all
// Use JSON:
$data = json_decode($input, true);3. Integrity checks
Add an HMAC (Hash-based Message Authentication Code) to serialized data. If the data has been modified, you detect that before deserialization:
// Before serialization
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] signature = mac.doFinal(serializedData);
// Send serializedData + signature
// Before deserialization
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] expectedSignature = mac.doFinal(receivedData);
if (!MessageDigest.isEqual(expectedSignature, receivedSignature)) {
throw new SecurityException("Data has been modified!");
}4. Remove unnecessary gadgets from the classpath
In Java: if you are not actively using Apache Commons Collections, remove it from your classpath. No gadgets on the classpath = no gadget chains.
5. Upgrade
Many of these vulnerabilities have been fixed in newer versions: - PHP
8.0 has removed most type juggling behavior -
lodash.merge filters __proto__ since version
4.6.2 - .NET's BinaryFormatter is deprecated and removed
in .NET 9 - Java's serialization filters (JEP 290) have been available since
Java 9
Language-specific defense
Java:
// DON'T:
ObjectInputStream ois = new ObjectInputStream(untrustedStream);
Object obj = ois.readObject();
// DO: use JSON (Jackson, Gson) with POJOs
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
// Deserialize to a specific type, not to Object
User user = mapper.readValue(jsonString, User.class);.NET:
// DON'T:
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(stream);
// DON'T:
var settings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto
};
// DO: System.Text.Json (no TypeNameHandling)
var user = JsonSerializer.Deserialize<User>(jsonString);PHP:
<?php
// DON'T:
$obj = unserialize($_COOKIE['session']);
// DO: JSON
$data = json_decode($_COOKIE['session'], true);
// DON'T:
if (md5($input) == $stored_hash) { ... }
// DO: strict comparison and password_verify
if (password_verify($input, $stored_hash)) { ... }JavaScript:
// DON'T: vulnerable deep merge
function merge(target, source) {
for (let key in source) {
target[key] = source[key]; // Polluted!
}
}
// DO: filter prototype keys
function safeMerge(target, source) {
for (let key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor') continue;
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = target[key] || {};
safeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
// OR: use Object.create(null) for prototype-less objects
const safeStore = Object.create(null);
// OR: use Map
const safeMap = new Map();Reference table
Deserialization risks per language
| Language | Sink | Detection | Gadget tool | Defense |
|---|---|---|---|---|
| Java | ObjectInputStream.readObject() |
AC ED 00 05 / rO0AB |
ysoserial | JEP 290 filters, whitelist classes |
| .NET | BinaryFormatter.Deserialize() |
Base64 in ViewState/cookies | ysoserial.net | System.Text.Json, no
TypeNameHandling |
| PHP | unserialize() |
O: prefix in data |
PHPGGC | json_decode(), allowed_classes |
| Python | pickle.loads() |
\x80\x05 header |
-- | json.loads(), never pickle on user input |
| Ruby | Marshal.load() |
\x04\x08 header |
-- | JSON.parse() |
Magic bytes for detection
| Format | Hex | Base64 prefix |
|---|---|---|
| Java serialized | AC ED 00 05 |
rO0ABQ |
| .NET BinaryFormatter | Variable | Variable (check ViewState) |
| Python pickle (v5) | 80 05 |
gAU |
| Ruby Marshal (4.8) | 04 08 |
BAg |
| PHP serialized | Readable: O:, a:, s: |
N/A (text) |
PHP type juggling cheat sheet
| Expression | Result | Why |
|---|---|---|
"0e123" == "0e456" |
true |
Both are 0 in scientific notation |
true == "anything" |
true |
Bool true == any non-empty string |
0 == "php" |
true (< 8.0) |
"php" cast to int 0 |
"" == null |
true |
Empty string is null-like |
"0" == false |
true |
String "0" is falsy |
[] == null |
false |
Array is not null |
strcmp([], "str") |
NULL |
strcmp crashes on arrays |
NULL == 0 |
true |
NULL cast to int 0 |
Prototype pollution RCE chains
| Template engine | Polluted property | Impact |
|---|---|---|
| EJS | outputFunctionName |
RCE via template render |
| Pug | block |
RCE via template compile |
| Handlebars | pendingContent |
RCE via compiler |
| Nunjucks | env |
RCE via environment |
Tools
| Tool | URL | Use |
|---|---|---|
| ysoserial | https://github.com/frohoff/ysoserial |
Java deserialization payloads |
| ysoserial.net | https://github.com/pwntester/ysoserial.net |
.NET deserialization payloads |
| PHPGGC | https://github.com/ambionics/phpggc |
PHP gadget chain payloads |
| marshalsec | https://github.com/mbechler/marshalsec |
Various marshalling formats |
| JNDI safety checklist | Internal guidelines | Safe parser and lookup configuration |
Summary
Deserialization is the problem that arises when we forget that not all suitcases are packed by friends. We take binary blobs, JSON with type hints, and PHP strings from the internet, and we build objects from them in the memory of our server. Objects that have methods. Objects that do things.
Java's gadget chains show how existing libraries can be chained together
into a remote code execution machine. .NET's
BinaryFormatter and ViewState prove that the problem is not
language-bound. PHP's type juggling demonstrates that you do not even
need deserialization -- a == instead of
=== is enough to bypass authentication. And JavaScript's
prototype pollution shows that enriching the prototype chain
of all objects in an application is just one __proto__
property away.
The defense is conceptually simple: do not trust user data
to construct objects. Use type-safe formats without
class information. Whitelist what may be deserialized. Validate
integrity with HMACs. And if you write PHP, use
===. Always. Everywhere. No exceptions.
Further reading in the knowledge base
These articles in the portal provide more background and practical context:
- APIs -- the invisible glue of the internet
- SSL/TLS -- why that padlock in your browser matters
- Encryption -- the art of making things unreadable
- Password hashing -- how websites store your password
- Penetration tests vs. vulnerability scans
You need an account to access the knowledge base. Log in or register.
Related security measures
These articles provide additional context and depth: