Inkorrekte Vergleichsoperation
Beschreibung
Inkorrekte Vergleichsoperation ist eine Schwachstelle auf Säulenebene, bei der Software einen Vergleich in einem sicherheitsrelevanten Kontext durchführt, aber der Vergleich selbst fehlerhaft ist. Dies umfasst mehrere Szenarien: Der Vergleich wertet einen Faktor falsch aus, mehrere Faktoren sollten berücksichtigt werden, aber mindestens einer wird vollständig ausgelassen, oder der Vergleich untersucht überhaupt den falschen Faktor. Wenn Vergleiche in Sicherheitskontexten fehlerhaft sind, führen sie typischerweise zu Umgehungen von Authentifizierungs-, Autorisierungs- oder Validierungsprüfungen.
Risiko
Inkorrekte Vergleiche schaffen schwerwiegende Sicherheitslücken. Authentifizierungssysteme mit partiellen String-Vergleichen können mit abgeschnittenen Anmeldedaten umgangen werden. Autorisierungsprüfungen, bei denen erforderliche Faktoren fehlen, können unangemessenen Zugriff gewähren. Gleichheitsprüfungen, die Groß-/Kleinschreibung oder Kodierung ignorieren, können bösartige Eingaben akzeptieren. Gleitkommazahl-Vergleiche mit falschen Operatoren liefern unzuverlässige Ergebnisse. Das Risiko wird verstärkt, weil Vergleichsfehler oft syntaktisch korrekt erscheinen und in den meisten Testfällen funktionieren können, nur in sicherheitskritischen Randfällen versagen, die Angreifer gezielt angreifen.
Lösung
Stellen Sie sicher, dass Vergleiche alle relevanten Faktoren einschließen. Verwenden Sie typsichere Vergleichsmethoden, die für die verglichenen Daten geeignet sind. Berücksichtigen Sie bei Strings Groß-/Kleinschreibung, Kodierung und Locale-Anforderungen. Verwenden Sie bei Gleitkommazahlen geeignete Epsilon-basierte Vergleiche. Überprüfen Sie, dass Vergleichsergebnisse korrekt verwendet werden (Prüfung auf Gleichheit vs. Ungleichheit). Verwenden Sie gut getestete Bibliotheksfunktionen für komplexe Vergleiche. Überprüfen und testen Sie Vergleichslogik mit Grenzfällen und gegnerischen Eingaben. Implementieren Sie umfassende Gleichheitsmethoden, die alle signifikanten Felder berücksichtigen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Umgehung des Schutzmechanismus - Fehlerhafte Vergleiche können Angreifern ermöglichen, Authentifizierung oder Autorisierung zu umgehen. |
| Integrität | Bereich: Integrität Unerwarteter Zustand - Inkorrekte Vergleiche führen zu falschem Programmzustand und -verhalten. |
| Sonstiges | Bereich: Sonstiges Variiert je nach Kontext - Auswirkungen hängen davon ab, was der Vergleich steuert. |
Beispielcode
Verwundbarer Code
// Verwundbar: Partieller String-Vergleich bei Authentifizierung
#include <string.h>
int vulnerable_authenticate(char *input_user, char *input_pass) {
char *stored_user = "administrator";
char *stored_pass = "secretpassword";
// Verwundbar: Verwendung von strlen der Angreifer-kontrollierten Eingabe!
// Angreifer kann "a" als Benutzername und "s" als Passwort verwenden
if (strncmp(stored_user, input_user, strlen(input_user)) != 0) {
return AUTH_FAIL;
}
// Verwundbar: Gleiches Problem - partieller Abgleich beim Passwort
if (strncmp(stored_pass, input_pass, strlen(input_pass)) == 0) {
return AUTH_SUCCESS; // Passt mit "s", "se", "sec", usw.
}
return AUTH_FAIL;
}
// Verwundbar: Falscher Vergleichsoperator für Gleitkommazahlen
int vulnerable_balance_check(float balance, float withdrawal) {
// Verwundbar: Direkter Float-Gleichheitsvergleich ist unzuverlässig
if (balance - withdrawal == 0.0) {
// Löst möglicherweise nicht aus, wenn es sollte, aufgrund von Gleitkommapräzision
return ERROR_INSUFFICIENT_FUNDS;
}
return OK;
}
// Verwundbar: Fehlendes Feld im equals-Vergleich
public class Truck {
private String make;
private String model;
private int year; // Wichtiges unterscheidendes Feld
@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;
// Verwundbar: Jahr-Vergleich fehlt!
// Zwei Trucks aus verschiedenen Jahren werden als gleich betrachtet
return this.make.equals(t.getMake()) &&
this.model.equals(t.getModel());
// Sollte auch vergleichen: && this.year == t.getYear()
}
@Override
public int hashCode() {
// Auch verwundbar: hashCode enthält Jahr nicht
return Objects.hash(make, model);
}
}
// Verwundbar: Groß-/Kleinschreibungsabhängiger Vergleich, wo unabhängiger nötig ist
public class VulnerableAuth {
public boolean checkRole(String userRole, String requiredRole) {
// Verwundbar: Groß-/Kleinschreibungsabhängiger Vergleich
// "Admin" != "admin"
return userRole.equals(requiredRole);
}
}
// Verwundbar: Substring-Vergleich anstelle von exaktem Abgleich
public class VulnerableHeaderCheck {
public boolean isValidOrigin(String origin) {
// Verwundbar: Substring-Abgleich anstelle von exaktem oder ordnungsgemäßem Parsing
return origin.contains("trusted.com");
// Akzeptiert "malicious-trusted.com" oder "trusted.com.evil.com"
}
}
# Verwundbar: Falsche Vergleichslogik für HTTP-Header (CVE-2020-15811-Muster)
def vulnerable_header_parse(header_line):
# Verwundbar: Substring-Suche anstelle von ordnungsgemäßem Parsing
if 'Content-Length' in header_line:
# Extrahiert Wert nach "Content-Length" irgendwo im String
# Kann getäuscht werden durch "X-Fake-Content-Length: 0\r\nContent-Length: 999"
parts = header_line.split(':')
return int(parts[1].strip())
return None
# Verwundbar: Inkorrekte boolesche Operatoren (CVE-2021-3116-Muster)
def vulnerable_auth_check(user, password):
valid_user = user == EXPECTED_USER
valid_pass = password == EXPECTED_PASS
# Verwundbar: Sollte AND sein, nicht OR
if valid_user or valid_pass: # FALSCH - einer reicht!
return True
return False
# Verwundbar: Falsche Typen vergleichen
def vulnerable_type_check(user_id):
admin_ids = ['1', '2', '3'] # String-Liste
# Verwundbar: Int mit Strings vergleichen
if user_id in admin_ids: # 1 != '1'
grant_admin_access()
# Integer 1 wird in String-Liste nicht gefunden
// Verwundbar: Lose Gleichheit in JavaScript
function vulnerablePermissionCheck(userLevel) {
// Verwundbar: Lose Gleichheit mit Typumwandlung
if (userLevel == 0) { // Typumwandlungsprobleme
// "0" == 0 ist true
// false == 0 ist true
// null == 0 ist false (inkonsistent)
return 'no access';
}
return 'access granted';
}
// Verwundbar: NaN-Vergleich
function vulnerableNumericCheck(value) {
// Verwundbar: NaN ist nie gleich irgendwas, einschließlich sich selbst
if (value === NaN) { // Immer false!
return 'invalid';
}
return 'valid';
}
// Verwundbar: Array-Vergleich
function vulnerableArrayCheck(userRoles, requiredRoles) {
// Verwundbar: Arrays werden per Referenz verglichen, nicht per Wert
if (userRoles === requiredRoles) { // Fast immer false
return true;
}
return false;
}
Korrigierter Code
// Korrigiert: Vollständiger String-Vergleich mit konstantzeitlicher Vergleichsfunktion
#include <string.h>
// Konstantzeitlicher String-Vergleich zur Verhinderung von Timing-Angriffen
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";
// Korrigiert: Vollständige Strings mit bekannten Längen vergleichen
size_t user_len = strlen(stored_user);
size_t pass_len = strlen(stored_pass);
// Korrigiert: Zuerst Längen prüfen, dann vollständige Strings vergleichen
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;
}
// Korrigiert: Epsilon-Vergleich für Gleitkommazahlen
#include <math.h>
int secure_balance_check(double balance, double withdrawal) {
double remaining = balance - withdrawal;
double epsilon = 0.001; // Angemessene Toleranz
// Korrigiert: Epsilon-Vergleich verwenden
if (fabs(remaining) < epsilon) {
return ERROR_INSUFFICIENT_FUNDS;
}
if (remaining < 0) {
return ERROR_INSUFFICIENT_FUNDS;
}
return OK;
}
// Korrigiert: Vollständige equals-Implementierung
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;
// Korrigiert: ALLE signifikanten Felder einschließen
return Objects.equals(this.make, t.getMake()) &&
Objects.equals(this.model, t.getModel()) &&
this.year == t.getYear();
}
@Override
public int hashCode() {
// Korrigiert: Alle Felder in hashCode einschließen
return Objects.hash(make, model, year);
}
}
// Korrigiert: Groß-/Kleinschreibungsunabhängiger Vergleich
public class SecureAuth {
public boolean checkRole(String userRole, String requiredRole) {
if (userRole == null || requiredRole == null) {
return false;
}
// Korrigiert: Groß-/Kleinschreibungsunabhängiger Vergleich
return userRole.equalsIgnoreCase(requiredRole);
}
}
// Korrigiert: Ordnungsgemäße Origin-Validierung
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;
// Korrigiert: Exakter Abgleich gegen Whitelist
return ALLOWED_ORIGINS.contains(origin);
}
}
# Korrigiert: Ordnungsgemäßes Header-Parsing
def secure_header_parse(header_line):
# Korrigiert: Header ordnungsgemäß parsen
if ':' not in header_line:
return None
name, _, value = header_line.partition(':')
name = name.strip()
# Korrigiert: Exakter Header-Namen-Abgleich (groß-/kleinschreibungsunabhängig per HTTP-Spezifikation)
if name.lower() == 'content-length':
try:
return int(value.strip())
except ValueError:
return None
return None
# Korrigiert: Korrekte boolesche Logik
def secure_auth_check(user, password):
valid_user = user == EXPECTED_USER
valid_pass = password == EXPECTED_PASS
# Korrigiert: Beide müssen gültig sein
if valid_user and valid_pass:
return True
return False
# Korrigiert: Typkonsistenter Vergleich
def secure_type_check(user_id):
admin_ids = [1, 2, 3] # Konsistente Typen verwenden
# Oder vor Vergleich konvertieren
if isinstance(user_id, str):
user_id = int(user_id)
if user_id in admin_ids:
grant_admin_access()
// Korrigiert: Strikte Gleichheit in JavaScript
function securePermissionCheck(userLevel) {
// Korrigiert: Strikte Gleichheit vermeidet Typumwandlung
if (userLevel === 0) {
return 'no access';
}
// Auch Typ explizit prüfen, wenn nötig
if (typeof userLevel !== 'number') {
return 'invalid input';
}
return 'access granted';
}
// Korrigiert: Ordnungsgemäße NaN-Prüfung
function secureNumericCheck(value) {
// Korrigiert: Number.isNaN() verwenden
if (Number.isNaN(value)) {
return 'invalid';
}
return 'valid';
}
// Korrigiert: Array-Inhaltsvergleich
function secureArrayCheck(userRoles, requiredRoles) {
if (!Array.isArray(userRoles) || !Array.isArray(requiredRoles)) {
return false;
}
// Korrigiert: Array-Inhalte vergleichen
if (userRoles.length !== requiredRoles.length) {
return false;
}
const sortedUser = [...userRoles].sort();
const sortedRequired = [...requiredRoles].sort();
return sortedUser.every((val, idx) => val === sortedRequired[idx]);
}
CVE-Beispiele
- CVE-2021-3116: Inkorrekte boolesche Operatoren in Python HTTP-Proxy verursachten Authentifizierungsumgehung.
- CVE-2020-15811: Proxy, der Substring-Suche anstelle von ordnungsgemäßem Header-Parsing verwendete, ermöglichte Request Splitting.
- CVE-2016-10003: Inkorrekter Request-Header-Vergleich führte zu Informationsoffenlegung.
Referenzen
- 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.