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

ImpactDetails
SecurityScope: Authentication Bypass

Password/token comparison may incorrectly succeed or fail.
LogicScope: Inconsistent Behavior

May work in tests but fail in production.
ReliabilityScope: 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

  1. MITRE. "CWE-597: Use of Wrong Operator in String Comparison." https://cwe.mitre.org/data/definitions/597.html

  2. Java Language Specification - String Interning.