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
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Schutzmechanismus umgehen - Die Verwendung falscher Faktoren bei Zugriffskontrollprüfungen kann unautorisierten Zugriff gewähren. |
| Integrität | Bereich: Integrität Anwendungsdaten ändern - Falsche Vergleiche können dazu führen, dass falsche Daten ausgewählt, geändert oder gelöscht werden. |
| Vertraulichkeit | Bereich: 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
-
MITRE Corporation. "CWE-1025: Comparison Using Wrong Factors." https://cwe.mitre.org/data/definitions/1025.html
-
OWASP. "Access Control Design Guidelines." https://cheatsheetseries.owasp.org/cheatsheets/Access_Control_Cheat_Sheet.html
-
SANS. "Secure Coding Practices." https://www.sans.org/top25-software-errors/