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

ImpactDetails
OtherScope: Other

Reduce Reliability - Incorrect comparison results lead to unpredictable behavior.
IntegrityScope: Integrity

Unexpected State - Objects may be duplicated or not found when they should be.
Access ControlScope: 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.


  • 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

  1. MITRE Corporation. "CWE-1097: Persistent Storable Data Element without Associated Comparison Control Element." https://cwe.mitre.org/data/definitions/1097.html
  2. Bloch, Joshua. "Effective Java" - Item 11: Always override hashCode when you override equals.
  3. CISQ Quality Measures - Reliability.