Incorrect Comparison

Description

Incorrect Comparison is a pillar-level weakness where software performs a comparison in a security-relevant context, but the comparison itself is incorrect. This encompasses several scenarios: the comparison evaluates one factor incorrectly, multiple factors should be considered but at least one is omitted entirely, or the comparison examines the wrong factor altogether. When comparisons are flawed in security contexts, they typically lead to bypasses of authentication, authorization, or validation checks.

Risk

Incorrect comparisons create severe security vulnerabilities. Authentication systems with partial string comparisons can be bypassed with truncated credentials. Authorization checks missing required factors may grant inappropriate access. Equality checks that ignore case sensitivity or encoding may accept malicious inputs. Floating-point comparisons with wrong operators produce unreliable results. The risk is amplified because comparison errors often appear syntactically correct and may work in most test cases, only failing in security-critical edge cases that attackers specifically target.

Solution

Ensure comparisons include all relevant factors. Use type-safe comparison methods appropriate for the data being compared. For strings, consider case sensitivity, encoding, and locale requirements. For floating-point numbers, use appropriate epsilon-based comparisons. Verify comparison results are used correctly (checking for equality vs. inequality). Use well-tested library functions for complex comparisons. Review and test comparison logic with boundary cases and adversarial inputs. Implement comprehensive equality methods that consider all significant fields.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Bypass Protection Mechanism - Flawed comparisons can allow attackers to bypass authentication or authorization.
IntegrityScope: Integrity

Unexpected State - Incorrect comparisons lead to incorrect program state and behavior.
OtherScope: Other

Varies by Context - Impacts depend on what the comparison controls.

Example Code

Vulnerable Code

// Vulnerable: Partial string comparison in authentication
#include <string.h>

int vulnerable_authenticate(char *input_user, char *input_pass) {
    char *stored_user = "administrator";
    char *stored_pass = "secretpassword";

    // Vulnerable: Using strlen of attacker-controlled input!
    // Attacker can use "a" as username and "s" as password
    if (strncmp(stored_user, input_user, strlen(input_user)) != 0) {
        return AUTH_FAIL;
    }

    // Vulnerable: Same issue - partial match on password
    if (strncmp(stored_pass, input_pass, strlen(input_pass)) == 0) {
        return AUTH_SUCCESS;  // Matches with "s", "se", "sec", etc.
    }

    return AUTH_FAIL;
}

// Vulnerable: Wrong comparison operator for floating point
int vulnerable_balance_check(float balance, float withdrawal) {
    // Vulnerable: Direct float equality is unreliable
    if (balance - withdrawal == 0.0) {
        // May not trigger when it should due to floating point precision
        return ERROR_INSUFFICIENT_FUNDS;
    }

    return OK;
}
// Vulnerable: Missing field in equals comparison
public class Truck {
    private String make;
    private String model;
    private int year;  // Important distinguishing field

    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if (o == this) return true;
        if (!(o instanceof Truck)) return false;

        Truck t = (Truck) o;

        // Vulnerable: Missing year comparison!
        // Two trucks from different years considered equal
        return this.make.equals(t.getMake()) &&
               this.model.equals(t.getModel());
        // Should also compare: && this.year == t.getYear()
    }

    @Override
    public int hashCode() {
        // Also vulnerable: hashCode doesn't include year
        return Objects.hash(make, model);
    }
}

// Vulnerable: Case-sensitive comparison where insensitive needed
public class VulnerableAuth {

    public boolean checkRole(String userRole, String requiredRole) {
        // Vulnerable: Case-sensitive comparison
        // "Admin" != "admin"
        return userRole.equals(requiredRole);
    }
}

// Vulnerable: Substring comparison instead of exact match
public class VulnerableHeaderCheck {

    public boolean isValidOrigin(String origin) {
        // Vulnerable: Substring match instead of exact or proper parsing
        return origin.contains("trusted.com");
        // Passes for "malicious-trusted.com" or "trusted.com.evil.com"
    }
}
# Vulnerable: Using wrong comparison for HTTP headers (CVE-2020-15811 pattern)
def vulnerable_header_parse(header_line):
    # Vulnerable: Substring search instead of proper parsing
    if 'Content-Length' in header_line:
        # Extracts value after "Content-Length" anywhere in string
        # Can be fooled by "X-Fake-Content-Length: 0\r\nContent-Length: 999"
        parts = header_line.split(':')
        return int(parts[1].strip())
    return None

# Vulnerable: Incorrect boolean operators (CVE-2021-3116 pattern)
def vulnerable_auth_check(user, password):
    valid_user = user == EXPECTED_USER
    valid_pass = password == EXPECTED_PASS

    # Vulnerable: Should be AND, not OR
    if valid_user or valid_pass:  # WRONG - either one is enough!
        return True
    return False

# Vulnerable: Comparing wrong types
def vulnerable_type_check(user_id):
    admin_ids = ['1', '2', '3']  # String list

    # Vulnerable: Comparing int to strings
    if user_id in admin_ids:  # 1 != '1'
        grant_admin_access()
    # Integer 1 won't be found in string list
// Vulnerable: Loose equality in JavaScript
function vulnerablePermissionCheck(userLevel) {
    // Vulnerable: Loose equality with type coercion
    if (userLevel == 0) {  // Type coercion issues
        // "0" == 0 is true
        // false == 0 is true
        // null == 0 is false (inconsistent)
        return 'no access';
    }
    return 'access granted';
}

// Vulnerable: NaN comparison
function vulnerableNumericCheck(value) {
    // Vulnerable: NaN is never equal to anything, including itself
    if (value === NaN) {  // Always false!
        return 'invalid';
    }
    return 'valid';
}

// Vulnerable: Array comparison
function vulnerableArrayCheck(userRoles, requiredRoles) {
    // Vulnerable: Arrays are compared by reference, not value
    if (userRoles === requiredRoles) {  // Almost always false
        return true;
    }
    return false;
}

Fixed Code

// Fixed: Full string comparison with constant-time comparison
#include <string.h>

// Constant-time string comparison to prevent timing attacks
int secure_compare(const char *a, const char *b, size_t len) {
    volatile int result = 0;
    for (size_t i = 0; i < len; i++) {
        result |= a[i] ^ b[i];
    }
    return result == 0;
}

int secure_authenticate(char *input_user, char *input_pass) {
    char *stored_user = "administrator";
    char *stored_pass = "secretpassword";

    // Fixed: Compare full strings with known lengths
    size_t user_len = strlen(stored_user);
    size_t pass_len = strlen(stored_pass);

    // Fixed: Check lengths first, then compare full strings
    if (strlen(input_user) != user_len ||
        !secure_compare(stored_user, input_user, user_len)) {
        return AUTH_FAIL;
    }

    if (strlen(input_pass) != pass_len ||
        !secure_compare(stored_pass, input_pass, pass_len)) {
        return AUTH_FAIL;
    }

    return AUTH_SUCCESS;
}

// Fixed: Epsilon comparison for floating point
#include <math.h>

int secure_balance_check(double balance, double withdrawal) {
    double remaining = balance - withdrawal;
    double epsilon = 0.001;  // Appropriate tolerance

    // Fixed: Use epsilon comparison
    if (fabs(remaining) < epsilon) {
        return ERROR_INSUFFICIENT_FUNDS;
    }

    if (remaining < 0) {
        return ERROR_INSUFFICIENT_FUNDS;
    }

    return OK;
}
// Fixed: Complete equals implementation
public class Truck {
    private String make;
    private String model;
    private int year;

    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if (o == this) return true;
        if (!(o instanceof Truck)) return false;

        Truck t = (Truck) o;

        // Fixed: Include ALL significant fields
        return Objects.equals(this.make, t.getMake()) &&
               Objects.equals(this.model, t.getModel()) &&
               this.year == t.getYear();
    }

    @Override
    public int hashCode() {
        // Fixed: Include all fields in hashCode
        return Objects.hash(make, model, year);
    }
}

// Fixed: Case-insensitive comparison
public class SecureAuth {

    public boolean checkRole(String userRole, String requiredRole) {
        if (userRole == null || requiredRole == null) {
            return false;
        }
        // Fixed: Case-insensitive comparison
        return userRole.equalsIgnoreCase(requiredRole);
    }
}

// Fixed: Proper origin validation
public class SecureHeaderCheck {

    private static final Set<String> ALLOWED_ORIGINS = Set.of(
        "https://trusted.com",
        "https://www.trusted.com"
    );

    public boolean isValidOrigin(String origin) {
        if (origin == null) return false;

        // Fixed: Exact match against allowlist
        return ALLOWED_ORIGINS.contains(origin);
    }
}
# Fixed: Proper header parsing
def secure_header_parse(header_line):
    # Fixed: Parse header properly
    if ':' not in header_line:
        return None

    name, _, value = header_line.partition(':')
    name = name.strip()

    # Fixed: Exact header name match (case-insensitive per HTTP spec)
    if name.lower() == 'content-length':
        try:
            return int(value.strip())
        except ValueError:
            return None
    return None

# Fixed: Correct boolean logic
def secure_auth_check(user, password):
    valid_user = user == EXPECTED_USER
    valid_pass = password == EXPECTED_PASS

    # Fixed: Both must be valid
    if valid_user and valid_pass:
        return True
    return False

# Fixed: Type-consistent comparison
def secure_type_check(user_id):
    admin_ids = [1, 2, 3]  # Use consistent types

    # Or convert before comparison
    if isinstance(user_id, str):
        user_id = int(user_id)

    if user_id in admin_ids:
        grant_admin_access()
// Fixed: Strict equality in JavaScript
function securePermissionCheck(userLevel) {
    // Fixed: Strict equality avoids type coercion
    if (userLevel === 0) {
        return 'no access';
    }
    // Also explicitly check type if needed
    if (typeof userLevel !== 'number') {
        return 'invalid input';
    }
    return 'access granted';
}

// Fixed: Proper NaN check
function secureNumericCheck(value) {
    // Fixed: Use Number.isNaN()
    if (Number.isNaN(value)) {
        return 'invalid';
    }
    return 'valid';
}

// Fixed: Array content comparison
function secureArrayCheck(userRoles, requiredRoles) {
    if (!Array.isArray(userRoles) || !Array.isArray(requiredRoles)) {
        return false;
    }

    // Fixed: Compare array contents
    if (userRoles.length !== requiredRoles.length) {
        return false;
    }

    const sortedUser = [...userRoles].sort();
    const sortedRequired = [...requiredRoles].sort();

    return sortedUser.every((val, idx) => val === sortedRequired[idx]);
}

CVE Examples

  • CVE-2021-3116: Incorrect boolean operators in Python HTTP proxy caused authentication bypass.
  • CVE-2020-15811: Proxy using substring search instead of proper header parsing enabled request splitting.
  • CVE-2016-10003: Incorrect request header comparison led to information disclosure.

References

  1. MITRE Corporation. "CWE-697: Incorrect Comparison." https://cwe.mitre.org/data/definitions/697.html
  2. CWE-1023: Incomplete Comparison with Missing Factors.
  3. CWE-1024: Comparison of Incompatible Types.