Fehlendes Serialisierungs-Kontrollelement
Beschreibung
Fehlendes Serialisierungs-Kontrollelement tritt auf, wenn ein Produkt ein serialisierbares Datenelement enthält, das keine zugehörige Serialisierungsmethode hat. In Sprachen wie Java und C# können Klassen als serialisierbar markiert werden (durch Implementierung von Serializable oder Verwendung des [Serializable]-Attributs), aber der Entwickler versäumt möglicherweise die Implementierung ordnungsgemäßer Serialisierungskontrollen. Ohne explizite writeObject/readObject-Methoden in Java oder ordnungsgemäße Serialisierungs-Callbacks in .NET kann das Standard-Serialisierungsverhalten sensible Daten exponieren, Validierung überspringen oder Sicherheitsschwachstellen während der Deserialisierung erzeugen.
Risiko
Fehlende Serialisierungskontrollen erzeugen direkte Sicherheitsrisiken. Sensible Felder, die von der Serialisierung ausgeschlossen werden sollten (Passwörter, Tokens, interner Zustand), können versehentlich serialisiert und exponiert werden. Ohne benutzerdefinierte readObject/readResolve-Methoden können deserialisierte Objekte Konstruktoren und Validierungslogik umgehen. Standard-Deserialisierung kann für Object-Injection-Angriffe ausgenutzt werden. Serialisierte Daten können mehr Informationen als beabsichtigt enthalten und Informationsoffenlegungsrisiken erzeugen. Versionkompatibilitätsprobleme können auftreten, wenn sich die Klassenstruktur ändert. Ohne ordnungsgemäße Kontrollen können Angreifer bösartige serialisierte Daten erstellen, die Objekte in ungültigem Zustand erzeugen.
Lösung
Implementieren Sie benutzerdefinierte Serialisierungsmethoden (writeObject, readObject, readResolve in Java; ISerializable-Interface in .NET). Markieren Sie sensible Felder als transient (Java) oder [NonSerialized] (.NET), um sie von der Serialisierung auszuschließen. Verwenden Sie Serialisierungs-Proxies für komplexe Objekte. Implementieren Sie Validierung in Deserialisierungsmethoden, um Objektintegrität sicherzustellen. Verwenden Sie serialVersionUID zur Verwaltung der Versionkompatibilität. Erwägen Sie sicherere Alternativen wie JSON mit explizitem Feldmapping anstelle von binärer Serialisierung. Implementieren Sie readObjectNoData zur Behandlung von Vererbungs-Randfällen. Verwenden Sie Whitelisting für erlaubte Klassen während der Deserialisierung.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Standard-Serialisierung kann sensible Felder exponieren, die nicht serialisiert werden sollten. |
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Fehlende readObject-Validierung ermöglicht die Erstellung von Objekten in ungültigem Zustand. |
| Andere | Bereich: Ändere Reduzierte Zuverlässigkeit - Serialisierung ohne ordnungsgemäße Kontrollen kann Ausnahmen und Laufzeitfehler verursachen. |
Beispielcode
Anfälliger Code
// Anfällig: Serialisierbare Klasse ohne Serialisierungskontrolle
public class VulnerableUserCredentials implements Serializable {
// Anfällig: Keine serialVersionUID definiert
// private static final long serialVersionUID = 1L;
private String username;
private String password; // Sensibel - wird serialisiert!
private String sessionToken; // Sensibel - wird serialisiert!
private transient String tempData; // Zumindest diese ist transient
private boolean isAdmin;
public VulnerableUserCredentials(String username, String password) {
this.username = username;
setPassword(password); // Validierung hier...
}
public void setPassword(String password) {
// Validierungslogik
if (password.length() < 8) {
throw new IllegalArgumentException("Passwort zu kurz");
}
this.password = hashPassword(password);
}
// Anfällig: Kein writeObject - sensible Daten serialisiert
// Anfällig: Kein readObject - Validierung bei Deserialisierung umgangen!
// Bei Deserialisierung:
// 1. Passwort wird in serialisierter Form exponiert
// 2. Passwort-Validierung wird umgangen
// 3. sessionToken wird geleakt
// 4. isAdmin könnte manipuliert werden
}
// Serialisierungsangriff:
// 1. Gültiges Objekt serialisieren
// 2. Serialisierte Bytes ändern um isAdmin = true zu setzen
// 3. Deserialisieren - Konstruktor-Validierung umgangen!
// Anfällig: Singleton ohne Serialisierungsschutz
public class VulnerableSingleton implements Serializable {
private static final VulnerableSingleton INSTANCE = new VulnerableSingleton();
private String sensitiveConfig;
private VulnerableSingleton() {
// Privater Konstruktor
loadConfig();
}
public static VulnerableSingleton getInstance() {
return INSTANCE;
}
// Anfällig: Kein readResolve - Deserialisierung erzeugt neue Instanz!
// Singleton-Pattern durch Serialisierung gebrochen
// Angriff:
// 1. Singleton serialisieren
// 2. Mehrfach deserialisieren
// 3. Mehrere "Singleton"-Instanzen mit potenziell unterschiedlichem Zustand erhalten
}
// Anfällig: .NET-Klasse ohne ordnungsgemäße Serialisierungskontrolle
[Serializable]
public class VulnerableSession
{
public string SessionId { get; set; }
public string UserId { get; set; }
public string AuthToken { get; set; } // Sensibel!
public DateTime CreatedAt { get; set; }
public bool IsAuthenticated { get; set; }
public List<string> Permissions { get; set; }
// Anfällig: Kein [NonSerialized] auf sensiblen Feldern
// Anfällig: Keine ISerializable-Implementierung
// Anfällig: Validierung im Konstruktor wird durch Deserialisierung umgangen
public VulnerableSession(string userId)
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentException("UserId erforderlich");
UserId = userId;
SessionId = GenerateSecureId();
CreatedAt = DateTime.UtcNow;
Permissions = new List<string>();
}
// Deserialisierung umgeht Konstruktor vollständig!
}
# Anfällig: Python pickle ohne Kontrollen
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):
# Passwort-Hashing
return hash(password)
def _generate_secret(self):
# Geheimen Schlüssel generieren
import secrets
return secrets.token_hex(32)
# Anfällig: Kein __reduce__ oder __getstate__/__setstate__
# Alle Attribute einschließlich _secret_key werden gepickelt!
# Angriff: Gepickelte Daten ändern um is_admin = True zu setzen
# Noch schlimmer - pickle kann beliebigen Code ausführen:
class Malicious:
def __reduce__(self):
import os
return (os.system, ('rm -rf /',))
# Deserialisieren nicht vertrauenswürdiger Pickle-Daten = Remote Code Execution!
Korrigierter Code
// Korrigiert: Ordnungsgemäße Serialisierungskontrollen
public class FixedUserCredentials implements Serializable {
// Korrigiert: Explizite serialVersionUID
private static final long serialVersionUID = 1L;
private String username;
// Korrigiert: Transiente sensible Felder
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("Passwort muss mindestens 8 Zeichen haben");
}
}
// Korrigiert: Benutzerdefiniertes writeObject - kontrollieren was serialisiert wird
private void writeObject(ObjectOutputStream out) throws IOException {
// Nur nicht-sensible Felder serialisieren
out.defaultWriteObject();
// Passwort oder sessionToken nicht schreiben
}
// Korrigiert: Benutzerdefiniertes readObject - bei Deserialisierung validieren
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Korrigiert: Deserialisierten Zustand validieren
if (username == null || username.isEmpty()) {
throw new InvalidObjectException("Benutzername darf nicht null sein");
}
// Korrigiert: Transiente Felder sicher initialisieren
this.sessionToken = null; // Muss sich erneut authentifizieren
// Korrigiert: Sicherheitskritische Felder validieren
// isAdmin sollte nur durch ordnungsgemäße Autorisierung true sein
}
// Korrigiert: Unterklassen-Angriffe verhindern
private void readObjectNoData() throws InvalidObjectException {
throw new InvalidObjectException("Stream-Daten erforderlich");
}
}
// Korrigiert: Singleton mit Serialisierungsschutz
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;
}
// Korrigiert: readResolve gibt Singleton-Instanz zurück
private Object readResolve() throws ObjectStreamException {
// Singleton-Instanz zurückgeben, deserialisierte Kopie verwerfen
return INSTANCE;
}
// Korrigiert: Serialisierung sensiblen Zustands verhindern
private void writeObject(ObjectOutputStream out) throws IOException {
// Sensible Config nicht serialisieren
out.defaultWriteObject();
}
private void loadConfig() {
// Konfiguration laden
}
}
// Alternative: Enum-Singleton verwenden (inhärent serialisierungssicher)
public enum FixedEnumSingleton {
INSTANCE;
private transient String sensitiveConfig;
public void doSomething() {
// Singleton-Verhalten
}
}
// Korrigiert: .NET-Klasse mit 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();
// Korrigiert: NonSerialized-Attribut auf sensiblen Feldern
[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;
}
// Korrigiert: Serialisierungskonstruktor
protected FixedSession(SerializationInfo info, StreamingContext context)
{
// Korrigiert: Explizite Deserialisierung mit Validierung
SessionId = info.GetString("SessionId");
UserId = info.GetString("UserId");
CreatedAt = info.GetDateTime("CreatedAt");
// Korrigiert: Deserialisierte Daten validieren
ValidateUserId(UserId);
// Korrigiert: Sicherheitskritische Felder erfordern erneute Authentifizierung
IsAuthenticated = false; // Muss nach Deserialisierung erneut authentifizieren
_authToken = null;
_permissions = new List<string>();
}
// Korrigiert: Explizite Serialisierung
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Nur nicht-sensible Daten serialisieren
info.AddValue("SessionId", SessionId);
info.AddValue("UserId", UserId);
info.AddValue("CreatedAt", CreatedAt);
// Nicht serialisieren: _authToken, IsAuthenticated, _permissions
}
private void ValidateUserId(string userId)
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentException("UserId ist erforderlich");
}
}
# Korrigiert: Python mit kontrollierter Serialisierung
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)
# Geheimer Schlüssel sollte nie serialisiert werden
_secret_key: str = field(default=None, repr=False, compare=False)
def __post_init__(self):
# Geheimen Schlüssel bei Erstellung generieren
if self._secret_key is None:
import secrets
self._secret_key = secrets.token_hex(32)
# Korrigiert: Pickle-Verhalten kontrollieren
def __getstate__(self):
"""Zustand für Pickling zurückgeben - sensible Daten ausschließen"""
state = self.__dict__.copy()
# Geheimen Schlüssel nicht picklen
del state['_secret_key']
return state
def __setstate__(self, state):
"""Zustand aus Pickle wiederherstellen - sensible Daten regenerieren"""
self.__dict__.update(state)
# Geheimen Schlüssel regenerieren
import secrets
self._secret_key = secrets.token_hex(32)
# Korrigiert: JSON für sicherere Serialisierung verwenden
def to_json(self) -> str:
"""Nach JSON serialisieren - Felder explizit kontrollieren"""
return json.dumps({
'username': self.username,
'is_admin': self.is_admin,
'permissions': self.permissions
# password_hash und secret_key ausschließen
})
@classmethod
def from_json(cls, json_str: str, password_hash: str) -> 'FixedUser':
"""Aus JSON deserialisieren mit Validierung"""
data = json.loads(json_str)
# Korrigiert: Vor Objekterstellung validieren
if not data.get('username'):
raise ValueError("Benutzername ist erforderlich")
# Korrigiert: is_admin erfordert explizite Autorisierung
# is_admin aus serialisierten Daten nicht vertrauen
return cls(
username=data['username'],
_password_hash=password_hash,
is_admin=False, # Immer false - erneute Autorisierung erforderlich
permissions=[] # Immer leer - erneute Autorisierung erforderlich
)
# Bessere Alternative: Pickle nicht für nicht vertrauenswürdige Daten verwenden
# JSON mit expliziter Schema-Validierung verwenden
CVE-Beispiele
- CVE-2015-7501: Apache Commons Collections Deserialisierungsschwachstelle ermöglichte Remote-Code-Ausführung aufgrund fehlender Serialisierungskontrollen.
- CVE-2016-1000031: Apache Commons FileUpload Deserialisierungsschwachstelle.
Verwandte CWEs
- CWE-710: Improper Adherence to Coding Standards (Eltern)
- CWE-502: Deserialization of Untrusted Data (verwandt)
- CWE-1006: Bad Coding Practices (Kategoriemitglied)
Referenzen
-
MITRE Corporation. "CWE-1066: Missing Serialization Control Element." https://cwe.mitre.org/data/definitions/1066.html
-
Bloch, Joshua. "Effective Java, Third Edition." Punkte 85-90 zur Serialisierung.
-
OWASP. "Deserialization Cheat Sheet."