Authentifizierungsumgehung durch primäre Schwäche
Beschreibung
Authentifizierungsumgehung durch primäre Schwäche ist eine Schwachstelle, die auftritt, wenn der Authentifizierungsalgorithmus selbst solide ist, aber eine zugrundeliegende Schwäche in anderen Teilen der Implementierung die Umgehung des Authentifizierungsmechanismus ermöglicht. Dies wird als resultierende Schwäche klassifiziert -- die Authentifizierungsumgehung beruht nicht auf Fehlern in der Authentifizierungslogik selbst, sondern stammt von separaten Problemen wie unsachgemäßer Eingabeverarbeitung, Speicherverwaltungsfehlern oder Vergleichslogikfehlern. Häufige Beispiele umfassen Passwortvergleiche, die nur das erste Zeichen prüfen, nicht initialisierte Variablen, die Passwörter abschneiden, oder Pufferbehandlungsfehler, die eine Umgehung ermöglichen.
Risiko
Authentifizierungsumgehung durch primäre Schwächen ist besonders heimtückisch, da Sicherheitsreviews, die sich ausschließlich auf den Authentifizierungsalgorithmus konzentrieren, die zugrundeliegende Schwachstelle übersehen können. Das Risiko ist schwerwiegend, da diese Schwächen oft komplexe Authentifizierung auf trivial umgehbare Prüfungen reduzieren -- wie die Verifizierung nur eines einzelnen Zeichens eines Passworts. Angreifer, die diese zugrundeliegenden Fehler identifizieren, können die Authentifizierung mit minimalem Aufwand umgehen. Die Schwachstellen, die diese Umgehungen verursachen, umfassen mehrere Kategorien einschließlich Speichersicherheitsprobleme, Vergleichsfehler und Initialisierungsprobleme. Da der Authentifizierungsmechanismus selbst korrekt erscheint, können diese Probleme durch mehrere Sicherheitsaudits bestehen bleiben, bis sie durch detaillierte Codeanalyse oder Ausnutzung entdeckt werden.
Lösung
Führen Sie umfassende Sicherheitstests durch, die über die Authentifizierungslogik hinausgehen und den gesamten unterstützenden Code untersuchen. Verwenden Sie speichersichere Programmiersprachen oder wenden Sie strenge Speichersicherheitspraktiken bei Verwendung von Sprachen wie C an. Initialisieren Sie alle Variablen explizit, insbesondere solche, die in sicherheitskritischen Vergleichen verwendet werden. Verwenden Sie Konstante-Zeit-Vergleichsfunktionen für Passwort- und Zugangsdaten-Verifizierung. Implementieren Sie umfassende Unit-Tests, die verifizieren, dass vollständige Zugangsdaten-Werte verglichen werden, nicht nur Präfixe. Wenden Sie statische Analyse-Tools an, um nicht initialisierte Variablen und Vergleichslogikfehler zu erkennen. Validieren Sie Eingabelängen vor der Verarbeitung von Zugangsdaten. Verwenden Sie gut getestete Authentifizierungsbibliotheken anstelle benutzerdefinierter Implementierungen, die subtile Fehler enthalten können.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Die zugrundeliegende Schwäche ermöglicht es Angreifern, die Authentifizierung vollständig zu umgehen und unautorisierten Zugang mit minimalen Zugangsdaten oder Aufwand zu erhalten. |
| Integrität, Vertraulichkeit | Bereich: Integrität, Vertraulichkeit Bei umgangener Authentifizierung erhalten Angreifer vollen Zugang zu geschützten Ressourcen, können Benutzeridentitäten annehmen und sensible Daten einsehen oder ändern. |
Beispielcode und Lösung
Verwundbarer Code (C)
Die folgenden Beispiele zeigen Authentifizierungsumgehung durch zugrundeliegende Schwächen:
// Verwundbar: Passwortvergleich prüft nur erstes Zeichen
#include <string.h>
#include <stdlib.h>
typedef struct {
char username[64];
char password[64];
} UserCredentials;
int vulnerable_authenticate(const char *username, const char *password) {
UserCredentials *user = lookup_user(username);
if (user == NULL) {
return 0;
}
// Verwundbar: strlen gibt 0 zurück aufgrund nicht initialisierten Speichers
// oder Pufferberechnungsfehler
size_t password_len = get_password_length(user);
// Verwundbar: Vergleicht nur erstes Zeichen wenn password_len 1 ist
// aufgrund eines Fehlers in get_password_length()
if (strncmp(password, user->password, password_len) == 0) {
return 1; // Mit nur dem ersten Zeichen authentifiziert!
}
return 0;
}
// Verwundbar: Nicht initialisiertes Array schneidet Passwort ab
int vulnerable_password_check(const char *input, const char *stored) {
char buffer[64];
// Verwundbar: buffer ist nicht initialisiert
// Erstes Null-Byte in nicht initialisierten Daten schneidet Vergleich ab
int i;
for (i = 0; input[i] != '\0' && i < 63; i++) {
buffer[i] = input[i];
}
// Verwundbar: Fehlender Null-Terminator wenn input genau 63 Zeichen lang ist
// Wenn buffer ein zufälliges Null-Byte an Position 1 hat, wird nur erstes Zeichen verglichen
return strcmp(buffer, stored) == 0;
}
# Verwundbar: Integer-Behandlungsfehler verursacht teilweisen Vergleich
def vulnerable_verify_password(input_password, stored_hash):
# Verwundbar: Längenfehlberechnung aufgrund von Kodierungsproblemen
# UTF-8-Multibyte-Zeichen verursachen Längenunterschied
input_bytes = input_password.encode('utf-8')
stored_bytes = stored_hash.encode('utf-8')
# Verwundbar: Verwendet falsche Länge für Vergleich
# Wenn Angreifer bestimmtes Unicode eingibt, gibt len() falschen Wert zurück
compare_length = len(input_password) # Zeichenanzahl, nicht Byte-Anzahl!
# Vergleicht nur teilweisen Hash bei Längenunterschied
if input_bytes[:compare_length] == stored_bytes[:compare_length]:
return True
return False
# Verwundbar: Typverwechslung ermöglicht Umgehung
def vulnerable_auth_check(username, password):
user = get_user(username)
if user is None:
return False
stored_password = user.get('password')
# Verwundbar: Wenn password-Parameter kein String ist,
# könnte sich der Vergleich unerwartet verhalten
if password == stored_password:
return True
# Verwundbar: Leerer String-Vergleich
if not password and not stored_password:
return True # Beide "leer" - umgangen!
return False
// Verwundbar: Null-Byte-Injection schneidet Vergleich ab
public class VulnerableAuth {
public boolean authenticate(String username, byte[] password) {
User user = userRepository.findByUsername(username);
if (user == null) {
return false;
}
byte[] storedPassword = user.getPasswordBytes();
// Verwundbar: Konvertierung in String, Null-Byte schneidet ab
String inputStr = new String(password); // Stoppt beim ersten \0
String storedStr = new String(storedPassword);
// Wenn Angreifer "a\0garbage" sendet, wird nur "a" verglichen
return inputStr.equals(storedStr);
}
// Verwundbar: Array-Grenzenproblem verursacht teilweisen Vergleich
public boolean vulnerableCompare(char[] input, char[] stored) {
// Verwundbar: Verwendet Eingabelänge, nicht gespeicherte Länge
// Angreifer sendet kurze Eingabe, um Vergleich zu reduzieren
for (int i = 0; i < input.length; i++) {
if (i >= stored.length || input[i] != stored[i]) {
return false;
}
}
// Gibt true zurück wenn Eingabe Präfix von gespeichertem Wert ist!
// "a" passt zu "admin123"
return true;
}
}
Sichere Lösung (C)
// Behoben: Vollständiger Passwortvergleich mit korrekter Initialisierung
#include <string.h>
#include <stdlib.h>
#include <openssl/crypto.h>
typedef struct {
char username[64];
char password_hash[65]; // SHA-256 hex + Null
size_t password_len; // Explizit gespeicherte Länge
} UserCredentials;
int secure_authenticate(const char *username, const char *password) {
UserCredentials *user = lookup_user(username);
// Konstante-Zeit-Verhalten auch wenn Benutzer nicht gefunden
if (user == NULL) {
// Dummy-Hash durchführen um Timing-Angriffe zu verhindern
char dummy_hash[65];
compute_password_hash(password, dummy_hash);
return 0;
}
// Hash des Eingabepassworts berechnen
char input_hash[65];
compute_password_hash(password, input_hash);
// Behoben: Konstante-Zeit-Vergleich vollständiger Strings
// CRYPTO_memcmp gibt 0 bei Gleichheit zurück, sonst ungleich Null
if (CRYPTO_memcmp(input_hash, user->password_hash, 64) == 0) {
return 1;
}
return 0;
}
// Behoben: Korrekte Pufferinitialisierung und Grenzprüfung
int secure_password_check(const char *input, const char *stored) {
// Behoben: Puffer explizit mit Nullen initialisieren
char buffer[64] = {0};
if (input == NULL || stored == NULL) {
return 0;
}
size_t input_len = strlen(input);
size_t stored_len = strlen(stored);
// Behoben: Längen vor Vergleich prüfen
if (input_len != stored_len) {
return 0;
}
if (input_len >= sizeof(buffer)) {
return 0; // Eingabe zu lang
}
// Behoben: Mit explizitem Null-Terminator kopieren
strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
// Behoben: Konstante-Zeit-Vergleich vollständiger Strings
return CRYPTO_memcmp(buffer, stored, stored_len) == 0;
}
# Behoben: Ordnungsgemäßer vollständiger Passwortvergleich
import hmac
import hashlib
def secure_verify_password(input_password, stored_hash):
if not input_password or not stored_hash:
return False
# Behoben: Eingabepasswort mit demselben Algorithmus hashen
input_hash = hashlib.sha256(input_password.encode('utf-8')).hexdigest()
# Behoben: Konstante-Zeit-Vergleich vollständiger Hashes
return hmac.compare_digest(input_hash, stored_hash)
def secure_auth_check(username, password):
# Behoben: Typen vor Vergleich validieren
if not isinstance(username, str) or not isinstance(password, str):
return False
if not username or not password:
return False
user = get_user(username)
if user is None:
# Behoben: Dummy-Arbeit durchführen um Timing-Angriffe zu verhindern
dummy_hash = hashlib.sha256(password.encode()).hexdigest()
return False
stored_hash = user.get('password_hash')
if not stored_hash:
return False
# Behoben: Korrekt hashen und vergleichen
input_hash = hashlib.sha256(password.encode('utf-8')).hexdigest()
return hmac.compare_digest(input_hash, stored_hash)
// Behoben: Ordnungsgemäßer Byte-Vergleich ohne Null-Byte-Probleme
import java.security.MessageDigest;
import java.util.Arrays;
public class SecureAuth {
public boolean authenticate(String username, byte[] password) {
User user = userRepository.findByUsername(username);
// Behoben: Konstante-Zeit-Verhalten
byte[] storedHash = (user != null) ?
user.getPasswordHash() : getDummyHash();
// Behoben: Passwort hashen, nicht in String konvertieren
byte[] inputHash = hashPassword(password);
// Behoben: MessageDigest.isEqual für Konstante-Zeit-Vergleich verwenden
boolean valid = MessageDigest.isEqual(inputHash, storedHash);
// Sensible Daten löschen
Arrays.fill(password, (byte) 0);
Arrays.fill(inputHash, (byte) 0);
return user != null && valid;
}
// Behoben: Ordnungsgemäßer Array-Vergleich
public boolean secureCompare(char[] input, char[] stored) {
if (input == null || stored == null) {
return false;
}
// Behoben: Muss exakt gleiche Länge haben
if (input.length != stored.length) {
return false;
}
// Behoben: ALLE Zeichen vergleichen
int result = 0;
for (int i = 0; i < stored.length; i++) {
result |= input[i] ^ stored[i];
}
return result == 0;
}
private byte[] hashPassword(byte[] password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return md.digest(password);
} catch (Exception e) {
throw new RuntimeException("Hashing failed", e);
}
}
}
Die Behebung stellt vollständigen Zugangsdatenvergleich mit korrekter Initialisierung und Konstante-Zeit-Operationen sicher.
Ausgenutzt in der Praxis
Einzelzeichen-Passwortvergleich (Verschiedene Systeme, 2000-2002)
CVE-2002-1374 und CVE-2000-0979 dokumentierten Systeme, bei denen die Passwortvalidierung nur das erste Zeichen verglich, was eine Authentifizierungsumgehung mit jedem Passwort ermöglichte, das mit dem korrekten Buchstaben beginnt.
Nicht initialisierter Passwortpuffer (Enterprise-Software, 2001)
CVE-2001-0088 dokumentierte Software, bei der ein nicht initialisiertes Array dazu führte, dass Passwörter effektiv zu einzelnen Zeichen wurden, was triviale Brute-Force-Angriffe ermöglichte.
Tools zum Testen und Ausnutzen
-
Valgrind -- Speicher-Debugging-Tool zur Erkennung nicht initialisierter Variablen.
-
AddressSanitizer -- Speicherfehler-Detektor für C/C++-Programme.
-
Burp Suite -- Web-Sicherheitstool zum Testen der Authentifizierung mit verschiedenen Eingaben.
CVE-Beispiele
-
CVE-2002-1374 -- Passwort nur gegen erstes Zeichen verglichen.
-
CVE-2000-0979 -- Einzelbyte-Passwortvergleich-Umgehung.
-
CVE-2001-0088 -- Nicht initialisiertes Array schneidet Passwort auf einzelnes Zeichen.
Referenzen
-
MITRE Corporation. "CWE-305: Authentication Bypass by Primary Weakness." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/305.html
-
OWASP Foundation. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
-
CERT/CC. "Secure Coding Standards." https://wiki.sei.cmu.edu/confluence/display/seccode