Comparison of Object References Instead of Object Contents
Description
Comparison of Object References Instead of Object Contents occurs when code uses reference equality operators (== or !=) to compare objects when it should compare their values using equals() or similar methods. In languages like Java, == compares memory addresses (whether two references point to the same object), not the actual content of objects. This leads to unexpected behavior when logically equal objects are considered different.
Risk
Reference comparison bugs cause authentication bypasses when string passwords are compared incorrectly. Business logic fails when equivalent objects aren't recognized as equal. Security tokens may not validate properly. Caching fails when equal objects aren't detected. Collections behave unexpectedly. The code may work with some inputs (due to string interning or boxing caches) but fail with others.
Solution
Use equals() method for object comparison in Java. Use Objects.equals() for null-safe comparison. Override equals() and hashCode() when creating value classes. In JavaScript, understand the difference between == and === for objects. Be aware of language-specific comparison semantics. Use appropriate comparison methods for strings, numbers, and custom objects.
Common Consequences
| Impact | Details |
|---|---|
| Logic | Scope: Incorrect Behavior Logically equal objects treated as different. |
| Security | Scope: Authentication Bypass Credential comparison may fail or succeed incorrectly. |
| Reliability | Scope: Inconsistent Results Code may work sometimes due to caching/interning. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: String comparison with ==
public class VulnerableAuth {
public boolean authenticate(String inputPassword, String storedPassword) {
// BUG: Compares references, not content!
return inputPassword == storedPassword;
}
public boolean checkRole(String userRole, String requiredRole) {
// May work sometimes due to string interning, fail other times
return userRole == requiredRole;
}
}
// VULNERABLE: Integer comparison with ==
public class VulnerableIdCheck {
public boolean isSameUser(Integer userId1, Integer userId2) {
// BUG: Works for -128 to 127 (cached), fails for larger values!
return userId1 == userId2;
}
public void demonstrateProblem() {
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true (cached)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false! (not cached)
}
}
// VULNERABLE: Custom object comparison
public class User {
private Long id;
private String name;
// No equals() override - uses Object.equals (reference comparison)
}
public class VulnerableUserService {
public boolean isSameUser(User user1, User user2) {
// BUG: Compares references, not content!
return user1 == user2;
}
public boolean containsUser(List<User> users, User target) {
for (User u : users) {
if (u == target) { // BUG: Reference comparison
return true;
}
}
return false;
}
}
// VULNERABLE: Token validation
public class VulnerableTokenValidator {
public boolean validateToken(String providedToken, String expectedToken) {
// BUG: May fail even with correct token!
return providedToken == expectedToken;
}
}
// VULNERABLE: Enum-like class comparison
public class Status {
public static final Status ACTIVE = new Status("ACTIVE");
public static final Status INACTIVE = new Status("INACTIVE");
private String name;
public Status(String name) {
this.name = name;
}
}
public class VulnerableStatusCheck {
public boolean isActive(Status status) {
// BUG: new Status("ACTIVE") == Status.ACTIVE is false!
return status == Status.ACTIVE;
}
}
// VULNERABLE: Boolean comparison
public class VulnerableBooleanCheck {
public boolean checkFlag(Boolean flag) {
// BUG: May fail for Boolean objects!
return flag == Boolean.TRUE;
}
public void demonstrate() {
Boolean b1 = Boolean.TRUE;
Boolean b2 = new Boolean(true); // Different object!
System.out.println(b1 == Boolean.TRUE); // true
System.out.println(b2 == Boolean.TRUE); // false!
}
}
// VULNERABLE: Array comparison
public class VulnerableArrayCheck {
public boolean sameArray(byte[] arr1, byte[] arr2) {
// BUG: Compares references, not array contents!
return arr1 == arr2;
}
}
// VULNERABLE: Date comparison
public class VulnerableDateCheck {
public boolean isSameDate(Date date1, Date date2) {
// BUG: Reference comparison!
return date1 == date2;
}
}
// VULNERABLE: JavaScript object comparison
function vulnerableUserCheck(user1, user2) {
// BUG: Objects with same content are not ===
return user1 === user2;
}
// Demonstrates the issue
const a = { name: "Alice" };
const b = { name: "Alice" };
console.log(a === b); // false!
// VULNERABLE: Array comparison
function vulnerableArrayCheck(arr1, arr2) {
// BUG: Different arrays with same contents are not equal
return arr1 === arr2;
}
const x = [1, 2, 3];
const y = [1, 2, 3];
console.log(x === y); // false!
Fixed Code
// SAFE: Use equals() for string comparison
public class SafeAuth {
public boolean authenticate(String inputPassword, String storedPassword) {
// Correct: Compares string content
if (inputPassword == null || storedPassword == null) {
return false;
}
return inputPassword.equals(storedPassword);
}
// Even safer: null-safe comparison
public boolean authenticateSafe(String inputPassword, String storedPassword) {
return Objects.equals(inputPassword, storedPassword);
}
// For security: constant-time comparison
public boolean authenticateSecure(String inputPassword, String storedPassword) {
if (inputPassword == null || storedPassword == null) {
return false;
}
return MessageDigest.isEqual(
inputPassword.getBytes(StandardCharsets.UTF_8),
storedPassword.getBytes(StandardCharsets.UTF_8)
);
}
}
// SAFE: Use equals() for Integer comparison
public class SafeIdCheck {
public boolean isSameUser(Integer userId1, Integer userId2) {
// Correct: Works for all integer values
return Objects.equals(userId1, userId2);
}
// Alternative with null handling
public boolean isSameUserExplicit(Integer userId1, Integer userId2) {
if (userId1 == null || userId2 == null) {
return userId1 == userId2; // Both null = equal
}
return userId1.equals(userId2);
}
}
// SAFE: Custom object with proper equals() and hashCode()
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User other = (User) obj;
return Objects.equals(id, other.id) &&
Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
public class SafeUserService {
public boolean isSameUser(User user1, User user2) {
// Now compares content!
return Objects.equals(user1, user2);
}
public boolean containsUser(List<User> users, User target) {
// Works correctly with proper equals()
return users.contains(target);
}
}
// SAFE: Token validation with proper comparison
public class SafeTokenValidator {
public boolean validateToken(String providedToken, String expectedToken) {
if (providedToken == null || expectedToken == null) {
return false;
}
// Constant-time comparison for security tokens
return MessageDigest.isEqual(
providedToken.getBytes(StandardCharsets.UTF_8),
expectedToken.getBytes(StandardCharsets.UTF_8)
);
}
}
// SAFE: Use real enum for type-safe comparison
public enum Status {
ACTIVE,
INACTIVE
}
public class SafeStatusCheck {
public boolean isActive(Status status) {
// Enum comparison with == is correct and preferred
return status == Status.ACTIVE;
}
}
// SAFE: Boolean comparison
public class SafeBooleanCheck {
public boolean checkFlag(Boolean flag) {
// Use equals() or compare to primitive
return Boolean.TRUE.equals(flag);
}
// Or unbox safely
public boolean checkFlagUnbox(Boolean flag) {
return flag != null && flag.booleanValue();
}
}
// SAFE: Array comparison
public class SafeArrayCheck {
public boolean sameArray(byte[] arr1, byte[] arr2) {
// Compares array contents
return Arrays.equals(arr1, arr2);
}
// For security-sensitive byte arrays
public boolean sameArraySecure(byte[] arr1, byte[] arr2) {
return MessageDigest.isEqual(arr1, arr2);
}
}
// SAFE: Date comparison
public class SafeDateCheck {
public boolean isSameDate(Date date1, Date date2) {
return Objects.equals(date1, date2);
}
// Or with null handling
public boolean isSameDateExplicit(Date date1, Date date2) {
if (date1 == null || date2 == null) {
return date1 == date2;
}
return date1.equals(date2);
}
}
// SAFE: JavaScript object comparison
function safeUserCheck(user1, user2) {
// Compare by relevant properties
if (user1 === null || user2 === null) {
return user1 === user2;
}
return user1.id === user2.id && user1.name === user2.name;
}
// Generic deep equality check
function deepEquals(obj1, obj2) {
if (obj1 === obj2) return true;
if (obj1 === null || obj2 === null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(key => deepEquals(obj1[key], obj2[key]));
}
// SAFE: Array comparison
function safeArrayCheck(arr1, arr2) {
if (arr1 === arr2) return true;
if (!arr1 || !arr2) return false;
if (arr1.length !== arr2.length) return false;
return arr1.every((val, idx) => val === arr2[idx]);
}
// Or use lodash/underscore
// _.isEqual(arr1, arr2);
// SAFE: JSON comparison for simple objects
function jsonEquals(obj1, obj2) {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
Exploited in the Wild
Authentication Bypasses
String comparison bugs in password validation have allowed attackers to bypass authentication.
Session Fixation
Session ID comparison using == allowed session hijacking when IDs matched by value but not reference.
Authorization Failures
Role checking with reference comparison allowed privilege escalation.
Tools to test/exploit
-
SpotBugs — detects reference comparison issues.
-
SonarQube — identifies comparison bugs.
-
Error Prone — Google's static analysis tool.
-
PMD — source code analyzer.
CVE Examples
-
Authentication bypass CVEs from string comparison errors.
-
Session management vulnerabilities from reference comparison.
References
-
MITRE. "CWE-595: Comparison of Object References Instead of Object Contents." https://cwe.mitre.org/data/definitions/595.html
-
Effective Java, Item 10: Obey the general contract when overriding equals.