Schlüsselaustausch ohne Entitätsauthentifizierung

Beschreibung

Schlüsselaustausch ohne Entitätsauthentifizierung ist eine Schwachstelle, die auftritt, wenn ein System einen kryptografischen Schlüsselaustausch mit einem Akteur durchführt, ohne dessen Identität zu verifizieren. Während die resultierende Verschlüsselung die Vertraulichkeit und Integrität der Nachrichten während der Übertragung schützen kann, gibt es keine Garantie darüber, wer am anderen Ende der Kommunikation ist. Ohne Entitätsauthentifizierung können Angreifer sich zwischen kommunizierenden Parteien positionieren, separate verschlüsselte Kanäle mit jeder Partei aufbauen und Kommunikation weiterleiten oder modifizieren - der klassische Man-in-the-Middle-Angriff. Die Verschlüsselung selbst mag stark sein, aber sie schützt eine Verbindung zur falschen Entität.

Risiko

Unauthentifizierter Schlüsselaustausch ermöglicht verheerende Man-in-the-Middle-Angriffe trotz stärker Verschlüsselung. Angreifer können sich als legitime Server ausgeben, verschlüsselte Verbindungen mit Opfern aufbauen, Anmeldedaten und sensible Daten sammeln und dann diese Anmeldedaten verwenden, um auf den echten Server zuzugreifen. Das Opfer glaubt, eine sichere, verschlüsselte Verbindung zu haben, während in Wirklichkeit alle seine Daten durch den Angreifer fließen. Dies ist besonders gefährlich, weil die Verschlüsselung ein falsches Sicherheitsgefühl erzeugt. Häufige Szenarien umfassen Diffie-Hellman-Schlüsselaustausch ohne Authentifizierung, SSL/TLS-Verbindungen, die Zertifikatsvalidierung überspringen oder ignorieren, und benutzerdefinierte Protokolle, die Verschlüsselung ohne Identitätsverifizierung implementieren. Das Risiko wird in nicht vertrauenswürdigen Netzwerken wie öffentlichem WiFi verstärkt, wo Angreifer leicht initiale Verbindungsversuche abfangen können.

Lösung

Authentifizieren Sie immer die Identität der kommunizierenden Parteien vor oder während des Schlüsselaustauschs. Für TLS/SSL-Verbindungen implementieren Sie ordnungsgemäße Zertifikatsvalidierung einschließlich Vertrauenskettenverifizierung, Hostname-Abgleich und Ablaufprüfung. Verwenden Sie Certificate Pinning für hochsichere Anwendungen, um Angriffe mit betrügerischen Zertifikaten zu verhindern. Implementieren Sie gegenseitige Authentifizierung, bei der beide Parteien die Identität des anderen verifizieren. Verwenden Sie authentifizierte Schlüsselaustauschprotokolle, die Authentifizierung an den Schlüsselaustauschprozess binden. Ignorieren Sie niemals Zertifikatsvalidierungsfehler oder deaktivieren Sie Sicherheitsprüfungen, auch nicht zum Testen. Für benutzerdefinierte Protokolle integrieren Sie digitale Signaturen oder Zertifikate in den Schlüsselaustauschprozess. Verstehen Sie, dass Verschlüsselung ohne Authentifizierung nur gegen passives Abhören schützt, nicht gegen aktive Angriffe.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Keine Authentifizierung erfolgt während des Schlüsselaustauschs, was den angenommenen Schutz durch Verschlüsselung vollständig umgeht. Angreifer können sich als beide Parteien in der Kommunikation ausgeben.
VertraulichkeitUmfang: Vertraulichkeit

Obwohl die Kommunikation verschlüsselt erscheint, können Zwischenstellen, die Man-in-the-Middle-Angriffe durchführen, alle Daten entschlüsseln, lesen und modifizieren, indem sie separate verschlüsselte Sitzungen mit jeder Partei aufbauen.

Beispielcode

Anfälliger Code (Python/Java)

Die folgenden Beispiele demonstrieren Schlüsselaustausch ohne ordnungsgemäße Entitätsauthentifizierung:

# Anfällig: TLS-Verbindung ohne Zertifikatsverifizierung
import ssl
import socket

def vulnerable_connect(hostname, port):
    # Anfällig: Kontext ohne Verifizierung erstellen
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

    # Anfällig: Zertifikatsverifizierung deaktivieren
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # Schlüsselaustausch passiert, aber wir wissen nicht mit wem!
    ssl_sock = context.wrap_socket(sock)
    ssl_sock.connect((hostname, port))

    # Angreifer könnte den Server imitieren
    return ssl_sock

# Anfällig: Diffie-Hellman ohne Authentifizierung
from cryptography.hazmat.primitives.asymmetric import dh
from cryptography.hazmat.backends import default_backend

def vulnerable_key_exchange(connection):
    # DH-Parameter generieren
    parameters = dh.generate_parameters(
        generator=2, key_size=2048, backend=default_backend()
    )

    # Privaten Schlüssel generieren
    private_key = parameters.generate_private_key()
    public_key = private_key.public_key()

    # Unseren öffentlichen Schlüssel senden
    connection.send(serialize_public_key(public_key))

    # Deren öffentlichen Schlüssel empfangen - aber von wem?
    # Anfällig: Keine Verifizierung wer das gesendet hat!
    peer_public_key_bytes = connection.recv(4096)
    peer_public_key = deserialize_public_key(peer_public_key_bytes)

    # Gemeinsames Geheimnis mit unbekannter Partei ableiten
    shared_key = private_key.exchange(peer_public_key)

    # Angreifer könnte in der Mitte sein!
    return shared_key
// Anfällig: SSL ohne Zertifikatsverifizierung
import javax.net.ssl.*;
import java.security.cert.X509Certificate;

public class VulnerableKeyExchange {

    public SSLSocket vulnerableConnect(String host, int port) throws Exception {
        // Anfällig: Trust Manager der jedes Zertifikat akzeptiert
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] certs, String type) {
                    // Anfällig: Keine Verifizierung!
                }
                public void checkServerTrusted(X509Certificate[] certs, String type) {
                    // Anfällig: Akzeptiert jedes Zertifikat!
                }
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, null);

        // Anfällig: Hostname-Verifier der jeden Hostnamen akzeptiert
        HttpsURLConnection.setDefaultHostnameVerifier(
            (hostname, session) -> true  // Gibt immer true zurück!
        );

        SSLSocketFactory factory = sslContext.getSocketFactory();

        // Schlüsselaustausch passiert mit unbekannter Entität
        return (SSLSocket) factory.createSocket(host, port);
    }

    // Anfällig: Zertifikatsfehler ignorieren
    public void vulnerableHTTPSRequest(String url) throws Exception {
        HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection();

        // Anfällig: Zertifikatsvalidierung ignorieren
        conn.setSSLSocketFactory(getInsecureSSLContext().getSocketFactory());
        conn.setHostnameVerifier((hostname, session) -> true);

        // Verschlüsselt, aber mit wem?
        conn.connect();
    }
}
// Anfällig: OpenSSL ohne Verifizierung
#include <openssl/ssl.h>
#include <openssl/err.h>

SSL* vulnerable_ssl_connect(const char *host, int port) {
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());

    // Anfällig: Verifizierung vollständig deaktivieren
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

    // Anfällig: Keine CA-Zertifikate geladen
    // SSL_CTX_set_default_verify_paths(ctx);  // Auskommentiert!

    SSL *ssl = SSL_new(ctx);

    int sock = create_socket(host, port);
    SSL_set_fd(ssl, sock);

    // Schlüsselaustausch ohne zu wissen wer am anderen Ende ist
    if (SSL_connect(ssl) != 1) {
        ERR_print_errors_fp(stderr);
        return NULL;
    }

    // Anfällig: Verifizierungsergebnis nicht prüfen
    // long result = SSL_get_verify_result(ssl);  // Ignoriert!

    return ssl;  // Mit unbekannter Entität verbunden
}

Korrigierter Code (Python/Java)

# Korrigiert: TLS-Verbindung mit ordnungsgemäßer Zertifikatsverifizierung
import ssl
import socket
import certifi

def secure_connect(hostname, port):
    # Korrigiert: Kontext mit Standard-Verifizierung erstellen
    context = ssl.create_default_context(cafile=certifi.where())

    # Korrigiert: Hostname-Verifizierung aktivieren (Standard in Python 3.7+)
    context.check_hostname = True
    context.verify_mode = ssl.CERT_REQUIRED

    # Korrigiert: Minimale TLS-Version setzen
    context.minimum_version = ssl.TLSVersion.TLSv1_2

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # Korrigiert: Hostname für SNI und Verifizierung bereitstellen
    ssl_sock = context.wrap_socket(sock, server_hostname=hostname)

    try:
        ssl_sock.connect((hostname, port))
    except ssl.CertificateError as e:
        # Korrigiert: Zertifikatsfehler nicht ignorieren
        raise SecurityError(f"Zertifikatsverifizierung fehlgeschlagen: {e}")

    # Korrigiert: Zertifikat nach Verbindung verifizieren
    cert = ssl_sock.getpeercert()
    if not cert:
        raise SecurityError("Kein Zertifikat empfangen")

    # Jetzt wissen wir, dass wir mit dem authentifizierten Server sprechen
    return ssl_sock

# Korrigiert: Authentifizierter Schlüsselaustausch mit Signaturen
from cryptography.hazmat.primitives.asymmetric import dh, rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

def secure_key_exchange(connection, peer_public_signing_key, my_signing_key):
    # DH-Parameter generieren
    parameters = dh.generate_parameters(
        generator=2, key_size=2048, backend=default_backend()
    )

    # DH-Schlüsselpaar generieren
    private_key = parameters.generate_private_key()
    public_key = private_key.public_key()

    # Korrigiert: Unseren öffentlichen Schlüssel signieren um Identität zu beweisen
    public_key_bytes = serialize_public_key(public_key)
    signature = my_signing_key.sign(
        public_key_bytes,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )

    # Signierten öffentlichen Schlüssel senden
    connection.send(public_key_bytes + b'::' + signature)

    # Signierten öffentlichen Schlüssel des Peers empfangen
    data = connection.recv(8192)
    peer_pub_bytes, peer_signature = data.split(b'::')

    # Korrigiert: Signatur des Peers verifizieren um ihn zu authentifizieren
    try:
        peer_public_signing_key.verify(
            peer_signature,
            peer_pub_bytes,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
    except Exception:
        raise SecurityError("Peer-Authentifizierung fehlgeschlagen")

    # Jetzt sicher deren öffentlichen Schlüssel zu verwenden
    peer_public_key = deserialize_public_key(peer_pub_bytes)
    shared_key = private_key.exchange(peer_public_key)

    # Wir wissen mit wem wir diesen Schlüssel abgeleitet haben
    return shared_key
// Korrigiert: SSL mit ordnungsgemäßer Zertifikatsverifizierung
import javax.net.ssl.*;
import java.security.cert.*;

public class SecureKeyExchange {

    public SSLSocket secureConnect(String host, int port) throws Exception {
        // Korrigiert: Standard-TrustManager mit Systemzertifikaten verwenden
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null);  // Verwendet System-Truststore

        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, tmf.getTrustManagers(), null);

        SSLSocketFactory factory = sslContext.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);

        // Korrigiert: Hostname für Verifizierung setzen
        SSLParameters params = socket.getSSLParameters();
        params.setEndpointIdentificationAlgorithm("HTTPS");
        socket.setSSLParameters(params);

        // Handshake mit Verifizierung
        socket.startHandshake();

        // Korrigiert: Prüfen ob Sitzung gültig ist
        SSLSession session = socket.getSession();
        if (!session.isValid()) {
            throw new SecurityException("Ungültige SSL-Sitzung");
        }

        // Korrigiert: Zusätzliche Zertifikatsprüfungen falls nötig
        X509Certificate[] certs = (X509Certificate[]) session.getPeerCertificates();
        verifyCertificateChain(certs, host);

        return socket;
    }

    private void verifyCertificateChain(X509Certificate[] certs, String host)
            throws CertificateException {

        if (certs == null || certs.length == 0) {
            throw new CertificateException("Keine Zertifikate empfangen");
        }

        // Korrigiert: Zertifikatsgültigkeit prüfen
        for (X509Certificate cert : certs) {
            cert.checkValidity();
        }

        // Korrigiert: Hostname-Übereinstimmung mit Zertifikat verifizieren
        X509Certificate serverCert = certs[0];
        // Hostname-Verifizierung passiert automatisch mit
        // setEndpointIdentificationAlgorithm("HTTPS")
    }
}

Die Korrektur implementiert ordnungsgemäße Zertifikatsverifizierung und Entitätsauthentifizierung während des Schlüsselaustauschs.


Ausgenutzt in der Praxis

SSL/TLS-Zertifikats-Bypass (Verschiedene Anwendungen, laufend)

Zahlreiche Anwendungen wurden entdeckt, die Zertifikatsverifizierung deaktivieren oder unsachgemäß implementieren, was Man-in-the-Middle-Angriffe gegen verschlüsselte Verbindungen ermöglicht.

Mobile App Certificate Pinning Bypass (Mobile Anwendungen, 2016)

Mehrere mobile Banking- und Finanzanwendungen wurden gefunden, die Zertifikatsvalidierung überspringen, was Angreifern ermöglichte, verschlüsselte Finanztransaktionen abzufangen.


Tools zum Testen/Ausnutzen

  • mitmproxy — HTTPS-Proxy zum Abfangen von Verbindungen mit unsachgemäßer Zertifikatsvalidierung.

  • SSLstrip — Tool zum Ausnutzen von SSL/TLS-Downgrade-Schwachstellen.

  • Burp Suite — Web-Sicherheitstool zum Testen der Zertifikatsvalidierung.


CVE-Beispiele

  • CVE-2014-0160 — Heartbleed-Schwachstelle ermöglicht Schlüsselextraktion.

  • CVE-2009-3555 — TLS-Renegotiation-Angriff ermöglicht Injektion.


Referenzen

  1. MITRE Corporation. "CWE-322: Key Exchange without Entity Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/322.html

  2. OWASP Foundation. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html

  3. RFC 5246. "The Transport Layer Security (TLS) Protocol Version 1.2." https://tools.ietf.org/html/rfc5246