Serializable Data Element Containing non-Serializable Item Elements

Description

Serializable Data Element Containing non-Serializable Item Elements occurs when a product contains a serializable, storable data element (such as a field or member) that internally contains member elements that lack serializability. For example, a class marked as Serializable in Java (implementing java.io.Serializable) or .NET (using [Serializable] attribute) may contain fields of types that are not serializable. When serialization is attempted on such objects, runtime exceptions occur, causing application failures and potentially leaving the system in an inconsistent state.

Risk

While primarily a reliability issue, this weakness has security implications. Serialization failures at runtime can cause denial of service. If serialization is part of session management, failures can cause authentication or authorization state to be lost. Partial serialization before failure can result in data corruption. The unpredictable failure mode may be exploitable to trigger error conditions. Exception handling for serialization failures may leak sensitive information in error messages. Applications relying on serialization for data persistence or transfer may fail silently in production when types change.

Solution

Ensure all fields of serializable classes are themselves serializable or marked as transient (Java) / [NonSerialized] (.NET). Use static analysis tools to detect non-serializable fields in serializable classes. Implement custom serialization methods (writeObject/readObject) to handle non-serializable fields. Consider using serialization proxies for complex object graphs. Test serialization round-trips in unit tests. Use serialization frameworks that provide better error reporting. Review class changes for serialization compatibility. Consider alternative approaches like JSON serialization with explicit field mapping.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

DoS: Crash - Attempting to serialize an object with non-serializable members throws NotSerializableException.
OtherScope: Other

Reduce Reliability - The product fails unpredictably when serialization is attempted.
IntegrityScope: Integrity

Unexpected State - Partial serialization before failure can corrupt data.

Example Code

Vulnerable Code

// Vulnerable: Serializable class with non-serializable member
import java.io.*;
import java.sql.Connection;
import java.net.Socket;

public class VulnerableUserSession implements Serializable {

    private static final long serialVersionUID = 1L;

    private String userId;
    private String username;

    // Vulnerable: Connection is not serializable!
    private Connection dbConnection;

    // Vulnerable: Socket is not serializable!
    private Socket notificationSocket;

    // Vulnerable: Custom class that doesn't implement Serializable
    private DatabaseConfig dbConfig;

    // Vulnerable: Thread is not serializable
    private Thread backgroundWorker;

    public VulnerableUserSession(String userId, String username) {
        this.userId = userId;
        this.username = username;
        this.dbConnection = createConnection();
        this.notificationSocket = createSocket();
        this.dbConfig = new DatabaseConfig();  // Not serializable!
    }

    // When serialized:
    // java.io.NotSerializableException: java.sql.Connection
}

// Non-serializable configuration class
class DatabaseConfig {  // Missing 'implements Serializable'
    private String host;
    private int port;
    private String database;
}
// Vulnerable: Collection with non-serializable elements
import java.io.*;
import java.util.*;

public class VulnerableCache implements Serializable {

    private static final long serialVersionUID = 1L;

    // Vulnerable: List is serializable, but contents may not be!
    private List<Object> cachedObjects = new ArrayList<>();

    // Vulnerable: Map values are non-serializable
    private Map<String, Connection> connectionPool = new HashMap<>();

    // Vulnerable: Generic type erases to Object
    private List<Runnable> pendingTasks = new ArrayList<>();  // Runnable not serializable!

    public void addObject(Object obj) {
        // No check if obj is serializable!
        cachedObjects.add(obj);
    }

    public void addConnection(String name, Connection conn) {
        connectionPool.put(name, conn);  // Connection not serializable!
    }
}
// Vulnerable: C# class with non-serializable members
using System;
using System.Data.SqlClient;
using System.Net.Sockets;

[Serializable]
public class VulnerableSessionState
{
    public string UserId { get; set; }
    public string SessionToken { get; set; }

    // Vulnerable: SqlConnection is not serializable
    public SqlConnection DatabaseConnection { get; set; }

    // Vulnerable: TcpClient is not serializable
    public TcpClient NetworkClient { get; set; }

    // Vulnerable: Delegate (event handlers) are not serializable by default
    public EventHandler OnStateChanged { get; set; }

    // Vulnerable: Custom class without [Serializable]
    public ConfigSettings Config { get; set; }
}

// Not marked as serializable
public class ConfigSettings
{
    public string Setting1 { get; set; }
    public int Setting2 { get; set; }
}
# Vulnerable: Python class with non-picklable attributes
import pickle
import threading
import socket

class VulnerableSessionData:
    def __init__(self, user_id):
        self.user_id = user_id
        self.username = None

        # Vulnerable: Lock is not picklable
        self.lock = threading.Lock()

        # Vulnerable: Socket is not picklable
        self.socket = socket.socket()

        # Vulnerable: Lambda functions are problematic
        self.validator = lambda x: len(x) > 0

        # Vulnerable: File handles are not picklable
        self.log_file = open('/var/log/app.log', 'a')

    # Attempting to pickle this object:
    # TypeError: cannot pickle '_thread.lock' object

Fixed Code

// Fixed: Proper handling of non-serializable members
import java.io.*;
import java.sql.Connection;
import java.net.Socket;

public class FixedUserSession implements Serializable {

    private static final long serialVersionUID = 1L;

    private String userId;
    private String username;

    // Fixed: Mark non-serializable fields as transient
    private transient Connection dbConnection;
    private transient Socket notificationSocket;
    private transient Thread backgroundWorker;

    // Fixed: Use serializable configuration class
    private FixedDatabaseConfig dbConfig;

    public FixedUserSession(String userId, String username) {
        this.userId = userId;
        this.username = username;
        initializeTransientFields();
        this.dbConfig = new FixedDatabaseConfig();
    }

    private void initializeTransientFields() {
        this.dbConnection = createConnection();
        this.notificationSocket = createSocket();
    }

    // Fixed: Custom writeObject - handle transient initialization data
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        // Optionally write data needed to restore transient fields
        out.writeUTF(dbConfig.getConnectionString());
    }

    // Fixed: Custom readObject - restore transient fields
    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        // Read restoration data
        String connString = in.readUTF();
        // Reinitialize transient fields after deserialization
        initializeTransientFields();
    }

    // Fixed: Prevent deserialization attacks
    private void readObjectNoData() throws InvalidObjectException {
        throw new InvalidObjectException("Stream data required");
    }
}

// Fixed: Serializable configuration class
class FixedDatabaseConfig implements Serializable {
    private static final long serialVersionUID = 1L;

    private String host;
    private int port;
    private String database;
    // Note: Don't store credentials here - they should be transient

    private transient Connection cachedConnection;

    public String getConnectionString() {
        return String.format("jdbc:mysql://%s:%d/%s", host, port, database);
    }
}
// Fixed: Type-safe serializable collections
import java.io.*;
import java.util.*;

public class FixedCache implements Serializable {

    private static final long serialVersionUID = 1L;

    // Fixed: Use type-safe collection with serializable elements
    private List<SerializableCacheEntry> cachedObjects = new ArrayList<>();

    // Fixed: Store connection parameters, not connections
    private Map<String, ConnectionParameters> connectionConfigs = new HashMap<>();

    // Fixed: Transient for non-serializable collections
    private transient Map<String, Connection> activeConnections;
    private transient List<Runnable> pendingTasks;

    public void addCacheEntry(String key, String value) {
        // Fixed: Only accept serializable entries
        cachedObjects.add(new SerializableCacheEntry(key, value));
    }

    public void configureConnection(String name, String host, int port, String db) {
        // Fixed: Store parameters, not connection
        connectionConfigs.put(name, new ConnectionParameters(host, port, db));
    }

    public Connection getConnection(String name) {
        if (activeConnections == null) {
            activeConnections = new HashMap<>();
        }
        // Lazy create connections from stored parameters
        return activeConnections.computeIfAbsent(name, n ->
            createConnection(connectionConfigs.get(n))
        );
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        // Initialize transient fields
        activeConnections = new HashMap<>();
        pendingTasks = new ArrayList<>();
    }
}

// Fixed: Serializable cache entry
class SerializableCacheEntry implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String key;
    private final String value;
    private final long timestamp;

    public SerializableCacheEntry(String key, String value) {
        this.key = key;
        this.value = value;
        this.timestamp = System.currentTimeMillis();
    }
}

// Fixed: Serializable connection parameters
class ConnectionParameters implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String host;
    private final int port;
    private final String database;

    public ConnectionParameters(String host, int port, String database) {
        this.host = host;
        this.port = port;
        this.database = database;
    }
}
// Fixed: C# with proper serialization handling
using System;
using System.Data.SqlClient;
using System.Runtime.Serialization;

[Serializable]
public class FixedSessionState : ISerializable, IDeserializationCallback
{
    public string UserId { get; set; }
    public string SessionToken { get; set; }

    // Fixed: NonSerialized attribute for non-serializable fields
    [NonSerialized]
    private SqlConnection _databaseConnection;

    [NonSerialized]
    private TcpClient _networkClient;

    // Fixed: Store connection parameters instead
    public string ConnectionString { get; private set; }
    public string ServerAddress { get; private set; }
    public int ServerPort { get; private set; }

    // Fixed: Use serializable config class
    public SerializableConfigSettings Config { get; set; }

    public FixedSessionState()
    {
    }

    // Fixed: ISerializable constructor
    protected FixedSessionState(SerializationInfo info, StreamingContext context)
    {
        UserId = info.GetString("UserId");
        SessionToken = info.GetString("SessionToken");
        ConnectionString = info.GetString("ConnectionString");
        ServerAddress = info.GetString("ServerAddress");
        ServerPort = info.GetInt32("ServerPort");
        Config = (SerializableConfigSettings)info.GetValue("Config",
            typeof(SerializableConfigSettings));
    }

    // Fixed: Explicit serialization
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("UserId", UserId);
        info.AddValue("SessionToken", SessionToken);
        info.AddValue("ConnectionString", ConnectionString);
        info.AddValue("ServerAddress", ServerAddress);
        info.AddValue("ServerPort", ServerPort);
        info.AddValue("Config", Config);
    }

    // Fixed: Restore transient fields after deserialization
    public void OnDeserialization(object sender)
    {
        // Reinitialize non-serializable resources
        if (!string.IsNullOrEmpty(ConnectionString))
        {
            _databaseConnection = new SqlConnection(ConnectionString);
        }
    }
}

[Serializable]
public class SerializableConfigSettings
{
    public string Setting1 { get; set; }
    public int Setting2 { get; set; }
}
# Fixed: Python class with proper pickle handling
import pickle
import threading
import socket
from typing import Optional

class FixedSessionData:
    def __init__(self, user_id: str):
        self.user_id = user_id
        self.username: Optional[str] = None

        # Store parameters instead of non-picklable objects
        self._server_host: str = ""
        self._server_port: int = 0
        self._log_path: str = '/var/log/app.log'

        # Non-picklable resources initialized lazily
        self._lock: Optional[threading.Lock] = None
        self._socket: Optional[socket.socket] = None
        self._log_file = None

        # Store validation rules as data, not lambdas
        self._min_length: int = 0

    def _ensure_lock(self) -> threading.Lock:
        """Lazy initialization of lock"""
        if self._lock is None:
            self._lock = threading.Lock()
        return self._lock

    def validate(self, value: str) -> bool:
        """Validation method instead of lambda"""
        return len(value) > self._min_length

    # Fixed: Control what gets pickled
    def __getstate__(self):
        """Return picklable state"""
        state = self.__dict__.copy()
        # Remove non-picklable attributes
        state['_lock'] = None
        state['_socket'] = None
        state['_log_file'] = None
        return state

    def __setstate__(self, state):
        """Restore state from pickle"""
        self.__dict__.update(state)
        # Non-picklable resources will be lazily reinitialized

    # Fixed: Use __reduce__ for more control
    def __reduce__(self):
        """Define how to reconstruct the object"""
        return (
            self.__class__,
            (self.user_id,),
            self.__getstate__()
        )


# Alternative: Use dataclass with explicit serialization
from dataclasses import dataclass, field
import json

@dataclass
class SerializableSession:
    user_id: str
    username: str = ""
    server_host: str = ""
    server_port: int = 0

    # Exclude from serialization using field metadata
    _connection: socket.socket = field(default=None, repr=False, compare=False)

    def to_json(self) -> str:
        """Explicit JSON serialization"""
        return json.dumps({
            'user_id': self.user_id,
            'username': self.username,
            'server_host': self.server_host,
            'server_port': self.server_port
        })

    @classmethod
    def from_json(cls, json_str: str) -> 'SerializableSession':
        """Explicit JSON deserialization"""
        data = json.loads(json_str)
        return cls(**data)

CVE Examples

This CWE is marked as PROHIBITED for direct CVE mapping as it represents a quality/reliability concern rather than a direct security vulnerability.


  • CWE-1076: Insufficient Adherence to Expected Conventions (parent)
  • CWE-1066: Missing Serialization Control Element (related)
  • CWE-502: Deserialization of Untrusted Data (can contribute to)

References

  1. MITRE Corporation. "CWE-1070: Serializable Data Element Containing non-Serializable Item Elements." https://cwe.mitre.org/data/definitions/1070.html
  2. Oracle. "Java Object Serialization Specification."
  3. Microsoft. "Serialization in .NET."