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

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Umgehung des Schutzmechanismus - Fehlerhafte Vergleiche können Angreifern ermöglichen, Authentifizierung oder Autorisierung zu umgehen.
IntegritätBereich: Integrität

Unerwarteter Zustand - Inkorrekte Vergleiche führen zu falschem Programmzustand und -verhalten.
SonstigesBereich: 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

  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.