Missing Serialization Control Element
Description
Missing Serialization Control Element occurs when a product contains a serializable data element that does not have an associated serialization method. In languages like Java and C#, classes can be marked as serializable (implementing Serializable or using [Serializable] attribute), but the developer may fail to implement proper serialization controls. Without explicit writeObject/readObject methods in Java or proper serialization callbacks in .NET, the default serialization behavior may expose sensitive data, skip validation, or create security vulnerabilities during deserialization.
Risk
Missing serialization controls create direct security risks. Sensitive fields that should be excluded from serialization (passwords, tokens, internal state) may be inadvertently serialized and exposed. Without custom readObject/readResolve methods, deserialized objects may bypass constructors and validation logic. Default deserialization can be exploited for object injection attacks. Serialized data may include more information than intended, creating information disclosure risks. Version compatibility issues can arise when class structure changes. Without proper controls, attackers can craft malicious serialized data that creates objects in invalid states.
Solution
Implement custom serialization methods (writeObject, readObject, readResolve in Java; ISerializable interface in .NET). Mark sensitive fields as transient (Java) or [NonSerialized] (.NET) to exclude them from serialization. Use serialization proxies for complex objects. Implement validation in deserialization methods to ensure object integrity. Use serialVersionUID to manage version compatibility. Consider using safer alternatives like JSON with explicit field mapping instead of binary serialization. Implement readObjectNoData for handling inheritance edge cases. Use whitelisting for allowed classes during deserialization.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Default serialization may expose sensitive fields that should not be serialized. |
| Integrity | Scope: Integrity Modify Application Data - Missing readObject validation allows creation of objects in invalid states. |
| Other | Scope: Other Reduce Reliability - Serialization without proper controls can cause exceptions and runtime errors. |
Example Code
Vulnerable Code
// Vulnerable: Serializable class without serialization control
public class VulnerableUserCredentials implements Serializable {
// Vulnerable: No serialVersionUID defined
// private static final long serialVersionUID = 1L;
private String username;
private String password; // Sensitive - will be serialized!
private String sessionToken; // Sensitive - will be serialized!
private transient String tempData; // At least this one is transient
private boolean isAdmin;
public VulnerableUserCredentials(String username, String password) {
this.username = username;
setPassword(password); // Validation here...
}
public void setPassword(String password) {
// Validation logic
if (password.length() < 8) {
throw new IllegalArgumentException("Password too short");
}
this.password = hashPassword(password);
}
// Vulnerable: No writeObject - sensitive data serialized
// Vulnerable: No readObject - validation bypassed on deserialization!
// When deserialized:
// 1. password is exposed in serialized form
// 2. password validation is bypassed
// 3. sessionToken is leaked
// 4. isAdmin could be manipulated
}
// Serialization attack:
// 1. Serialize valid object
// 2. Modify serialized bytes to set isAdmin = true
// 3. Deserialize - bypass constructor validation!
// Vulnerable: Singleton without serialization protection
public class VulnerableSingleton implements Serializable {
private static final VulnerableSingleton INSTANCE = new VulnerableSingleton();
private String sensitiveConfig;
private VulnerableSingleton() {
// Private constructor
loadConfig();
}
public static VulnerableSingleton getInstance() {
return INSTANCE;
}
// Vulnerable: No readResolve - deserialization creates new instance!
// Singleton pattern broken by serialization
// Attack:
// 1. Serialize the singleton
// 2. Deserialize multiple times
// 3. Get multiple "singleton" instances with potentially different state
}
// Vulnerable: .NET class without proper serialization control
[Serializable]
public class VulnerableSession
{
public string SessionId { get; set; }
public string UserId { get; set; }
public string AuthToken { get; set; } // Sensitive!
public DateTime CreatedAt { get; set; }
public bool IsAuthenticated { get; set; }
public List<string> Permissions { get; set; }
// Vulnerable: No [NonSerialized] on sensitive fields
// Vulnerable: No ISerializable implementation
// Vulnerable: Validation in constructor bypassed by deserialization
public VulnerableSession(string userId)
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentException("UserId required");
UserId = userId;
SessionId = GenerateSecureId();
CreatedAt = DateTime.UtcNow;
Permissions = new List<string>();
}
// Deserialization bypasses constructor entirely!
}
# Vulnerable: Python pickle without controls
import pickle
class VulnerableUser:
def __init__(self, username, password):
self.username = username
self._password_hash = self._hash_password(password)
self._secret_key = self._generate_secret()
self.is_admin = False
def _hash_password(self, password):
# Password hashing
return hash(password)
def _generate_secret(self):
# Generate secret key
import secrets
return secrets.token_hex(32)
# Vulnerable: No __reduce__ or __getstate__/__setstate__
# All attributes including _secret_key are pickled!
# Attack: Modify pickled data to set is_admin = True
# Even worse - pickle can execute arbitrary code:
class Malicious:
def __reduce__(self):
import os
return (os.system, ('rm -rf /',))
# Deserializing untrusted pickle data = Remote Code Execution!
Fixed Code
// Fixed: Proper serialization controls
public class FixedUserCredentials implements Serializable {
// Fixed: Explicit serialVersionUID
private static final long serialVersionUID = 1L;
private String username;
// Fixed: Transient sensitive fields
private transient String password;
private transient String sessionToken;
private boolean isAdmin;
public FixedUserCredentials(String username, String password) {
this.username = username;
setPassword(password);
}
public void setPassword(String password) {
validatePassword(password);
this.password = hashPassword(password);
}
private void validatePassword(String password) {
if (password == null || password.length() < 8) {
throw new IllegalArgumentException("Password must be at least 8 characters");
}
}
// Fixed: Custom writeObject - control what gets serialized
private void writeObject(ObjectOutputStream out) throws IOException {
// Only serialize non-sensitive fields
out.defaultWriteObject();
// Don't write password or sessionToken
}
// Fixed: Custom readObject - validate on deserialization
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Fixed: Validate deserialized state
if (username == null || username.isEmpty()) {
throw new InvalidObjectException("Username cannot be null");
}
// Fixed: Initialize transient fields safely
this.sessionToken = null; // Must re-authenticate
// Fixed: Validate security-critical fields
// isAdmin should only be true through proper authorization
}
// Fixed: Prevent subclass attacks
private void readObjectNoData() throws InvalidObjectException {
throw new InvalidObjectException("Stream data required");
}
}
// Fixed: Singleton with serialization protection
public class FixedSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final FixedSingleton INSTANCE = new FixedSingleton();
private transient String sensitiveConfig;
private FixedSingleton() {
loadConfig();
}
public static FixedSingleton getInstance() {
return INSTANCE;
}
// Fixed: readResolve returns the singleton instance
private Object readResolve() throws ObjectStreamException {
// Return the singleton instance, discarding deserialized copy
return INSTANCE;
}
// Fixed: Prevent serialization of sensitive state
private void writeObject(ObjectOutputStream out) throws IOException {
// Don't serialize sensitive config
out.defaultWriteObject();
}
private void loadConfig() {
// Load configuration
}
}
// Alternative: Use enum singleton (inherently serialization-safe)
public enum FixedEnumSingleton {
INSTANCE;
private transient String sensitiveConfig;
public void doSomething() {
// Singleton behavior
}
}
// Fixed: .NET class with ISerializable
[Serializable]
public class FixedSession : ISerializable
{
public string SessionId { get; private set; }
public string UserId { get; private set; }
public DateTime CreatedAt { get; private set; }
public bool IsAuthenticated { get; private set; }
public IReadOnlyList<string> Permissions => _permissions.AsReadOnly();
// Fixed: NonSerialized attribute on sensitive fields
[NonSerialized]
private string _authToken;
private List<string> _permissions;
public FixedSession(string userId)
{
ValidateUserId(userId);
UserId = userId;
SessionId = GenerateSecureId();
CreatedAt = DateTime.UtcNow;
_permissions = new List<string>();
IsAuthenticated = false;
}
// Fixed: Serialization constructor
protected FixedSession(SerializationInfo info, StreamingContext context)
{
// Fixed: Explicit deserialization with validation
SessionId = info.GetString("SessionId");
UserId = info.GetString("UserId");
CreatedAt = info.GetDateTime("CreatedAt");
// Fixed: Validate deserialized data
ValidateUserId(UserId);
// Fixed: Security-critical fields require re-authentication
IsAuthenticated = false; // Must re-authenticate after deserialization
_authToken = null;
_permissions = new List<string>();
}
// Fixed: Explicit serialization
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Only serialize non-sensitive data
info.AddValue("SessionId", SessionId);
info.AddValue("UserId", UserId);
info.AddValue("CreatedAt", CreatedAt);
// Don't serialize: _authToken, IsAuthenticated, _permissions
}
private void ValidateUserId(string userId)
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentException("UserId is required");
}
}
# Fixed: Python with controlled serialization
import json
from dataclasses import dataclass, field
from typing import List
@dataclass
class FixedUser:
username: str
_password_hash: str = field(repr=False)
is_admin: bool = False
permissions: List[str] = field(default_factory=list)
# Secret key should never be serialized
_secret_key: str = field(default=None, repr=False, compare=False)
def __post_init__(self):
# Generate secret key on creation
if self._secret_key is None:
import secrets
self._secret_key = secrets.token_hex(32)
# Fixed: Control pickle behavior
def __getstate__(self):
"""Return state for pickling - exclude sensitive data"""
state = self.__dict__.copy()
# Don't pickle the secret key
del state['_secret_key']
return state
def __setstate__(self, state):
"""Restore state from pickle - regenerate sensitive data"""
self.__dict__.update(state)
# Regenerate secret key
import secrets
self._secret_key = secrets.token_hex(32)
# Fixed: Use JSON for safer serialization
def to_json(self) -> str:
"""Serialize to JSON - explicitly control fields"""
return json.dumps({
'username': self.username,
'is_admin': self.is_admin,
'permissions': self.permissions
# Exclude password_hash and secret_key
})
@classmethod
def from_json(cls, json_str: str, password_hash: str) -> 'FixedUser':
"""Deserialize from JSON with validation"""
data = json.loads(json_str)
# Fixed: Validate before creating object
if not data.get('username'):
raise ValueError("Username is required")
# Fixed: is_admin requires explicit authorization
# Don't trust is_admin from serialized data
return cls(
username=data['username'],
_password_hash=password_hash,
is_admin=False, # Always false - require re-authorization
permissions=[] # Always empty - require re-authorization
)
# Better alternative: Don't use pickle for untrusted data
# Use JSON with explicit schema validation
CVE Examples
- CVE-2015-7501: Apache Commons Collections deserialization vulnerability allowed remote code execution due to missing serialization controls.
- CVE-2016-1000031: Apache Commons FileUpload deserialization vulnerability.
Related CWEs
- CWE-710: Improper Adherence to Coding Standards (parent)
- CWE-502: Deserialization of Untrusted Data (related)
- CWE-1006: Bad Coding Practices (category member)
References
- MITRE Corporation. "CWE-1066: Missing Serialization Control Element." https://cwe.mitre.org/data/definitions/1066.html
- Bloch, Joshua. "Effective Java, Third Edition." Items 85-90 on Serialization.
- OWASP. "Deserialization Cheat Sheet."