Vergleich mit falschen Faktoren

Beschreibung

Vergleich mit falschen Faktoren tritt auf, wenn ein Produkt einen Vergleich durchführt, aber die falschen Attribute, Faktoren oder Kriterien für den Vergleichskontext verwendet. Anders als bei unvollständigen Vergleichen (bei denen Faktoren fehlen) beinhaltet diese Schwachstelle die aktive Verwendung falscher Faktoren, die nicht ordnungsgemäß repräsentieren, was verglichen werden sollte. Dies kann zu falsch-positiven oder falsch-negativen Ergebnissen bei Sicherheitsentscheidungen führen, was entweder unnötige Ablehnungen gültiger Anfragen oder die Zulassung ungültiger Anfragen verursacht.

Risiko

Diese Schwachstelle kann Sicherheitsentscheidungen auf subtile Weise kompromittieren. Der Vergleich von Benutzer-IDs, wenn Session-IDs verglichen werden sollten, kann Session-Hijacking ermöglichen. Die Verwendung von Erstellungszeitstempeln anstelle von Änderungszeitstempeln für Cache-Validierung kann veraltete Inhalte liefern. Der Vergleich von Dateipfaden anstelle kanonischer Pfade ermöglicht Path-Traversal-Umgehungen. Die Prüfung nur des Benutzernamens ohne Mandanten-ID in Multi-Tenant-Systemen ermöglicht mandantenübergreifenden Zugriff. Das Risiko ist hoch, weil die Vergleichslogik funktional erscheint, aber grundlegend das Falsche prüft.

Lösung

Analysieren Sie sorgfältig, welche Faktoren für jeden Vergleichskontext relevant sind. Dokumentieren Sie die Vergleichskriterien und die Begründung. Vergleichen Sie bei der Authentifizierung Anmeldedaten mit gespeicherten Werten für die gleiche Identität. Vergleichen Sie bei der Autorisierung angeforderte Berechtigungen mit erteilten Berechtigungen für die gleiche Ressource. Vergleichen Sie bei der Datenvalidierung kanonische/normalisierte Formen anstelle von Roheingaben. Verwenden Sie Code-Reviews, um zu verifizieren, dass die richtigen Faktoren verglichen werden. Erwägen Sie die Verwendung dedizierter Vergleichsfunktionen, die die korrekte Faktorverwendung erzwingen. Schreiben Sie Tests, die verifizieren, dass Vergleiche fehlschlagen, wenn sie fehlschlagen sollten.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Die Verwendung falscher Faktoren bei Zugriffskontrollprüfungen kann unautorisierten Zugriff gewähren.
IntegritätBereich: Integrität

Anwendungsdaten ändern - Falsche Vergleiche können dazu führen, dass falsche Daten ausgewählt, geändert oder gelöscht werden.
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Falsche Vergleichsfaktoren können Daten unautorisierten Parteien offenlegen.

Beispielcode und Lösung

Verwundbarer Code

// Verwundbar: Falsche Faktoren für Session-Validierung vergleichen
public class VerwundbarerSessionValidator {

    public boolean validateSession(HttpServletRequest request) {
        String sessionToken = request.getHeader("X-Session-Token");
        User user = getUserFromToken(sessionToken);

        // Gespeicherte Session abrufen
        Session storedSession = sessionStore.get(sessionToken);

        if (storedSession == null) {
            return false;
        }

        // Verwundbar: Benutzer-ID statt Session-Token vergleichen!
        // Dies ermöglicht Session-Hijacking, wenn Benutzer-ID übereinstimmt
        return user.getId().equals(storedSession.getUserId());

        // Sollte vergleichen: sessionToken.equals(storedSession.getToken())
    }
}
# Verwundbar: Rohen Pfad statt kanonischen Pfad vergleichen
import os

def verwundbare_dateizugriff_prüfung(angeforderter_pfad, erlaubtes_verzeichnis):
    # Verwundbar: Rohe Pfade vergleichen
    # Angreifer kann "../" verwenden, um erlaubtes Verzeichnis zu verlassen

    if angeforderter_pfad.startswith(erlaubtes_verzeichnis):
        return True  # Zugriff erlauben

    return False

# Angriff: angeforderter_pfad = "/var/www/erlaubt/../../../etc/passwd"
# Dies beginnt mit "/var/www/erlaubt", verlässt es aber!
// Verwundbar: Falschen Zeitstempel für Cache-Validierung verwenden
class VerwundbarerCache {

    isValid(cachedItem, sourceItem) {
        // Verwundbar: Erstellungszeit statt Änderungszeit vergleichen
        // Veraltete Daten werden geliefert, wenn nach Erstellung geändert

        return cachedItem.createdAt >= sourceItem.createdAt;

        // Sollte vergleichen: cachedItem.cachedAt >= sourceItem.modifiedAt
    }

    get(key) {
        const cached = this.cache.get(key);
        const source = this.source.get(key);

        if (cached && this.isValid(cached, source)) {
            return cached.data;  // Kann veraltete Daten zurückgeben!
        }

        return this.fetchAndCache(key);
    }
}
<?php
// Verwundbar: Multi-Tenant-Abfrage ohne Mandanten-Vergleich
class VerwundbarerDokumentenService {

    public function getDocument($documentId, $userId) {
        // Verwundbar: Prüft nur Dokument-ID und Benutzer-ID
        // Mandanten-ID-Vergleich fehlt!

        $sql = "SELECT * FROM documents
                WHERE id = ? AND owner_id = ?";

        $result = $this->db->query($sql, [$documentId, $userId]);

        // Benutzer von Mandant A kann auf Dokumente zugreifen, wenn er
        // die gleiche Benutzer-ID wie der Dokumentenbesitzer in Mandant B hat
        return $result;
    }
}
// Verwundbar: String-Darstellung statt numerischen Wert vergleichen
func verwundbareKontostandPrüfung(angefordert float64, verfügbar string) bool {
    // Verwundbar: Float mit String-Darstellung vergleichen
    // Präzisionsprobleme und Formatunterschiede verursachen Probleme

    angeforderterStr := fmt.Sprintf("%f", angefordert)

    // "100.000000" vs "100.0" - stimmt möglicherweise nicht überein, obwohl gleich
    return angeforderterStr == verfügbar
}
// Verwundbar: Speicheradressen statt Werte vergleichen
#include <string.h>

int verwundbare_token_vergleich(const char *provided, const char *stored) {
    // Verwundbar: Pointer statt String-Inhalt vergleichen
    // Gibt nur true zurück, wenn beide auf gleiche Speicherstelle zeigen

    return provided == stored;  // Falsch: vergleicht Adressen!

    // Sollte sein: strcmp(provided, stored) == 0
}

// Verwundbar: sizeof auf Pointer statt Array verwenden
int verwundbare_array_vergleich(int *arr1, int *arr2) {
    // Verwundbar: sizeof(arr1) ist Zeigergröße, nicht Array-Größe!
    return memcmp(arr1, arr2, sizeof(arr1)) == 0;

    // Dies vergleicht nur die ersten 4 oder 8 Bytes (Zeigergröße)
}

Sichere Lösung

// Sicher: Korrekte Faktoren für Session-Validierung vergleichen
public class SichererSessionValidator {

    public boolean validateSession(HttpServletRequest request) {
        String sessionToken = request.getHeader("X-Session-Token");

        if (sessionToken == null || sessionToken.isEmpty()) {
            return false;
        }

        // Gespeicherte Session nach Token abrufen
        Session storedSession = sessionStore.get(sessionToken);

        if (storedSession == null) {
            return false;
        }

        // Sicher: Den Session-Token selbst vergleichen
        if (!secureCompare(sessionToken, storedSession.getToken())) {
            return false;
        }

        // Sicher: Auch prüfen, ob Session nicht abgelaufen ist
        if (storedSession.isExpired()) {
            return false;
        }

        // Sicher: IP-Bindung verifizieren, wenn aktiviert
        String clientIP = request.getRemoteAddr();
        if (storedSession.isIPBound() &&
            !clientIP.equals(storedSession.getBoundIP())) {
            return false;
        }

        return true;
    }

    private boolean secureCompare(String a, String b) {
        return MessageDigest.isEqual(
            a.getBytes(StandardCharsets.UTF_8),
            b.getBytes(StandardCharsets.UTF_8)
        );
    }
}
# Sicher: Kanonische Pfade vergleichen
import os

def sichere_dateizugriff_prüfung(angeforderter_pfad, erlaubtes_verzeichnis):
    # Sicher: Zu kanonischen (realen) Pfaden auflösen
    kanonisch_angefordert = os.path.realpath(angeforderter_pfad)
    kanonisch_erlaubt = os.path.realpath(erlaubtes_verzeichnis)

    # Sicher: Erlaubtes Verzeichnis muss mit Separator enden
    if not kanonisch_erlaubt.endswith(os.sep):
        kanonisch_erlaubt += os.sep

    # Sicher: Kanonische Pfade vergleichen
    # Auch prüfen, dass Datei selbst innerhalb erlaubt ist (nicht nur beginnt mit)
    return kanonisch_angefordert.startswith(kanonisch_erlaubt) or \
           kanonisch_angefordert == kanonisch_erlaubt.rstrip(os.sep)


# Alternative: pathlib für saubereren Vergleich verwenden
from pathlib import Path

def sichere_dateizugriff_prüfung_pathlib(angeforderter_pfad, erlaubtes_verzeichnis):
    try:
        angefordert = Path(angeforderter_pfad).resolve()
        erlaubt = Path(erlaubtes_verzeichnis).resolve()

        # Sicher: Prüfen, ob angeforderter Pfad relativ zu erlaubtem Verzeichnis ist
        angefordert.relative_to(erlaubt)
        return True
    except ValueError:
        # Nicht relativ zu erlaubtem Verzeichnis
        return False
// Sicher: Korrekten Zeitstempel für Cache-Validierung verwenden
class SichererCache {

    isValid(cachedItem, sourceItem) {
        // Sicher: Cache-Zeit mit Quell-Änderungszeit vergleichen
        return cachedItem.cachedAt >= sourceItem.modifiedAt;
    }

    get(key) {
        const cached = this.cache.get(key);

        if (!cached) {
            return this.fetchAndCache(key);
        }

        const source = this.source.getMetadata(key);

        // Sicher: Änderungszeit für Vergleich verwenden
        if (this.isValid(cached, source)) {
            return cached.data;
        }

        return this.fetchAndCache(key);
    }

    fetchAndCache(key) {
        const data = this.source.get(key);
        const metadata = this.source.getMetadata(key);

        this.cache.set(key, {
            data: data,
            cachedAt: Date.now(),
            sourceModifiedAt: metadata.modifiedAt
        });

        return data;
    }
}
<?php
// Sicher: Multi-Tenant-Abfrage mit ordnungsgemäßer Mandanten-Isolation
class SichererDokumentenService {

    public function getDocument($documentId, $userId, $tenantId) {
        // Sicher: Mandanten-ID in Vergleich einbeziehen
        $sql = "SELECT * FROM documents
                WHERE id = ?
                AND owner_id = ?
                AND tenant_id = ?";

        $result = $this->db->query($sql, [$documentId, $userId, $tenantId]);

        return $result;
    }

    // Alternative: Mandanten-beschränktes Repository verwenden
    public function getDocumentScoped($documentId, $tenantContext) {
        // Mandantenkontext wird auf Request-Ebene gesetzt, kann nicht gefälscht werden
        $sql = "SELECT * FROM documents
                WHERE id = ?
                AND tenant_id = ?";

        $result = $this->db->query($sql, [
            $documentId,
            $tenantContext->getTenantId()
        ]);

        if ($result && $result['owner_id'] !== $tenantContext->getUserId()) {
            // Eigentümerschaft innerhalb des Mandanten prüfen
            if (!$this->canAccessDocument($result, $tenantContext)) {
                throw new UnauthorizedException();
            }
        }

        return $result;
    }
}
// Sicher: String-Werte korrekt vergleichen
#include <string.h>
#include <openssl/crypto.h>

int sicherer_token_vergleich(const char *provided, const char *stored) {
    if (provided == NULL || stored == NULL) {
        return 0;  // NULL als nicht übereinstimmend behandeln
    }

    size_t provided_len = strlen(provided);
    size_t stored_len = strlen(stored);

    if (provided_len != stored_len) {
        return 0;
    }

    // Sicher: String-Inhalt vergleichen, nicht Pointer
    // Konstant-zeitlichen Vergleich für Sicherheit verwenden
    return CRYPTO_memcmp(provided, stored, stored_len) == 0;
}

// Sicher: Arrays mit korrekter Größe vergleichen
int sicherer_array_vergleich(const int *arr1, const int *arr2, size_t count) {
    // Sicher: Array-Größe als Parameter übergeben
    return memcmp(arr1, arr2, count * sizeof(int)) == 0;
}

// Verwendung:
// int a[10], b[10];
// sicherer_array_vergleich(a, b, 10);  // Vergleicht alle 10 Elemente

Ausgenutzt in der Praxis

OAuth-Token-Verwechslung (2016)

Mehrere OAuth-Implementierungen verglichen Token-IDs anstelle der vollständigen Tokensignatur, was Token-Fälschungsangriffe ermöglichte.

Multi-Tenant-SaaS-Datenlecks (2018)

Ein größer SaaS-Anbieter hatte eine Schwachstelle, bei der Mandanten-IDs nicht in Datenbankabfragen verglichen wurden, was mandantenübergreifenden Datenzugriff ermöglichte.

Path-Traversal durch falschen Pfadvergleich (2019)

Mehrere Webserver verglichen Dateipfade ohne Kanonisierung, was Path-Traversal-Angriffe ermöglichte.


Tools zum Testen und Ausnutzen

  • Burp Suite - Parameteranalyse und -manipulation.

  • OWASP ZAP - Automatisierte Sicherheitstests.


CVE-Beispiele

  • CVE-2006-5393 - Produkt verglich IP-Adresse mit falschem gespeicherten Wert.

  • CVE-2005-3025 - Authentifizierung verglich Passwort-Hash mit Hash des falschen Benutzers.


Referenzen

  1. MITRE Corporation. "CWE-1025: Comparison Using Wrong Factors." https://cwe.mitre.org/data/definitions/1025.html

  2. OWASP. "Access Control Design Guidelines." https://cheatsheetseries.owasp.org/cheatsheets/Access_Control_Cheat_Sheet.html

  3. SANS. "Secure Coding Practices." https://www.sans.org/top25-software-errors/