Use of Wrong Operator in String Comparison
Description
Use of Wrong Operator in String Comparison occurs when code uses identity operators (==, ===, is) instead of equality methods to compare strings. While this overlaps with general object comparison issues, string comparison deserves special attention because strings are fundamental to security (passwords, tokens, usernames) and because string interning can make bugs appear to work intermittently.
Risk
String comparison bugs in authentication allow password bypass. Token validation failures enable session hijacking. Username comparisons may fail, causing authorization issues. String interning makes bugs inconsistent—comparison may work for short strings or literals but fail for dynamically constructed strings. This inconsistency makes testing difficult and production failures unpredictable.
Solution
Always use appropriate string comparison methods: equals() in Java, strcmp() in C, === with strings (not objects) in JavaScript, == in Python (which does value comparison for strings). Be aware of string interning behavior. For security-sensitive comparisons, use constant-time comparison to prevent timing attacks.
Common Consequences
| Impact | Details |
|---|---|
| Security | Scope: Authentication Bypass Password/token comparison may incorrectly succeed or fail. |
| Logic | Scope: Inconsistent Behavior May work in tests but fail in production. |
| Reliability | Scope: Unpredictable Failures String interning creates intermittent bugs. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: String comparison with ==
public class VulnerableStringAuth {
private static final String ADMIN_PASSWORD = "admin123";
public boolean checkPassword(String input) {
// BUG: Reference comparison!
return input == ADMIN_PASSWORD;
}
public boolean validateApiKey(String providedKey, String expectedKey) {
// BUG: May fail even with correct key!
return providedKey == expectedKey;
}
public void demonstrateInterning() {
String s1 = "hello"; // Interned
String s2 = "hello"; // Same interned string
String s3 = new String("hello"); // New object
String s4 = "hel" + "lo"; // Compile-time concat - interned
String s5 = "hel";
s5 += "lo"; // Runtime concat - NOT interned
System.out.println(s1 == s2); // true (both interned)
System.out.println(s1 == s3); // false (different objects)
System.out.println(s1 == s4); // true (compile-time interning)
System.out.println(s1 == s5); // false! (runtime construction)
}
}
// VULNERABLE: Username comparison
public class VulnerableUserLookup {
public User findUser(List<User> users, String username) {
for (User user : users) {
// BUG: Reference comparison may fail!
if (user.getUsername() == username) {
return user;
}
}
return null;
}
}
// VULNERABLE: String comparison in switch statement workaround
public class VulnerableRoleCheck {
public int getPermissionLevel(String role) {
// Before Java 7, switch on strings wasn't supported
// Some developers used == as workaround
if (role == "admin") {
return 100;
} else if (role == "user") {
return 10;
}
return 0;
}
}
// VULNERABLE: C string comparison with ==
#include <string.h>
int vulnerable_auth(const char* input, const char* password) {
// BUG: Compares pointers, not string contents!
if (input == password) {
return 1; // Authenticated
}
return 0;
}
void demonstrate_problem() {
char* s1 = "hello";
char* s2 = "hello";
char s3[] = "hello";
// s1 == s2 might be true (string literal pooling)
// s1 == s3 is false (s3 is on stack)
printf("s1 == s2: %d\n", s1 == s2); // Might be 1
printf("s1 == s3: %d\n", s1 == s3); // 0
}
// VULNERABLE: Token validation
int vulnerable_validate_token(const char* provided, const char* expected) {
return provided == expected; // BUG!
}
# VULNERABLE: Python string comparison with 'is'
def vulnerable_auth(input_password, stored_password):
# BUG: 'is' compares identity, not value!
return input_password is stored_password
def demonstrate_interning():
# Short strings may be interned
a = "hello"
b = "hello"
print(a is b) # True (interned)
# Longer or dynamic strings may not be
c = "hello world " * 100
d = "hello world " * 100
print(c is d) # May be False!
# User input is never interned
e = input("Enter 'hello': ")
print(e is "hello") # False even if user types "hello"
# VULNERABLE: Command comparison
def vulnerable_command_check(cmd):
if cmd is "quit": # BUG!
return True
return False
// JavaScript: Using == can have type coercion issues
function vulnerableCompare(input, expected) {
// == does type coercion which can cause issues
return input == expected;
}
// Example of problematic coercion
console.log("0" == false); // true!
console.log("" == false); // true!
console.log(null == undefined); // true!
// VULNERABLE: Object string comparison
function vulnerableObjectStringCompare(str1, str2) {
// If either is a String object (not primitive), this fails
return str1 === str2;
}
var a = new String("hello");
var b = new String("hello");
console.log(a === b); // false!
Fixed Code
// SAFE: Use equals() for string comparison
public class SafeStringAuth {
private static final String ADMIN_PASSWORD = "admin123";
public boolean checkPassword(String input) {
// Correct: Compare string contents
if (input == null) {
return false;
}
return ADMIN_PASSWORD.equals(input);
}
// Null-safe version
public boolean checkPasswordSafe(String input) {
return Objects.equals(input, ADMIN_PASSWORD);
}
// Constant-time comparison for security
public boolean checkPasswordSecure(String input, String stored) {
if (input == null || stored == null) {
return false;
}
return MessageDigest.isEqual(
input.getBytes(StandardCharsets.UTF_8),
stored.getBytes(StandardCharsets.UTF_8)
);
}
// Case-insensitive comparison
public boolean checkUsernameIgnoreCase(String input, String stored) {
if (input == null || stored == null) {
return false;
}
return input.equalsIgnoreCase(stored);
}
}
// SAFE: Username lookup with proper comparison
public class SafeUserLookup {
public User findUser(List<User> users, String username) {
if (username == null) {
return null;
}
for (User user : users) {
if (username.equals(user.getUsername())) {
return user;
}
}
return null;
}
// Using streams
public Optional<User> findUserStream(List<User> users, String username) {
if (username == null) {
return Optional.empty();
}
return users.stream()
.filter(u -> username.equals(u.getUsername()))
.findFirst();
}
}
// SAFE: Modern Java switch statement
public class SafeRoleCheck {
public int getPermissionLevel(String role) {
if (role == null) {
return 0;
}
// Java 7+ supports switch on strings (uses equals internally)
switch (role) {
case "admin":
return 100;
case "user":
return 10;
default:
return 0;
}
}
}
// SAFE: Use strcmp() for C string comparison
#include <string.h>
int safe_auth(const char* input, const char* password) {
if (input == NULL || password == NULL) {
return 0;
}
// Correct: Compare string contents
if (strcmp(input, password) == 0) {
return 1;
}
return 0;
}
// SAFE: Case-insensitive comparison
int safe_auth_nocase(const char* input, const char* password) {
if (input == NULL || password == NULL) {
return 0;
}
return strcasecmp(input, password) == 0;
}
// SAFE: Constant-time comparison for security
int safe_auth_constant_time(const char* input, const char* password) {
if (input == NULL || password == NULL) {
return 0;
}
size_t input_len = strlen(input);
size_t pass_len = strlen(password);
// Constant-time comparison
volatile int result = input_len ^ pass_len;
size_t min_len = input_len < pass_len ? input_len : pass_len;
for (size_t i = 0; i < min_len; i++) {
result |= input[i] ^ password[i];
}
return result == 0;
}
// SAFE: Token validation
int safe_validate_token(const char* provided, const char* expected) {
if (provided == NULL || expected == NULL) {
return 0;
}
return strcmp(provided, expected) == 0;
}
# SAFE: Use == for Python string comparison
def safe_auth(input_password, stored_password):
# Correct: == compares values in Python
if input_password is None or stored_password is None:
return False
return input_password == stored_password
# SAFE: Constant-time comparison for security
import hmac
def safe_auth_secure(input_password, stored_password):
if input_password is None or stored_password is None:
return False
# Constant-time comparison
return hmac.compare_digest(input_password, stored_password)
# SAFE: Command comparison
def safe_command_check(cmd):
if cmd is None:
return False
return cmd == "quit" # == for value comparison
# SAFE: Case-insensitive comparison
def safe_username_check(input_name, stored_name):
if input_name is None or stored_name is None:
return False
return input_name.lower() == stored_name.lower()
// SAFE: JavaScript string comparison
function safeCompare(input, expected) {
// Use === for strict equality (no type coercion)
// Works correctly for primitive strings
return input === expected;
}
// SAFE: Handle String objects
function safeCompareAny(str1, str2) {
// Convert to primitives if needed
if (str1 === null || str2 === null) {
return str1 === str2;
}
return String(str1) === String(str2);
}
// SAFE: Constant-time comparison (Node.js)
const crypto = require('crypto');
function safeCompareSecure(input, expected) {
if (typeof input !== 'string' || typeof expected !== 'string') {
return false;
}
// Constant-time comparison
try {
return crypto.timingSafeEqual(
Buffer.from(input),
Buffer.from(expected)
);
} catch (e) {
return false; // Different lengths
}
}
Exploited in the Wild
Authentication Bypasses
Password comparison bugs using == allowed attackers to bypass authentication systems.
API Key Validation
API key validation failures due to reference comparison allowed unauthorized access.
Session Token Issues
Session tokens compared with identity operators caused session management vulnerabilities.
Tools to test/exploit
-
SpotBugs — ES_COMPARING_STRINGS_WITH_EQ detector.
-
SonarQube — String comparison rules.
-
Pylint — Python 'is' with literal detection.
-
ESLint — eqeqeq rule.
CVE Examples
-
CVEs from string comparison bugs in authentication.
-
Token validation bypasses from identity comparison.
References
-
MITRE. "CWE-597: Use of Wrong Operator in String Comparison." https://cwe.mitre.org/data/definitions/597.html
-
Java Language Specification - String Interning.