Persistent speicherbares Datenelement ohne zugehöriges Vergleichs-Kontrollelement
Beschreibung
Persistent speicherbares Datenelement ohne zugehöriges Vergleichs-Kontrollelement tritt auf, wenn ein Produkt ein speicherbares Datenelement verwendet, dem die notwendigen Funktionen oder Methoden zur Unterstützung von Vergleichsoperationen fehlen. In Java beispielsweise erfordert eine Klasse, die persistent gemacht wird, typischerweise sowohl korrekt definierte hashCode()- als auch equals()-Methoden. Ohne diese Vergleichs-Kontrollelemente kann das Produkt speicherbare Objekte nicht zuverlässig vergleichen, was zu fehlerhaftem Verhalten in Collections, Caches und Datenbankoperationen führt.
Risiko
Fehlende Vergleichsmethoden in persistenten Objekten haben Sicherheitsimplikationen. Fehlerhafte Vergleichsergebnisse können zu Autorisierungsumgehungen führen, wenn Benutzerobjekte falsch verglichen werden. Objekte werden möglicherweise in Collections nicht gefunden, obwohl sie vorhanden sind, was zu Zugriffskontrolfehler führt. Doppelte Objekte können gespeichert werden, obwohl sie eindeutig sein sollten, was Dateninkonsistenz verursacht. Cache-Lookups können unerwartet fehlschlagen und Leistungsverschlechterung oder Sicherheitsumgehungen verursachen. Datenbankoperationen, die auf Objektidentität basieren, können sich unerwartet verhalten. Die Zuverlässigkeitsprobleme können ausgenutzt werden, wenn Angreifer betroffene Codepfade erreichen können.
Lösung
Implementieren Sie immer ordnungsgemäße Vergleichsmethoden für persistente oder speicherbare Objekte. Überschreiben Sie in Java sowohl hashCode()- als auch equals()-Methoden gemeinsam und stellen Sie sicher, dass sie den Vertrag einhalten (gleiche Objekte müssen gleiche Hash-Codes haben). Verwenden Sie IDE-Generierung oder Bibliotheken wie Lombok, um korrekte Implementierungen zu erzeugen. Implementieren Sie in anderen Sprachen geeignete Gleichheits- und Hashing-Mechanismen. Verwenden Sie statische Analysetools, um fehlende Vergleichsmethoden zu erkennen. Testen Sie Vergleichsverhalten mit Unit-Tests, die Grenzfälle abdecken. Erwägen Sie die Verwendung unveränderlicher Objekte, wo möglich, um die Vergleichslogik zu vereinfachen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Andere | Bereich: Ändere Reduzierte Zuverlässigkeit - Fehlerhafte Vergleichsergebnisse führen zu unvorhersehbarem Verhalten. |
| Integrität | Bereich: Integrität Unerwarteter Zustand - Objekte können dupliziert werden oder nicht gefunden werden, wenn sie sollten. |
| Zugriffskontrolle | Bereich: Zugriffskontrolle Schutzmechanismus umgehen - Identitätsprüfungen können unerwartet fehlschlagen. |
Beispielcode
Anfälliger Code
// Anfällig: Persistente Klasse ohne hashCode/equals
@Entity
public class VulnerableUser {
@Id
private Long id;
private String username;
private String email;
private String role;
// Getter und Setter...
// Keine equals()-Methode!
// Keine hashCode()-Methode!
// Problem: Standard Object.equals() verwendet Referenzgleichheit
// Zwei User-Objekte mit gleicher ID werden nicht als gleich betrachtet!
}
public class VulnerableUserService {
private Set<VulnerableUser> activeUsers = new HashSet<>();
public void loginUser(VulnerableUser user) {
activeUsers.add(user);
}
public boolean isUserLoggedIn(VulnerableUser user) {
// Anfällig: Wird fast immer false zurückgeben!
// Selbst wenn Benutzer mit gleicher ID im Set ist, andere Objektreferenz
return activeUsers.contains(user);
}
public void cacheUser(VulnerableUser user) {
Map<VulnerableUser, Session> sessions = new HashMap<>();
sessions.put(user, createSession());
// Später, anderes Objekt mit gleicher Benutzer-ID
VulnerableUser sameUser = userRepository.findById(user.getId());
// Anfällig: Gibt null zurück wegen anderer Objektreferenz!
Session session = sessions.get(sameUser);
}
}
// Anfällig: Nur hashCode, kein equals (Vertragsverletzung)
@Entity
public class VulnerableProduct {
@Id
private Long id;
private String name;
private BigDecimal price;
@Override
public int hashCode() {
return Objects.hash(id);
}
// Fehlendes equals()!
// Vertragsverletzung: Objekte mit gleichem hashCode sind möglicherweise nicht gleich
// Dies verursacht unvorhersehbares Verhalten in hash-basierten Collections
}
# Anfällig: Python-Klasse ohne __eq__ und __hash__
class VulnerableOrder:
def __init__(self, order_id, customer_id, total):
self.order_id = order_id
self.customer_id = customer_id
self.total = total
# Kein __eq__ oder __hash__ definiert
# Standard verwendet Objektidentität (Speicheradresse)
class VulnerableOrderService:
def __init__(self):
self.processed_orders = set()
def process_order(self, order):
if order in self.processed_orders:
# Anfällig: Diese Prüfung funktioniert nie wie erwartet!
# Selbst gleiche order_id wird nicht erkannt
raise DuplicateOrderError()
# Bestellung verarbeiten...
self.processed_orders.add(order)
def is_duplicate(self, order):
# Anfällig: Immer False für verschiedene Objektinstanzen
# selbst mit gleicher order_id
return order in self.processed_orders
# Anfällig: Nur __eq__, kein __hash__ (nicht hashbar)
class VulnerableCustomer:
def __init__(self, customer_id, name):
self.customer_id = customer_id
self.name = name
def __eq__(self, other):
if not isinstance(other, VulnerableCustomer):
return False
return self.customer_id == other.customer_id
# Kein __hash__!
# Dies macht die Klasse nicht hashbar
def vulnerable_cache_customers():
customers = {}
customer = VulnerableCustomer(1, "Alice")
# Löst TypeError aus: unhashable type: 'VulnerableCustomer'
customers[customer] = "VIP"
// Anfällig: C#-Klasse ohne Equals und GetHashCode
public class VulnerableSession
{
public Guid SessionId { get; set; }
public string UserId { get; set; }
public DateTime CreatedAt { get; set; }
// Kein Equals()-Override
// Kein GetHashCode()-Override
// Verwendet standardmäßig Referenzgleichheit
}
public class VulnerableSessionManager
{
private HashSet<VulnerableSession> _activeSessions = new HashSet<VulnerableSession>();
public void AddSession(VulnerableSession session)
{
_activeSessions.Add(session);
}
public bool IsSessionActive(VulnerableSession session)
{
// Anfällig: Immer false, es sei denn exakt gleiche Objektreferenz
return _activeSessions.Contains(session);
}
public void RemoveSession(VulnerableSession session)
{
// Anfällig: Entfernt nicht, wenn anderes Objekt mit gleicher SessionId
_activeSessions.Remove(session);
}
}
// Anfällig: Inkonsistente Equals/GetHashCode
public class VulnerableDocument
{
public int Id { get; set; }
public string Title { get; set; }
public override bool Equals(object obj)
{
if (obj is VulnerableDocument other)
{
return Id == other.Id;
}
return false;
}
// GetHashCode nicht überschrieben - verwendet Basis-Implementierung!
// Vertragsverletzung: Gleiche Objekte können verschiedene Hash-Codes haben
}
Korrigierter Code
// Korrigiert: Ordnungsgemäße equals- und hashCode-Implementierung
@Entity
public class FixedUser {
@Id
private Long id;
private String username;
private String email;
private String role;
// Getter und Setter...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FixedUser fixedUser = (FixedUser) o;
return Objects.equals(id, fixedUser.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
// Korrigiert: Verwendung eines natürlichen Schlüssels anstelle eines Ersatzschlüssels
@Entity
public class FixedProduct {
@Id
private Long id;
@Column(unique = true)
private String sku; // Natürlicher Geschäftsschlüssel
private String name;
private BigDecimal price;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FixedProduct that = (FixedProduct) o;
// Geschäftsschlüssel für Gleichheit verwenden
return Objects.equals(sku, that.sku);
}
@Override
public int hashCode() {
return Objects.hash(sku);
}
}
// Korrigiert: Verwendung von Lombok für automatische Generierung
@Entity
@EqualsAndHashCode(of = "id") // Lombok generiert beide Methoden
public class FixedOrder {
@Id
@GeneratedValue
private Long id;
private String orderNumber;
private BigDecimal total;
}
// Korrigiert: Ordnungsgemäßer Service mit funktionierenden Collections
public class FixedUserService {
private Set<FixedUser> activeUsers = new HashSet<>();
private Map<FixedUser, Session> sessions = new HashMap<>();
public void loginUser(FixedUser user) {
activeUsers.add(user);
sessions.put(user, createSession());
}
public boolean isUserLoggedIn(FixedUser user) {
// Korrigiert: Funktioniert korrekt, da equals/hashCode nach ID vergleichen
return activeUsers.contains(user);
}
public Session getSession(FixedUser user) {
// Korrigiert: Gibt korrekte Session auch mit anderem Objekt zurück
return sessions.get(user);
}
}
# Korrigiert: Python mit ordnungsgemäßem __eq__ und __hash__
from dataclasses import dataclass, field
from typing import Optional
# Ansatz 1: Manuelle Implementierung
class FixedOrder:
def __init__(self, order_id: str, customer_id: str, total: float):
self.order_id = order_id
self.customer_id = customer_id
self.total = total
def __eq__(self, other):
if not isinstance(other, FixedOrder):
return NotImplemented
return self.order_id == other.order_id
def __hash__(self):
return hash(self.order_id)
def __repr__(self):
return f"FixedOrder(order_id={self.order_id!r})"
# Ansatz 2: Verwendung von dataclass (empfohlen)
@dataclass(frozen=True) # frozen macht es hashbar
class FixedCustomer:
customer_id: str
name: str
email: str
def __eq__(self, other):
if not isinstance(other, FixedCustomer):
return NotImplemented
return self.customer_id == other.customer_id
def __hash__(self):
return hash(self.customer_id)
# Ansatz 3: Dataclass mit expliziten eq/hash-Feldern
@dataclass
class FixedProduct:
product_id: str # Verwendet für Gleichheit
name: str = field(compare=False) # Nicht im Vergleich verwendet
price: float = field(compare=False)
stock: int = field(compare=False, hash=False)
def __hash__(self):
return hash(self.product_id)
# Korrigierter Service
class FixedOrderService:
def __init__(self):
self.processed_orders: set[FixedOrder] = set()
def process_order(self, order: FixedOrder):
if order in self.processed_orders:
# Korrigiert: Funktioniert korrekt basierend auf order_id
raise DuplicateOrderError(f"Bestellung {order.order_id} bereits verarbeitet")
# Bestellung verarbeiten...
self.processed_orders.add(order)
def is_duplicate(self, order: FixedOrder) -> bool:
# Korrigiert: Identifiziert Duplikate korrekt nach order_id
return order in self.processed_orders
def test_fixed_order():
service = FixedOrderService()
order1 = FixedOrder("ORD-001", "CUST-1", 100.0)
order2 = FixedOrder("ORD-001", "CUST-1", 100.0) # Gleiche order_id
service.process_order(order1)
assert service.is_duplicate(order2) # True - anderes Objekt, gleiche order_id
// Korrigiert: C# mit ordnungsgemäßem Equals und GetHashCode
public class FixedSession : IEquatable<FixedSession>
{
public Guid SessionId { get; }
public string UserId { get; }
public DateTime CreatedAt { get; }
public FixedSession(Guid sessionId, string userId)
{
SessionId = sessionId;
UserId = userId;
CreatedAt = DateTime.UtcNow;
}
public override bool Equals(object obj)
{
return Equals(obj as FixedSession);
}
public bool Equals(FixedSession other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return SessionId.Equals(other.SessionId);
}
public override int GetHashCode()
{
return SessionId.GetHashCode();
}
public static bool operator ==(FixedSession left, FixedSession right)
{
return Equals(left, right);
}
public static bool operator !=(FixedSession left, FixedSession right)
{
return !Equals(left, right);
}
}
// Korrigiert: Verwendung von record-Typ (C# 9+)
public record FixedDocument(int Id, string Title, string Content)
{
// Records generieren automatisch Equals, GetHashCode, ToString
// Standardmäßig basiert Gleichheit auf allen Eigenschaften
// Überschreiben, wenn nur ID-basierte Gleichheit gewünscht:
public virtual bool Equals(FixedDocument other)
{
if (other is null) return false;
return Id == other.Id;
}
public override int GetHashCode() => Id.GetHashCode();
}
// Korrigiert: Session-Manager funktioniert korrekt
public class FixedSessionManager
{
private readonly HashSet<FixedSession> _activeSessions = new();
private readonly object _lock = new();
public void AddSession(FixedSession session)
{
lock (_lock)
{
_activeSessions.Add(session);
}
}
public bool IsSessionActive(FixedSession session)
{
lock (_lock)
{
// Korrigiert: Funktioniert korrekt basierend auf SessionId
return _activeSessions.Contains(session);
}
}
public bool IsSessionActive(Guid sessionId)
{
lock (_lock)
{
// Alternative: Suche nach ID
return _activeSessions.Any(s => s.SessionId == sessionId);
}
}
public void RemoveSession(FixedSession session)
{
lock (_lock)
{
// Korrigiert: Entfernt korrekt basierend auf SessionId
_activeSessions.Remove(session);
}
}
}
CVE-Beispiele
Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Codequalitätsproblem und keine direkte Sicherheitsschwachstelle darstellt.
Verwandte CWEs
- CWE-1076: Insufficient Adherence to Expected Conventions (Eltern)
- CWE-595: Comparison of Object References Instead of Object Contents (verwandt)
- CWE-1006: Bad Coding Practices (Kategoriemitglied)
Referenzen
- MITRE Corporation. "CWE-1097: Persistent Storable Data Element without Associated Comparison Control Element." https://cwe.mitre.org/data/definitions/1097.html
- Bloch, Joshua. "Effective Java" - Item 11: Always override hashCode when you override equals.
- CISQ Quality Measures - Reliability.