Unsachgemäße Validierung von Zertifikaten mit Host-Diskrepanz
Beschreibung
Unsachgemäße Validierung von Zertifikaten mit Host-Diskrepanz ist eine Schwachstelle, die auftritt, wenn ein Produkt Zertifikate akzeptiert, ohne zu verifizieren, dass sie zum beabsichtigten Host gehören. Ein Zertifikat kann vollständig gültig sein, ordnungsgemäß von einer vertrauenswürdigen CA signiert und nicht abgelaufen, aber für eine andere Domain als diejenige ausgestellt sein, mit der verbunden wird. Ohne Hostname-Validierung können Angreifer legitime Zertifikate für ihre eigenen Domains erhalten und sie während Man-in-the-Middle-Angriffen oder Umleitungsszenarien verwenden. Das Produkt muss die host-spezifischen Daten des Zertifikats validieren, insbesondere den Common Name (CN) im Subject-Feld oder, vorzugsweise, die Subject Alternative Name (SAN)-Erweiterung.
Risiko
Fehlende Hostname-Validierung macht den gesamten Zertifikatsverifikationsprozess gegen gezielte Angriffe nahezu nutzlos. Angreifer können leicht gültige Zertifikate von Zertifizierungsstellen für Domains erhalten, die sie kontrollieren. Wenn die Anwendung eines Opfers sich mit einem vermeintlich vertrauenswürdigen Server verbindet, präsentiert ein Man-in-the-Middle-Angreifer sein legitimes Zertifikat. Ohne Hostname-Prüfung akzeptiert die Anwendung dieses Zertifikat, weil es technisch gültig ist, was dem Angreifer ermöglicht, den gesamten Verkehr abzufangen. CVE-2012-5810 demonstrierte echte finanzielle Verluste durch eine Mobile-Banking-Anwendung, die Hostnamen nicht verifizierte. Implementierungsfehler wie unsachgemäße Null-Byte-Behandlung können ebenfalls die Hostname-Validierung aushebeln, selbst wenn sie versucht wird.
Lösung
Validieren Sie immer, dass der Hostname des Zertifikats mit dem erwarteten Server-Hostname übereinstimmt. Prüfen Sie sowohl den Common Name (CN) im Subject-Feld als auch die Subject Alternative Name (SAN)-Erweiterung - moderne Zertifikate verwenden SAN, und RFC 6125 empfiehlt, SAN bevorzugt zu prüfen. Verwenden Sie TLS-Bibliotheksfunktionen, die für Hostname-Validierung konzipiert sind, anstatt benutzerdefinierte Prüfungen zu implementieren. Behandeln Sie Randfälle einschließlich Wildcards, internationalisierten Domain-Namen (IDN) und Null-Bytes in Zertifikatsnamen. Bei Verwendung von Certificate Pinning validieren Sie alle Zertifikatseigenschaften einschließlich Hostname bevor Pinning stattfindet. Kommunizieren Sie Verifikationsergebnisse klar an Benutzer, damit sie verstehen, wenn Verbindungen möglicherweise unsicher sind. Testen Sie mit Zertifikaten, die für verschiedene Hostnamen gültig sind, um sicherzustellen, dass Hostname-Validierung funktioniert.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle, Authentifizierung | Bereich: Zugriffskontrolle, Authentifizierung Daten vom validierten System können tatsächlich vom Host eines Angreifers stammen, der ein gültiges Zertifikat für eine andere Domain präsentiert. Dies ermöglicht Identitätsfälschung und Man-in-the-Middle-Angriffe, bei denen Angreifer alle Kommunikationen abfangen, lesen und modifizieren können. |
Beispielcode
Anfälliger Code (C/OpenSSL)
Die folgenden Beispiele demonstrieren unsachgemäße Hostname-Validierung:
// Anfällig: Fehlende Hostname-Verifikation
#include <openssl/ssl.h>
#include <openssl/x509.h>
int vulnerable_verify_cert(SSL *ssl, const char *hostname) {
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert && (SSL_get_verify_result(ssl) == X509_V_OK)) {
// Anfällig: Zertifikatskette validiert, aber Hostname NICHT geprüft!
// Angreifer kann JEDES gültige Zertifikat verwenden
return 1; // "Vertrauenswürdig" - aber für falschen Host!
}
return 0;
}
void vulnerable_connect(const char *hostname, int port) {
SSL *ssl = create_ssl_connection(hostname, port);
// Prüft nur ob Zertifikat gültig ist, nicht ob es für diesen Host ist
if (SSL_get_verify_result(ssl) == X509_V_OK) {
// Fährt ohne Hostname-Prüfung fort
send_sensitive_data(ssl);
}
}
# Anfällig: Python mit deaktivierter Hostname-Prüfung
import ssl
import socket
def vulnerable_connect(hostname, port):
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
context.load_default_certs()
# Anfällig: Hostname-Prüfung explizit deaktiviert
context.check_hostname = False
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(sock) # Fehlender server_hostname-Parameter!
ssl_sock.connect((hostname, port))
# Zertifikatskette validiert, aber Hostname NICHT verifiziert
return ssl_sock
def vulnerable_requests():
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# Anfällig: Benutzerdefinierter Adapter ignoriert Hostname
# Manche Implementierungen tun dies
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# Oder benutzerdefinierter SSL-Kontext ohne Hostname-Prüfung
return requests.get('https://example.com', verify=True)
// Anfällig: Java mit permissivem Hostname-Verifier
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
public class VulnerableHostnameCheck {
public static void vulnerable_setup() throws Exception {
// Anfällig: HostnameVerifier der alle Hostnamen akzeptiert
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> true // Gibt immer true zurück!
);
// Jetzt wird jedes Zertifikat für jeden Hostname akzeptiert
URL url = new URL("https://secure.example.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// Angreifer kann mit Zertifikat für "evil.attacker.com" abfangen
}
public void vulnerable_connect(String targetHost) throws Exception {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, getDefaultTrustManagers(), null);
SSLSocketFactory factory = ctx.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket(targetHost, 443);
socket.startHandshake();
// Anfällig: Holt Zertifikat aber prüft nicht Hostname
SSLSession session = socket.getSession();
X509Certificate[] certs = (X509Certificate[]) session.getPeerCertificates();
// Zertifikat ist gültig aber könnte für anderen Host sein!
sendSensitiveData(socket);
}
}
Korrigierter Code (C/OpenSSL)
// Korrigiert: Ordnungsgemäße Hostname-Verifikation
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
int secure_verify_cert(SSL *ssl, const char *expected_hostname) {
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert == NULL) {
fprintf(stderr, "Kein Zertifikat präsentiert\n");
return 0;
}
// Zertifikatskettenvalidierung prüfen
long verify_result = SSL_get_verify_result(ssl);
if (verify_result != X509_V_OK) {
fprintf(stderr, "Zertifikatsverifikation fehlgeschlagen: %s\n",
X509_verify_cert_error_string(verify_result));
X509_free(cert);
return 0;
}
// Korrigiert: Hostname stimmt mit Zertifikat überein
// X509_check_host behandelt CN, SAN, Wildcards und Null-Bytes
int hostname_match = X509_check_host(cert, expected_hostname,
strlen(expected_hostname),
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
NULL);
X509_free(cert);
if (hostname_match != 1) {
fprintf(stderr, "Hostname-Verifikation fehlgeschlagen für: %s\n",
expected_hostname);
return 0;
}
return 1; // Zertifikat vollständig validiert einschließlich Hostname
}
// Alternative: Verwendung von SSL_set1_host für automatische Verifikation
SSL* secure_connect_with_auto_verify(SSL_CTX *ctx, const char *hostname, int port) {
SSL *ssl = SSL_new(ctx);
// Erwarteten Hostname für automatische Verifikation setzen
if (!SSL_set1_host(ssl, hostname)) {
fprintf(stderr, "Konnte Hostname für Verifikation nicht setzen\n");
SSL_free(ssl);
return NULL;
}
// Verbinden und Handshake
BIO *bio = BIO_new_ssl_connect(ctx);
BIO_set_conn_hostname(bio, hostname);
BIO_set_conn_port(bio, "443");
if (BIO_do_connect(bio) <= 0) {
fprintf(stderr, "Verbindung fehlgeschlagen\n");
return NULL;
}
// Hostname-Verifikation erfolgt automatisch während Handshake
return ssl;
}
# Korrigiert: Python mit ordnungsgemäßer Hostname-Verifikation
import ssl
import socket
import certifi
def secure_connect(hostname, port):
context = ssl.create_default_context(cafile=certifi.where())
# Korrigiert: Hostname-Verifikation ist aktiviert (Standard in Python 3.7+)
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Korrigiert: server_hostname für SNI und Hostname-Verifikation bereitstellen
ssl_sock = context.wrap_socket(sock, server_hostname=hostname)
try:
ssl_sock.connect((hostname, port))
except ssl.CertificateError as e:
# Dies schließt Hostname-Diskrepanzen ein
print(f"Zertifikatsfehler (möglicherweise Hostname-Diskrepanz): {e}")
raise
return ssl_sock
def secure_requests():
import requests
# Korrigiert: verify=True aktiviert sowohl Ketten- als auch Hostname-Validierung (Standard)
response = requests.get('https://example.com', verify=True)
# Oder mit explizitem CA-Bundle
response = requests.get('https://example.com', verify=certifi.where())
return response
// Korrigiert: Java mit ordnungsgemäßer Hostname-Verifikation
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
public class SecureHostnameCheck {
public static void secure_setup() throws Exception {
// Korrigiert: Standard-HostnameVerifier verwenden (validiert Hostname)
// Nicht mit permissivem Verifier überschreiben
// HttpsURLConnection verwendet standardmäßig HTTPS-Hostname-Verifier
}
public void secure_connect(String targetHost, int port) throws Exception {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, getDefaultTrustManagers(), null);
SSLSocketFactory factory = ctx.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket(targetHost, port);
// Korrigiert: Erwarteten Hostname für Verifikation setzen
SSLParameters params = socket.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS"); // Aktiviert Hostname-Prüfung
socket.setSSLParameters(params);
socket.startHandshake();
// Korrigiert: Hostname wird automatisch während Handshake verifiziert
// Bei Diskrepanz wird SSLException geworfen
sendSensitiveData(socket);
}
// Alternative mit HttpsURLConnection
public void secure_https_connect(String url) throws Exception {
URL target = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) target.openConnection();
// Standard-Hostname-Verifier validiert gegen Zertifikat
// Nur überschreiben wenn STRENGERE Prüfung nötig, nie permissiver
conn.connect();
// Hostname automatisch verifiziert
}
}
Die Korrektur stellt sicher, dass Hostname-Verifikation aktiviert ist und der Hostname des Zertifikats (CN oder SAN) mit dem erwarteten Server übereinstimmt.
Ausgenutzt in der Praxis
Mobile-Banking-Hostname-Umgehung (Banking-Apps, 2012)
CVE-2012-5810 dokumentierte eine Mobile-Banking-Anwendung, die Hostnamen in TLS-Zertifikaten nicht verifizierte, was echte finanzielle Verluste verursachte, wenn Benutzer sich über kompromittierte Netzwerke verbanden und Angreifer Banking-Transaktionen abfingen.
Null-Byte in Zertifikatsname (Browser, 2009)
CVE-2009-2408 dokumentierte Browser, die Null-Bytes in Common-Name-Feldern nicht handhaben könnten, was Zertifikaten wie "www.bank.com\0.attacker.com" ermöglichte, die Hostname-Validierung für "www.bank.com" zu bestehen.
Python-Bibliothek Hostname-Regex (Python-Bibliotheken, 2012)
CVE-2012-3446 dokumentierte eine Python-Bibliothek, die fehlerhafte Regex für Hostname-Matching verwendete, was Umgehungen durch manipulierte Zertifikatsnamen ermöglichte.
Tools zum Testen/Ausnutzen
-
testssl.sh — Kommandozeilentool zum Testen von SSL/TLS einschließlich Hostname-Verifikation.
-
mitmproxy — HTTPS-Proxy zum Testen mit Zertifikaten für verschiedene Hostnamen.
-
badssl.com — Testseite mit verschiedenen Zertifikatsfehlerszenarien einschließlich Hostname-Diskrepanzen.
CVE-Beispiele
-
CVE-2009-2408 — Browser behandelt Null-Bytes im Common Name nicht, ermöglicht HTTPS-Spoofing.
-
CVE-2012-5810 — Mobile-Banking-App verifiziert Hostname nicht, verursacht finanzielle Verluste.
-
CVE-2012-3446 — Python-Bibliothek verwendet fehlerhafte Regex für Hostname-Matching.
-
CVE-2012-0867 — Datenbankprogramm kürzt Common Name während Verifikation.
-
CVE-2003-0355 — Webbrowser validiert Common Name nicht.
Referenzen
-
MITRE Corporation. "CWE-297: Improper Validation of Certificate with Host Mismatch." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/297.html
-
RFC 6125. "Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates." https://tools.ietf.org/html/rfc6125
-
OWASP Foundation. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html