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
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: 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. |
| Vertraulichkeit | Umfang: 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
-
MITRE Corporation. "CWE-322: Key Exchange without Entity Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/322.html
-
OWASP Foundation. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html
-
RFC 5246. "The Transport Layer Security (TLS) Protocol Version 1.2." https://tools.ietf.org/html/rfc5246