This weakness occurs when code compares two entities such as users, files, or requests but checks only some of the required characteristics, ignoring others…
This weakness occurs when code compares two entities (such as users, files, or requests) but checks only some of the required characteristics, ignoring others. An attacker can bypass security checks by crafting input that matches the checked factors while differing in the unchecked ones. This is a logic flaw, not a coding error—the code works as written, but the logic is incomplete.
02How It Happens
Security decisions often depend on multiple criteria. For example, a file access check might need to verify both the filename *and* the file owner; a user authentication check might need to verify both the username *and* the account status; a request validator might need to check both the method *and* the origin. When developers implement only a subset of these checks—either by oversight, misunderstanding requirements, or time pressure—the incomplete comparison creates a gap. An attacker who understands which factors are missing can satisfy only the checked conditions and bypass the intended protection.
The root cause is usually a mismatch between the security policy (what *should* be checked) and the implementation (what *is* checked). This is particularly common in refactoring, when legacy code is partially updated, or when security requirements are added after initial development.
03Real-World Impact
Incomplete comparisons can lead to unauthorized access, privilege escalation, or data exposure. For instance, if a system checks that a user has "admin" in their role but forgets to verify the account is active, a disabled admin account could still perform privileged actions. If a file-sharing system checks the filename matches a whitelist but ignores the directory path, an attacker might access files outside the intended folder. The severity depends on what factors are missing and how critical they are to the security decision.
04Vulnerable & Fixed Patterns
Vulnerable pattern
def is_authorized_user(username, role):
# Check only the role, ignoring account status
if role == "admin":
return True
return False
# Later, in request handler:
user_role = get_user_role(username)
if is_authorized_user(username, user_role):
perform_sensitive_operation()
Why it's vulnerable: The function checks only the role but ignores whether the account is active, suspended, or locked. A disabled admin account would still pass the check.
Fixed pattern
def is_authorized_user(username, role):
# Check both role AND account status
user_status = get_user_status(username)
if role == "admin" and user_status == "active":
return True
return False
# Later, in request handler:
user_role = get_user_role(username)
if is_authorized_user(username, user_role):
perform_sensitive_operation()
Vulnerable pattern
function is_file_accessible($filename, $user_id) {
// Check only the filename against whitelist, ignore directory
$allowed_files = ['report.pdf', 'summary.txt'];
if (in_array(basename($filename), $allowed_files)) {
return true;
}
return false;
}
// Later:
if (is_file_accessible($_GET['file'], $user_id)) {
readfile($_GET['file']);
}
Why it's vulnerable: The check compares only the filename against a whitelist but ignores the full path. An attacker could request ../../sensitive/report.pdf and bypass the check because basename() extracts only report.pdf.
Fixed pattern
function is_file_accessible($filename, $user_id) {
// Check both filename AND that it's in the allowed directory
$allowed_dir = '/var/www/public_files/';
$real_path = realpath($filename);
if ($real_path === false || strpos($real_path, $allowed_dir) !== 0) {
return false;
}
$allowed_files = ['report.pdf', 'summary.txt'];
if (in_array(basename($real_path), $allowed_files)) {
return true;
}
return false;
}
// Later:
if (is_file_accessible($_GET['file'], $user_id)) {
readfile($_GET['file']);
}
05Prevention Checklist
Document all factors that should be checked for each security decision (e.g., "user must be admin AND active AND not locked").
Review comparisons in code that make security decisions; ensure every documented factor is actually tested.
Use allowlists and deny-lists together where applicable—check both what is allowed and what is explicitly forbidden.
Test with edge cases that match some factors but not others (e.g., a disabled admin, a file in a parent directory, a request from an allowed IP but with a forged header).
Pair security checks with assertions or comments explaining why each factor matters, so future maintainers understand the full requirement.
Use centralized validation functions rather than scattering checks across the codebase, reducing the chance of inconsistent or incomplete logic.
06Signs You May Already Be Affected
Look for security checks in your codebase that test only one or two conditions when the policy requires more. For example, search for role or permission checks that don't also verify account status, or path validation that checks only the filename. Review access logs for patterns of unusual activity from accounts that should be restricted (disabled users, locked accounts, or users accessing resources outside their expected scope).