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
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Bypass Protection Mechanism - Flawed comparisons can allow attackers to bypass authentication or authorization. |
| Integrity | Scope: Integrity Unexpected State - Incorrect comparisons lead to incorrect program state and behavior. |
| Other | Scope: 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
- MITRE Corporation. "CWE-697: Incorrect Comparison." https://cwe.mitre.org/data/definitions/697.html
- CWE-1023: Incomplete Comparison with Missing Factors.
- CWE-1024: Comparison of Incompatible Types.