Persistent Storable Data Element without Associated Comparison Control Element
Description
Persistent Storable Data Element without Associated Comparison Control Element occurs when a product uses a storable data element that lacks the necessary functions or methods to support comparison operations. In Java, for example, a class that is made persistent typically requires both hashCode() and equals() methods to be properly defined. Without these comparison control elements, the product cannot reliably compare storable objects, leading to incorrect behavior in collections, caches, and database operations.
Risk
Missing comparison methods in persistent objects has security implications. Incorrect comparison results can lead to authorization bypass if user objects compare incorrectly. Objects may not be found in collections even when present, causing access control failures. Duplicate objects may be stored when they should be unique, causing data inconsistency. Cache lookups may fail unexpectedly, causing performance degradation or security bypasses. Database operations relying on object identity may behave unexpectedly. The reliability issues can be exploited if attackers can reach affected code paths.
Solution
Always implement proper comparison methods for persistent or storable objects. In Java, override both hashCode() and equals() methods together, ensuring they follow the contract (equal objects must have equal hash codes). Use IDE generation or libraries like Lombok to generate correct implementations. In other languages, implement appropriate equality and hashing mechanisms. Use static analysis tools to detect missing comparison methods. Test comparison behavior with unit tests covering edge cases. Consider using immutable objects where possible to simplify comparison logic.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Reliability - Incorrect comparison results lead to unpredictable behavior. |
| Integrity | Scope: Integrity Unexpected State - Objects may be duplicated or not found when they should be. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Identity checks may fail unexpectedly. |
Example Code
Vulnerable Code
// Vulnerable: Persistent class without hashCode/equals
@Entity
public class VulnerableUser {
@Id
private Long id;
private String username;
private String email;
private String role;
// Getters and setters...
// No equals() method!
// No hashCode() method!
// Problem: Default Object.equals() uses reference equality
// Two User objects with same ID are not considered equal!
}
public class VulnerableUserService {
private Set<VulnerableUser> activeUsers = new HashSet<>();
public void loginUser(VulnerableUser user) {
activeUsers.add(user);
}
public boolean isUserLoggedIn(VulnerableUser user) {
// Vulnerable: Will almost always return false!
// Even if user with same ID is in set, different object reference
return activeUsers.contains(user);
}
public void cacheUser(VulnerableUser user) {
Map<VulnerableUser, Session> sessions = new HashMap<>();
sessions.put(user, createSession());
// Later, different object with same user ID
VulnerableUser sameUser = userRepository.findById(user.getId());
// Vulnerable: Returns null because different object reference!
Session session = sessions.get(sameUser);
}
}
// Vulnerable: Only hashCode, no equals (contract violation)
@Entity
public class VulnerableProduct {
@Id
private Long id;
private String name;
private BigDecimal price;
@Override
public int hashCode() {
return Objects.hash(id);
}
// Missing equals()!
// Contract violation: Objects with same hashCode might not be equal
// This causes unpredictable behavior in hash-based collections
}
# Vulnerable: Python class without __eq__ and __hash__
class VulnerableOrder:
def __init__(self, order_id, customer_id, total):
self.order_id = order_id
self.customer_id = customer_id
self.total = total
# No __eq__ or __hash__ defined
# Default uses object identity (memory address)
class VulnerableOrderService:
def __init__(self):
self.processed_orders = set()
def process_order(self, order):
if order in self.processed_orders:
# Vulnerable: This check never works as expected!
# Even same order_id won't be detected
raise DuplicateOrderError()
# Process order...
self.processed_orders.add(order)
def is_duplicate(self, order):
# Vulnerable: Always False for different object instances
# even with same order_id
return order in self.processed_orders
# Vulnerable: Only __eq__, no __hash__ (unhashable)
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
# No __hash__!
# This makes the class unhashable
def vulnerable_cache_customers():
customers = {}
customer = VulnerableCustomer(1, "Alice")
# Raises TypeError: unhashable type: 'VulnerableCustomer'
customers[customer] = "VIP"
// Vulnerable: C# class without Equals and GetHashCode
public class VulnerableSession
{
public Guid SessionId { get; set; }
public string UserId { get; set; }
public DateTime CreatedAt { get; set; }
// No Equals() override
// No GetHashCode() override
// Uses reference equality by default
}
public class VulnerableSessionManager
{
private HashSet<VulnerableSession> _activeSessions = new HashSet<VulnerableSession>();
public void AddSession(VulnerableSession session)
{
_activeSessions.Add(session);
}
public bool IsSessionActive(VulnerableSession session)
{
// Vulnerable: Always false unless exact same object reference
return _activeSessions.Contains(session);
}
public void RemoveSession(VulnerableSession session)
{
// Vulnerable: Won't remove if different object with same SessionId
_activeSessions.Remove(session);
}
}
// Vulnerable: Inconsistent 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 not overridden - uses base implementation!
// Contract violation: Equal objects may have different hash codes
}
Fixed Code
// Fixed: Proper equals and hashCode implementation
@Entity
public class FixedUser {
@Id
private Long id;
private String username;
private String email;
private String role;
// Getters and setters...
@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);
}
}
// Fixed: Using natural key instead of surrogate key
@Entity
public class FixedProduct {
@Id
private Long id;
@Column(unique = true)
private String sku; // Natural business key
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;
// Use business key for equality
return Objects.equals(sku, that.sku);
}
@Override
public int hashCode() {
return Objects.hash(sku);
}
}
// Fixed: Using Lombok for automatic generation
@Entity
@EqualsAndHashCode(of = "id") // Lombok generates both methods
public class FixedOrder {
@Id
@GeneratedValue
private Long id;
private String orderNumber;
private BigDecimal total;
}
// Fixed: Proper service with working 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) {
// Fixed: Works correctly because equals/hashCode compare by ID
return activeUsers.contains(user);
}
public Session getSession(FixedUser user) {
// Fixed: Returns correct session even with different object
return sessions.get(user);
}
}
# Fixed: Python with proper __eq__ and __hash__
from dataclasses import dataclass, field
from typing import Optional
# Approach 1: Manual implementation
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})"
# Approach 2: Using dataclass (recommended)
@dataclass(frozen=True) # frozen makes it hashable
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)
# Approach 3: Dataclass with explicit eq/hash fields
@dataclass
class FixedProduct:
product_id: str # Used for equality
name: str = field(compare=False) # Not used in comparison
price: float = field(compare=False)
stock: int = field(compare=False, hash=False)
def __hash__(self):
return hash(self.product_id)
# Fixed service
class FixedOrderService:
def __init__(self):
self.processed_orders: set[FixedOrder] = set()
def process_order(self, order: FixedOrder):
if order in self.processed_orders:
# Fixed: Works correctly based on order_id
raise DuplicateOrderError(f"Order {order.order_id} already processed")
# Process order...
self.processed_orders.add(order)
def is_duplicate(self, order: FixedOrder) -> bool:
# Fixed: Correctly identifies duplicates by 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) # Same order_id
service.process_order(order1)
assert service.is_duplicate(order2) # True - different object, same order_id
// Fixed: C# with proper Equals and 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);
}
}
// Fixed: Using record type (C# 9+)
public record FixedDocument(int Id, string Title, string Content)
{
// Records automatically generate Equals, GetHashCode, ToString
// By default, equality is based on all properties
// Override if you only want ID-based equality:
public virtual bool Equals(FixedDocument other)
{
if (other is null) return false;
return Id == other.Id;
}
public override int GetHashCode() => Id.GetHashCode();
}
// Fixed: Session manager working correctly
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)
{
// Fixed: Works correctly based on SessionId
return _activeSessions.Contains(session);
}
}
public bool IsSessionActive(Guid sessionId)
{
lock (_lock)
{
// Alternative: lookup by ID
return _activeSessions.Any(s => s.SessionId == sessionId);
}
}
public void RemoveSession(FixedSession session)
{
lock (_lock)
{
// Fixed: Removes correctly based on SessionId
_activeSessions.Remove(session);
}
}
}
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality concern rather than a direct security vulnerability.
Related CWEs
- CWE-1076: Insufficient Adherence to Expected Conventions (parent)
- CWE-595: Comparison of Object References Instead of Object Contents (related)
- CWE-1006: Bad Coding Practices (category member)
References
- 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.