Fehlerhafte Implementierung eines Authentifizierungsalgorithmus

Beschreibung

Fehlerhafte Implementierung eines Authentifizierungsalgorithmus ist eine Schwachstelle, die auftritt, wenn die Anforderungen eines Produkts die Verwendung eines etablierten Authentifizierungsalgorithmus vorschreiben, die tatsächliche Implementierung jedoch Fehler enthält, die eine Umgehung der Authentifizierung ermöglichen. Dies unterscheidet sich von der Wahl eines schwachen Algorithmus -- hier kann der Algorithmus selbst solide sein, aber Implementierungsfehler führen Schwachstellen ein. Häufige Fehler umfassen falsche Bedingungsoperatoren (Verwendung von OR statt AND), fehlende Validierungsschritte, Race Conditions bei Authentifizierungsprüfungen, unsachgemäße Fehlerbehandlung mit Standard-Erfolg und Logikfehler, die bestimmten Codepfaden das Überspringen der Authentifizierung ermöglichen.

Risiko

Fehlerhafte Implementierung von Authentifizierungsalgorithmen erzeugt schwerwiegende Sicherheitsrisiken, da die Schwachstelle trotz Wahl eines angemessenen Authentifizierungsmechanismus besteht. Diese Fehler sind besonders gefährlich, da Code-Reviews sich möglicherweise auf die Algorithmusauswahl statt auf Implementierungsdetails konzentrieren und Standard-Sicherheitstests möglicherweise keine Randfälle aufdecken, in denen die fehlerhafte Logik ausgenutzt werden kann. Apples "goto fail"-Fehler veranschaulicht dieses Risiko -- eine einfache duplizierte Anweisung verursachte eine vollständige Authentifizierungsumgehung, die Millionen von Geräten betraf. Implementierungsfehler können Angreifern ermöglichen, sich ohne gültige Zugangsdaten zu authentifizieren, andere Benutzer zu imitieren oder Rechte zu eskalieren. Das Risiko wird verstärkt, wenn dieselbe fehlerhafte Implementierung in mehreren Komponenten oder Produkten verwendet wird.

Lösung

Implementieren Sie Authentifizierungsalgorithmen mit strenger Aufmerksamkeit für Korrektheit und folgen Sie etablierten Implementierungsmustern. Verwenden Sie gut getestete Authentifizierungsbibliotheken anstatt Algorithmen von Grund auf zu implementieren. Implementieren Sie umfassende Unit-Tests, die alle Authentifizierungspfade einschließlich Fehlerbedingungen und Randfälle abdecken. Führen Sie gründliche Code-Reviews durch, die speziell auf Authentifizierungslogik fokussiert sind und auf Bedingungsoperatoren, frühe Returns und Ausnahmebehandlung achten. Verwenden Sie statische Analyse-Tools zur Erkennung von Logikfehlern und unerreichbarem Code. Wenden Sie formale Verifikationsmethoden für kritische Authentifizierungskomponenten an, wo machbar. Implementieren Sie Defense-in-Depth mit mehreren Authentifizierungsprüfungen auf verschiedenen Ebenen. Folgen Sie dem Prinzip des sicheren Scheiterns -- im Zweifel verweigern Sie den Zugang anstatt ihn zu gewähren.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Implementierungsfehler können die Authentifizierung vollständig umgehen und Angreifern unautorisierten Zugang ohne gültige Zugangsdaten ermöglichen. Die beabsichtigte Sicherheit des Authentifizierungsalgorithmus wird aufgehoben.
Integrität, VertraulichkeitBereich: Integrität, Vertraulichkeit

Bei umgangener Authentifizierung können Angreifer auf sensible Daten zugreifen, sie ändern oder exfiltrieren, unautorisierte Aktionen ausführen und die Integrität des gesamten Systems kompromittieren.

Beispielcode und Lösung

Verwundbarer Code (C)

Die folgenden Beispiele zeigen fehlerhafte Implementierungen von Authentifizierungsalgorithmen:

// Verwundbar: "goto fail"-Fehler im Apple-Stil
#include <openssl/ssl.h>
#include <stdbool.h>

int vulnerable_verify_signature(SSL *ssl, const unsigned char *signature) {
    int err = 0;

    // Schritt 1: Zertifikatskette verifizieren
    err = verify_cert_chain(ssl);
    if (err != 0) {
        goto fail;
    }

    // Schritt 2: Signaturalgorithmus verifizieren
    err = verify_signature_algorithm(ssl);
    if (err != 0) {
        goto fail;
    }
    goto fail;  // Verwundbar: Doppeltes goto - überspringt verbleibende Prüfungen!

    // Schritt 3: Tatsächliche Signatur verifizieren (WIRD NIEMALS Ausgeführt!)
    err = verify_signature_data(ssl, signature);
    if (err != 0) {
        goto fail;
    }

    // Schritt 4: Zertifikatgültigkeit prüfen
    err = verify_cert_validity(ssl);
    if (err != 0) {
        goto fail;
    }

    return 0;  // Erfolg - aber Signatur wurde nie verifiziert!

fail:
    return err;
}

// Verwundbar: Falscher Operator in der Bedingung
bool vulnerable_authenticate(const char *username, const char *password) {
    User *user = lookup_user(username);

    if (user == NULL) {
        return false;
    }

    bool password_correct = verify_password(password, user->password_hash);
    bool account_active = user->status == ACTIVE;
    bool not_locked = user->failed_attempts < MAX_ATTEMPTS;

    // Verwundbar: OR statt AND
    // Sollte ALLE Bedingungen als wahr erfordern
    if (password_correct || account_active || not_locked) {
        return true;  // Angreifer kann mit nur einer Bedingung umgehen!
    }

    return false;
}
# Verwundbar: Fehlendes Return nach gescheiterter Prüfung
def vulnerable_auth_check(request):
    token = request.headers.get('Authorization')

    if not token:
        # Verwundbar: Fehlendes Return!
        log_error("No token provided")
        # Ausführung geht ohne Authentifizierung weiter

    # Dieser Code wird auch ohne Token ausgeführt
    user = get_user_from_token(token)  # Schlägt fehl, stoppt aber nicht die Ausführung

    # Verwundbar: Keine Prüfung, ob user None ist
    return process_authenticated_request(user)

# Verwundbar: Race Condition bei der Authentifizierung
import threading

authenticated_users = {}

def vulnerable_login(username, password):
    # Passwort prüfen
    if not verify_password(username, password):
        return False

    # Verwundbar: Zeitlücke zwischen Prüfung und Nutzung
    # Ein anderer Thread könnte authenticated_users ändern
    authenticated_users[username] = True
    return True

def vulnerable_access_resource(username, resource):
    # Verwundbar: Race Condition - Status könnte sich ändern
    if username in authenticated_users:
        # Zwischen Prüfung und Nutzung könnte Benutzer abgemeldet worden sein
        return get_resource(resource)
    return None
// Verwundbar: Ausnahmebehandlung mit Standard-Erfolg
public class VulnerableAuth {

    public boolean authenticate(String username, String password) {
        try {
            User user = userRepository.findByUsername(username);

            if (user == null) {
                throw new UserNotFoundException();
            }

            if (!passwordEncoder.matches(password, user.getPasswordHash())) {
                throw new InvalidPasswordException();
            }

            // Zusätzliche Prüfungen...
            verifyAccountStatus(user);
            verify2FA(user);

            return true;

        } catch (UserNotFoundException e) {
            return false;
        } catch (InvalidPasswordException e) {
            return false;
        } catch (Exception e) {
            // Verwundbar: Generischer Catch fängt alles ab
            // Einschließlich Fehler, die den Zugang verweigern sollten!
            log.error("Auth error: " + e.getMessage());
            return true;  // GEFÄHRLICH: Standard ist authentifiziert!
        }
    }

    // Verwundbar: Logikfehler bei Multi-Faktor-Prüfung
    public boolean verifyMFA(User user, String code) {
        boolean totpValid = verifyTOTP(user, code);
        boolean smsValid = verifySMS(user, code);
        boolean emailValid = verifyEmailCode(user, code);

        // Verwundbar: Sollte die KONFIGURIERTE Methode als gültig erfordern
        // Erlaubt stattdessen JEDE Methode
        return totpValid || smsValid || emailValid;
    }
}

Sichere Lösung (C)

// Behoben: Korrekte Signaturverifizierung ohne doppeltes goto
#include <openssl/ssl.h>
#include <stdbool.h>

int secure_verify_signature(SSL *ssl, const unsigned char *signature) {
    int err = 0;

    // Schritt 1: Zertifikatskette verifizieren
    err = verify_cert_chain(ssl);
    if (err != 0) {
        log_error("Certificate chain verification failed");
        return err;
    }

    // Schritt 2: Signaturalgorithmus verifizieren
    err = verify_signature_algorithm(ssl);
    if (err != 0) {
        log_error("Signature algorithm verification failed");
        return err;
    }

    // Behoben: Kein doppeltes goto, Signaturverifizierung wird ausgeführt
    // Schritt 3: Tatsächliche Signatur verifizieren
    err = verify_signature_data(ssl, signature);
    if (err != 0) {
        log_error("Signature data verification failed");
        return err;
    }

    // Schritt 4: Zertifikatgültigkeit prüfen
    err = verify_cert_validity(ssl);
    if (err != 0) {
        log_error("Certificate validity check failed");
        return err;
    }

    // Alle Prüfungen bestanden
    return 0;
}

// Behoben: Korrekter Operator erfordert ALLE Bedingungen
bool secure_authenticate(const char *username, const char *password) {
    User *user = lookup_user(username);

    if (user == NULL) {
        // Behoben: Konstante-Zeit-Vergleich zur Verhinderung von Timing-Angriffen
        dummy_password_check();
        return false;
    }

    bool password_correct = verify_password(password, user->password_hash);
    bool account_active = user->status == ACTIVE;
    bool not_locked = user->failed_attempts < MAX_ATTEMPTS;

    // Behoben: AND-Operator erfordert ALLE Bedingungen
    if (password_correct && account_active && not_locked) {
        reset_failed_attempts(user);
        return true;
    }

    // Behoben: Fehlversuche bei Misserfolg erhöhen
    if (!password_correct) {
        increment_failed_attempts(user);
    }

    return false;
}
# Behoben: Korrekte Return-Anweisungen und Null-Prüfungen
def secure_auth_check(request):
    token = request.headers.get('Authorization')

    if not token:
        log_error("No token provided")
        return unauthorized_response()  # Behoben: Kehrt sofort zurück

    try:
        user = get_user_from_token(token)
    except TokenExpiredError:
        return unauthorized_response("Token expired")
    except InvalidTokenError:
        return unauthorized_response("Invalid token")

    # Behoben: Explizite Null-Prüfung
    if user is None:
        return unauthorized_response("User not found")

    # Behoben: Prüfen, ob Benutzer noch aktiv ist
    if not user.is_active:
        return unauthorized_response("Account disabled")

    return process_authenticated_request(user)

# Behoben: Thread-sichere Authentifizierung
import threading
from contextlib import contextmanager

class SecureSessionManager:
    def __init__(self):
        self._sessions = {}
        self._lock = threading.RLock()

    def login(self, username, password):
        if not verify_password(username, password):
            return None

        with self._lock:
            session_id = generate_secure_session_id()
            self._sessions[session_id] = {
                'username': username,
                'created_at': time.time(),
                'valid': True
            }
            return session_id

    def access_resource(self, session_id, resource):
        with self._lock:  # Behoben: Atomare Prüfung und Zugriff
            session = self._sessions.get(session_id)

            if session is None or not session['valid']:
                return None

            if self._is_session_expired(session):
                del self._sessions[session_id]
                return None

            # Behoben: Zugriff erfolgt unter Halte des Locks
            return get_resource(resource, session['username'])
// Behoben: Korrekte Ausnahmebehandlung scheitert sicher
public class SecureAuth {

    public boolean authenticate(String username, String password) {
        try {
            User user = userRepository.findByUsername(username);

            if (user == null) {
                // Behoben: Konstante-Zeit zur Verhinderung von Enumeration
                performDummyPasswordCheck();
                return false;
            }

            if (!passwordEncoder.matches(password, user.getPasswordHash())) {
                user.incrementFailedAttempts();
                userRepository.save(user);
                return false;
            }

            // Zusätzliche Prüfungen mit explizitem Scheitern
            if (!verifyAccountStatus(user)) {
                return false;
            }

            if (!verify2FA(user)) {
                return false;
            }

            // Alle Prüfungen bestanden
            user.resetFailedAttempts();
            userRepository.save(user);
            return true;

        } catch (Exception e) {
            // Behoben: Jede Ausnahme bedeutet Authentifizierungsfehler
            log.error("Auth error (denied): " + e.getMessage());
            return false;  // Sicherer Standard: Zugang verweigern
        }
    }

    // Behoben: Die konfigurierte MFA-Methode des Benutzers verifizieren
    public boolean verifyMFA(User user, String code) {
        MFAMethod configuredMethod = user.getMfaMethod();

        // Behoben: Nur die konfigurierte Methode prüfen
        switch (configuredMethod) {
            case TOTP:
                return verifyTOTP(user, code);
            case SMS:
                return verifySMS(user, code);
            case EMAIL:
                return verifyEmailCode(user, code);
            case NONE:
                return true;  // MFA nicht erforderlich
            default:
                log.warn("Unknown MFA method");
                return false;  // Sicher scheitern
        }
    }
}

Die Behebung stellt sicher, dass alle Authentifizierungsschritte korrekt mit ordnungsgemäßer Operatorlogik und sicher scheiternder Ausnahmebehandlung ausgeführt werden.


Ausgenutzt in der Praxis

Apple SSL "goto fail"-Fehler (iOS/macOS, 2014)

CVE-2014-1266 dokumentierte einen kritischen Implementierungsfehler in Apples SSL/TLS-Code, bei dem eine duplizierte "goto fail"-Anweisung dazu führte, dass die Signaturverifizierung vollständig umgangen wurde, was Man-in-the-Middle-Angriffe auf die gesamte verschlüsselte Kommunikation ermöglichte.

Fehler in der Bedingungslogik (Verschiedene Anwendungen)

CVE-2003-0750 dokumentierte eine Authentifizierungsumgehung aufgrund der Verwendung von 'or' anstelle von 'and'-Operatoren in Bedingungsanweisungen, was eine Authentifizierung mit nur teilweiser Zugangsdaten-Verifizierung ermöglichte.


Tools zum Testen und Ausnutzen


CVE-Beispiele

  • CVE-2014-1266 -- Apple SSL "goto fail"-Fehler umgeht Signaturverifizierung.

  • CVE-2003-0750 -- OR statt AND-Operator in Authentifizierungslogik.

  • CVE-2008-0166 -- Debian OpenSSL schwache Zufallszahlengenerierung.


Referenzen

  1. MITRE Corporation. "CWE-303: Incorrect Implementation of Authentication Algorithm." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/303.html

  2. OWASP Foundation. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

  3. Wheeler, D. "Secure Programming HOWTO." https://dwheeler.com/secure-programs/