Serialisierbares Datenelement mit nicht-serialisierbaren Elementelementen

Beschreibung

Serialisierbares Datenelement mit nicht-serialisierbaren Elementelementen tritt auf, wenn ein Produkt ein serialisierbares, speicherbares Datenelement (wie ein Feld oder Member) enthält, das intern Memberelemente enthält, denen Serialisierbarkeit fehlt. Zum Beispiel kann eine Klasse, die in Java als Serializable markiert ist (java.io.Serializable implementierend) oder in .NET (mit [Serializable]-Attribut), Felder von Typen enthalten, die nicht serialisierbar sind. Wenn Serialisierung bei solchen Objekten versucht wird, treten Laufzeitausnahmen auf, die Anwendungsfehler verursachen und das System möglicherweise in einem inkonsistenten Zustand belassen.

Risiko

Obwohl hauptsächlich ein Zuverlässigkeitsproblem, hat diese Schwäche Sicherheitsimplikationen. Serialisierungsfehler zur Laufzeit können Denial-of-Service verursachen. Wenn Serialisierung Teil der Sitzungsverwaltung ist, können Fehler dazu führen, dass Authentifizierungs- oder Autorisierungszustand verloren geht. Teilweise Serialisierung vor dem Fehler kann zu Datenbeschädigung führen. Der unvorhersehbare Fehlermodus kann ausnutzbar sein, um Fehlerbedingungen auszulösen. Ausnahmebehandlung für Serialisierungsfehler kann sensible Informationen in Fehlermeldungen leaken. Anwendungen, die sich auf Serialisierung für Datenpersistenz oder -übertragung verlassen, können in der Produktion stillschweigend fehlschlagen, wenn sich Typen ändern.

Lösung

Stellen Sie sicher, dass alle Felder serialisierbarer Klassen selbst serialisierbar sind oder als transient (Java) / [NonSerialized] (.NET) markiert sind. Verwenden Sie statische Analysetools, um nicht-serialisierbare Felder in serialisierbaren Klassen zu erkennen. Implementieren Sie benutzerdefinierte Serialisierungsmethoden (writeObject/readObject), um nicht-serialisierbare Felder zu behandeln. Erwägen Sie die Verwendung von Serialisierungs-Proxies für komplexe Objektgraphen. Testen Sie Serialisierungs-Roundtrips in Unit-Tests. Verwenden Sie Serialisierungs-Frameworks, die bessere Fehlerberichte bieten. Überprüfen Sie Klassenänderungen auf Serialisierungskompatibilität. Erwägen Sie alternative Ansätze wie JSON-Serialisierung mit explizitem Feldmapping.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz - Der Versuch, ein Objekt mit nicht-serialisierbaren Membern zu serialisieren, wirft NotSerializableException.
AndereBereich: Ändere

Reduzierte Zuverlässigkeit - Das Produkt schlägt unvorhersehbar fehl, wenn Serialisierung versucht wird.
IntegritätBereich: Integrität

Unerwarteter Zustand - Teilweise Serialisierung vor dem Fehler kann Daten beschädigen.

Beispielcode

Anfälliger Code

// Anfällig: Serialisierbare Klasse mit nicht-serialisierbarem 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;

    // Anfällig: Connection ist nicht serialisierbar!
    private Connection dbConnection;

    // Anfällig: Socket ist nicht serialisierbar!
    private Socket notificationSocket;

    // Anfällig: Benutzerdefinierte Klasse die Serializable nicht implementiert
    private DatabaseConfig dbConfig;

    // Anfällig: Thread ist nicht serialisierbar
    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();  // Nicht serialisierbar!
    }

    // Bei Serialisierung:
    // java.io.NotSerializableException: java.sql.Connection
}

// Nicht-serialisierbare Konfigurationsklasse
class DatabaseConfig {  // Fehlendes 'implements Serializable'
    private String host;
    private int port;
    private String database;
}
// Anfällig: Collection mit nicht-serialisierbaren Elementen
import java.io.*;
import java.util.*;

public class VulnerableCache implements Serializable {

    private static final long serialVersionUID = 1L;

    // Anfällig: List ist serialisierbar, aber Inhalte möglicherweise nicht!
    private List<Object> cachedObjects = new ArrayList<>();

    // Anfällig: Map-Werte sind nicht-serialisierbar
    private Map<String, Connection> connectionPool = new HashMap<>();

    // Anfällig: Generischer Typ wird zu Object gelöscht
    private List<Runnable> pendingTasks = new ArrayList<>();  // Runnable nicht serialisierbar!

    public void addObject(Object obj) {
        // Keine Prüfung ob obj serialisierbar ist!
        cachedObjects.add(obj);
    }

    public void addConnection(String name, Connection conn) {
        connectionPool.put(name, conn);  // Connection nicht serialisierbar!
    }
}
// Anfällig: C#-Klasse mit nicht-serialisierbaren Membern
using System;
using System.Data.SqlClient;
using System.Net.Sockets;

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

    // Anfällig: SqlConnection ist nicht serialisierbar
    public SqlConnection DatabaseConnection { get; set; }

    // Anfällig: TcpClient ist nicht serialisierbar
    public TcpClient NetworkClient { get; set; }

    // Anfällig: Delegate (Event-Handler) sind standardmäßig nicht serialisierbar
    public EventHandler OnStateChanged { get; set; }

    // Anfällig: Benutzerdefinierte Klasse ohne [Serializable]
    public ConfigSettings Config { get; set; }
}

// Nicht als serialisierbar markiert
public class ConfigSettings
{
    public string Setting1 { get; set; }
    public int Setting2 { get; set; }
}
# Anfällig: Python-Klasse mit nicht-pickelbaren Attributen
import pickle
import threading
import socket

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

        # Anfällig: Lock ist nicht pickelbar
        self.lock = threading.Lock()

        # Anfällig: Socket ist nicht pickelbar
        self.socket = socket.socket()

        # Anfällig: Lambda-Funktionen sind problematisch
        self.validator = lambda x: len(x) > 0

        # Anfällig: Datei-Handles sind nicht pickelbar
        self.log_file = open('/var/log/app.log', 'a')

    # Versuch dieses Objekt zu pickeln:
    # TypeError: cannot pickle '_thread.lock' object

Korrigierter Code

// Korrigiert: Ordnungsgemäße Behandlung nicht-serialisierbarer Member
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;

    // Korrigiert: Nicht-serialisierbare Felder als transient markieren
    private transient Connection dbConnection;
    private transient Socket notificationSocket;
    private transient Thread backgroundWorker;

    // Korrigiert: Serialisierbare Konfigurationsklasse verwenden
    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();
    }

    // Korrigiert: Benutzerdefiniertes writeObject - transiente Initialisierungsdaten behandeln
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        // Optional Daten schreiben die zur Wiederherstellung transienter Felder benötigt werden
        out.writeUTF(dbConfig.getConnectionString());
    }

    // Korrigiert: Benutzerdefiniertes readObject - transiente Felder wiederherstellen
    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        // Wiederherstellungsdaten lesen
        String connString = in.readUTF();
        // Transiente Felder nach Deserialisierung reinitialisieren
        initializeTransientFields();
    }

    // Korrigiert: Deserialisierungsangriffe verhindern
    private void readObjectNoData() throws InvalidObjectException {
        throw new InvalidObjectException("Stream-Daten erforderlich");
    }
}

// Korrigiert: Serialisierbare Konfigurationsklasse
class FixedDatabaseConfig implements Serializable {
    private static final long serialVersionUID = 1L;

    private String host;
    private int port;
    private String database;
    // Hinweis: Credentials hier nicht speichern - sollten transient sein

    private transient Connection cachedConnection;

    public String getConnectionString() {
        return String.format("jdbc:mysql://%s:%d/%s", host, port, database);
    }
}
// Korrigiert: Typsichere serialisierbare Collections
import java.io.*;
import java.util.*;

public class FixedCache implements Serializable {

    private static final long serialVersionUID = 1L;

    // Korrigiert: Typsichere Collection mit serialisierbaren Elementen verwenden
    private List<SerializableCacheEntry> cachedObjects = new ArrayList<>();

    // Korrigiert: Verbindungsparameter speichern, nicht Verbindungen
    private Map<String, ConnectionParameters> connectionConfigs = new HashMap<>();

    // Korrigiert: Transient für nicht-serialisierbare Collections
    private transient Map<String, Connection> activeConnections;
    private transient List<Runnable> pendingTasks;

    public void addCacheEntry(String key, String value) {
        // Korrigiert: Nur serialisierbare Einträge akzeptieren
        cachedObjects.add(new SerializableCacheEntry(key, value));
    }

    public void configureConnection(String name, String host, int port, String db) {
        // Korrigiert: Parameter speichern, nicht Verbindung
        connectionConfigs.put(name, new ConnectionParameters(host, port, db));
    }

    public Connection getConnection(String name) {
        if (activeConnections == null) {
            activeConnections = new HashMap<>();
        }
        // Verbindungen lazy aus gespeicherten Parametern erstellen
        return activeConnections.computeIfAbsent(name, n ->
            createConnection(connectionConfigs.get(n))
        );
    }

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

// Korrigiert: Serialisierbarer Cache-Eintrag
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();
    }
}

// Korrigiert: Serialisierbare Verbindungsparameter
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;
    }
}
// Korrigiert: C# mit ordnungsgemäßer Serialisierungsbehandlung
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; }

    // Korrigiert: NonSerialized-Attribut für nicht-serialisierbare Felder
    [NonSerialized]
    private SqlConnection _databaseConnection;

    [NonSerialized]
    private TcpClient _networkClient;

    // Korrigiert: Verbindungsparameter stattdessen speichern
    public string ConnectionString { get; private set; }
    public string ServerAddress { get; private set; }
    public int ServerPort { get; private set; }

    // Korrigiert: Serialisierbare Config-Klasse verwenden
    public SerializableConfigSettings Config { get; set; }

    public FixedSessionState()
    {
    }

    // Korrigiert: ISerializable-Konstruktor
    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));
    }

    // Korrigiert: Explizite Serialisierung
    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);
    }

    // Korrigiert: Transiente Felder nach Deserialisierung wiederherstellen
    public void OnDeserialization(object sender)
    {
        // Nicht-serialisierbare Ressourcen reinitialisieren
        if (!string.IsNullOrEmpty(ConnectionString))
        {
            _databaseConnection = new SqlConnection(ConnectionString);
        }
    }
}

[Serializable]
public class SerializableConfigSettings
{
    public string Setting1 { get; set; }
    public int Setting2 { get; set; }
}
# Korrigiert: Python-Klasse mit ordnungsgemäßer Pickle-Behandlung
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

        # Parameter statt nicht-pickelbarer Objekte speichern
        self._server_host: str = ""
        self._server_port: int = 0
        self._log_path: str = '/var/log/app.log'

        # Nicht-pickelbare Ressourcen lazy initialisiert
        self._lock: Optional[threading.Lock] = None
        self._socket: Optional[socket.socket] = None
        self._log_file = None

        # Validierungsregeln als Daten speichern, nicht als Lambdas
        self._min_length: int = 0

    def _ensure_lock(self) -> threading.Lock:
        """Lazy-Initialisierung von Lock"""
        if self._lock is None:
            self._lock = threading.Lock()
        return self._lock

    def validate(self, value: str) -> bool:
        """Validierungsmethode statt Lambda"""
        return len(value) > self._min_length

    # Korrigiert: Kontrollieren was gepickelt wird
    def __getstate__(self):
        """Pickelbaren Zustand zurückgeben"""
        state = self.__dict__.copy()
        # Nicht-pickelbare Attribute entfernen
        state['_lock'] = None
        state['_socket'] = None
        state['_log_file'] = None
        return state

    def __setstate__(self, state):
        """Zustand aus Pickle wiederherstellen"""
        self.__dict__.update(state)
        # Nicht-pickelbare Ressourcen werden lazy reinitialisiert

    # Korrigiert: __reduce__ für mehr Kontrolle verwenden
    def __reduce__(self):
        """Definieren wie das Objekt rekonstruiert wird"""
        return (
            self.__class__,
            (self.user_id,),
            self.__getstate__()
        )


# Alternative: Dataclass mit expliziter Serialisierung verwenden
from dataclasses import dataclass, field
import json

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

    # Von Serialisierung ausschließen mit Feld-Metadaten
    _connection: socket.socket = field(default=None, repr=False, compare=False)

    def to_json(self) -> str:
        """Explizite JSON-Serialisierung"""
        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':
        """Explizite JSON-Deserialisierung"""
        data = json.loads(json_str)
        return cls(**data)

CVE-Beispiele

Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Qualitäts-/Zuverlässigkeitsproblem und keine direkte Sicherheitsschwachstelle darstellt.


Verwandte CWEs

  • CWE-1076: Insufficient Adherence to Expected Conventions (Eltern)
  • CWE-1066: Missing Serialization Control Element (verwandt)
  • CWE-502: Deserialization of Untrusted Data (kann beitragen zu)

Referenzen

  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."