Insecure Deserialization Remediation

Risk Severity
๐Ÿ”ด Critical
Fix Effort
๐Ÿ—๏ธ High (Significant Work)
Est. Time
โฑ๏ธ 4-8 hours
Reference
A08:2021 CWE-502

Insecure deserialization occurs when applications deserialize untrusted data without proper validation, potentially leading to remote code execution. The safest approach is to avoid deserializing untrusted data entirely.

Critical Impact

Insecure deserialization often leads to remote code execution (RCE), making it one of the most dangerous vulnerability classes. Attacks like Java's "ysoserial" can instantly compromise servers.

Understanding the Risk

High-Risk Serialization

  • โ€ข Java: ObjectInputStream, XMLDecoder
  • โ€ข PHP: unserialize()
  • โ€ข Python: pickle, PyYAML (unsafe_load)
  • โ€ข .NET: BinaryFormatter, NetDataContractSerializer
  • โ€ข Ruby: Marshal.load

Attack Chain

  1. Attacker crafts malicious serialized object
  2. Application deserializes untrusted input
  3. Magic methods execute during deserialization
  4. Gadget chains lead to RCE or other exploits

Primary Defense: Avoid Native Serialization

Best Practice

Use language-agnostic formats like JSON for data exchange. Native serialization formats (Java serialized objects, pickle, etc.) should never be used with untrusted data.

Safe Alternatives

JSON

Language-agnostic, no code execution. Use with strict parsing.

Protocol Buffers

Strongly typed, efficient, schema-based serialization.

MessagePack

Binary JSON alternative, fast and safe.

Java Remediation

java
// AVOID: Direct deserialization of untrusted data
ObjectInputStream ois = new ObjectInputStream(untrustedInput);
Object obj = ois.readObject(); // DANGEROUS!

// OPTION 1: Use allowlist with ObjectInputFilter (Java 9+)
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "com.myapp.SafeClass;!*"  // Only allow specific classes
);
ois.setObjectInputFilter(filter);

// OPTION 2: Use a safe library like Apache Commons IO
// with look-ahead deserialization
ValidatingObjectInputStream vois = new ValidatingObjectInputStream(input);
vois.accept(SafeClass.class);
vois.reject("*");

// OPTION 3: Use JSON instead
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NONE); // Disable polymorphism
MyClass obj = mapper.readValue(json, MyClass.class);

Python Remediation

python
# AVOID: pickle with untrusted data
import pickle
obj = pickle.loads(untrusted_data)  # DANGEROUS!

# AVOID: unsafe YAML loading
import yaml
data = yaml.load(untrusted_yaml)  # DANGEROUS!

# SAFE: Use JSON
import json
data = json.loads(json_string)

# SAFE: Use safe YAML loader
import yaml
data = yaml.safe_load(yaml_string)

# If pickle is absolutely required, use hmac signing
import hmac
import hashlib

def safe_load(data, signature, secret_key):
    expected_sig = hmac.new(secret_key, data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected_sig):
        raise ValueError("Invalid signature")
    return pickle.loads(data)

PHP Remediation

php
// AVOID: unserialize on untrusted data
$obj = unserialize($untrusted);  // DANGEROUS!

// OPTION 1: Use JSON instead
$data = json_decode($json, true);

// OPTION 2: If unserialize is required, use allowed_classes
$obj = unserialize($data, [
    'allowed_classes' => ['SafeClass', 'AnotherSafeClass']
]);

// OPTION 3: Completely disable object instantiation
$obj = unserialize($data, ['allowed_classes' => false]);

// OPTION 4: Validate and sign serialized data
function safe_unserialize($data, $signature, $key) {
    $expected = hash_hmac('sha256', $data, $key);
    if (!hash_equals($expected, $signature)) {
        throw new Exception('Invalid signature');
    }
    return unserialize($data, ['allowed_classes' => ['SafeClass']]);
}

.NET Remediation

csharp
// AVOID: BinaryFormatter (deprecated in .NET 5+)
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(stream);  // DANGEROUS!

// SAFE: Use System.Text.Json
using System.Text.Json;
var obj = JsonSerializer.Deserialize<MyClass>(json);

// SAFE: Use DataContractSerializer with known types
var serializer = new DataContractSerializer(
    typeof(MyClass),
    new DataContractSerializerSettings {
        KnownTypes = new[] { typeof(SafeType) }
    }
);

// For XML, use XmlSerializer (safer than DataContractSerializer)
var serializer = new XmlSerializer(typeof(MyClass));

๐Ÿงช Testing Verification

Testing Approach

bash
# Java: Use ysoserial to generate test payloads
java -jar ysoserial.jar CommonsCollections1 "id" | base64

# Python: Test pickle handling
import pickle
malicious = b"cos\nsystem\n(S'id'\ntR."
# Application should reject this

# PHP: Test unserialize handling
O:8:"stdClass":1:{s:4:"test";s:2:"ok";}
# Should fail if object instantiation is blocked

# Check for:
# 1. Application crashes (may indicate vulnerable)
# 2. Command execution
# 3. File operations
# 4. Network connections

Common Mistakes

Blocklist Approach

Blocking known gadget classes fails as new chains are discovered

Correct Approach

Use allowlist of specific safe classes only

Signing After Serialization

Signing the serialized output doesn't prevent attacks if verification fails

Correct Approach

Verify signature BEFORE any deserialization attempt

Deserialization Prevention Checklist

  • โ˜ Native serialization avoided for untrusted data
  • โ˜ JSON or other safe formats used for data exchange
  • โ˜ If serialization required, strict allowlist of classes
  • โ˜ Integrity checks (HMAC) before deserialization
  • โ˜ Monitoring for deserialization-related errors
  • โ˜ Regular dependency updates (gadget chain libraries)