Fehlende Validierung des OpenSSL-Zertifikats

Beschreibung

Fehlende Validierung des OpenSSL-Zertifikats tritt auf, wenn eine Anwendung, die OpenSSL verwendet, SSL/TLS-Zertifikate während sicherer Verbindungen nicht ordnungsgemäß verifiziert. Dies umfasst das Versäumnis, die Zertifikatskette zu prüfen, nicht zu verifizieren, dass der Hostname mit dem Common Name oder den Subject Alternative Names des Zertifikats übereinstimmt, das Akzeptieren abgelaufener Zertifikate oder das Nicht-Prüfen des Zertifikats-Widerrufsstatus. Die Anwendung stellt Verbindungen ohne ordnungsgemäße Identitätsverifikation her.

Risiko

Ohne Zertifikatsvalidierung können Angreifer Man-in-the-Middle-Angriffe mit selbstsignierten oder unsachgemäß ausgestellten Zertifikaten durchführen. Verbindungen, die als sicher angenommen werden, bieten tatsächlich keine Authentifizierung des entfernten Servers. Sensible Daten (Credentials, persönliche Informationen, Finanzdaten), die über diese Verbindungen übertragen werden, können abgefangen und gelesen werden. Die Anwendung kann sich mit bösartigen Servern verbinden, die legitime Dienste imitieren.

Lösung

Aktivieren Sie Zertifikatsverifizierung mit SSL_CTX_set_verify() und SSL_VERIFY_PEER. Laden Sie vertrauenswürdige CA-Zertifikate mit SSL_CTX_load_verify_locations(). Verifizieren Sie, dass der Hostname mit dem Zertifikat übereinstimmt, mit SSL_set1_host() oder manueller Verifikation. Prüfen Sie SSL_get_verify_result() nach der Verbindung. Erwägen Sie die Prüfung des Zertifikats-Widerrufsstatus via OCSP oder CRLs. Verwenden Sie moderne TLS-Versionen und deaktivieren Sie unsichere Protokolle.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Datenabfangen

Angreifer können verschlüsselte Kommunikation abfangen.
AuthentifizierungBereich: Server-Imitation

Kann nicht verifizieren, dass mit beabsichtigtem Server verbunden.
IntegritätBereich: Datenmanipulation

MITM-Angreifer können Daten während der Übertragung modifizieren.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Keine Zertifikatsverifizierung
SSL_CTX* create_context_vulnerable() {
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());

    // Keine Verifizierung gesetzt - akzeptiert JEDES Zertifikat!
    // SSL_CTX_set_verify nicht aufgerufen

    return ctx;
}

void connect_vulnerable(const char* host, int port) {
    SSL_CTX* ctx = create_context_vulnerable();
    SSL* ssl = SSL_new(ctx);

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

    // Verbindet ohne Server-Identität zu verifizieren!
    if (SSL_connect(ssl) <= 0) {
        handle_error();
        return;
    }

    // MITM-Angreifer kann jedes Zertifikat präsentieren
    send_sensitive_data(ssl);
}

// VERWUNDBAR: Verifizierung deaktiviert
SSL_CTX* create_context_insecure() {
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());

    // Deaktiviert explizit Verifizierung!
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

    return ctx;
}

// VERWUNDBAR: Verifizierung aktiviert aber keine CA geladen
SSL_CTX* create_context_no_ca() {
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());

    // Verifizierung angefordert, wird aber immer fehlschlagen
    // weil keine vertrauenswürdigen CAs geladen sind
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    // Fehlt: SSL_CTX_load_verify_locations()

    return ctx;
}

// VERWUNDBAR: Keine Hostname-Verifizierung
void connect_no_hostname_check(SSL_CTX* ctx, const char* host, int port) {
    SSL* ssl = SSL_new(ctx);
    int fd = create_socket(host, port);
    SSL_set_fd(ssl, fd);

    if (SSL_connect(ssl) > 0) {
        // Zertifikat könnte gültig sein, aber für anderen Host!
        // Angreifer mit jedem gültigen Zertifikat kann abfangen
        if (SSL_get_verify_result(ssl) == X509_V_OK) {
            // FEHLER: Hat nicht verifiziert, dass Hostname mit Zertifikat übereinstimmt
            send_data(ssl);
        }
    }
}

// VERWUNDBAR: Verifizierungsergebnis ignorieren
void connect_ignore_result(SSL_CTX* ctx, const char* host, int port) {
    SSL* ssl = SSL_new(ctx);
    int fd = create_socket(host, port);
    SSL_set_fd(ssl, fd);

    SSL_connect(ssl);  // Kann Verifizierung fehlschlagen

    // FEHLER: Hat nie SSL_get_verify_result() geprüft!
    send_sensitive_data(ssl);
}
# VERWUNDBAR: Python mit deaktivierter Zertifikatsverifizierung
import ssl
import urllib.request

# VERWUNDBAR: Alle Zertifikatsprüfungen deaktivieren
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

response = urllib.request.urlopen(url, context=context)

# VERWUNDBAR: Verwendung veralteter/unsicherer Methoden
import requests

# Deaktiviert Zertifikatsverifizierung!
response = requests.get(url, verify=False)

# VERWUNDBAR: Socket ohne Verifizierung
import socket
import ssl

def connect_vulnerable(host, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))

    # Keine Zertifikatsverifizierung
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    ssl_sock = context.wrap_socket(sock)
    return ssl_sock
// VERWUNDBAR: Java TrustManager, der alle Zertifikate akzeptiert
public class VulnerableTrustManager implements X509TrustManager {

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {
        // Tut nichts - akzeptiert alle Client-Zertifikate
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {
        // Tut nichts - akzeptiert alle Server-Zertifikate!
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

// VERWUNDBAR: Verwendung des unsicheren Trust-Managers
public class VulnerableHttpClient {

    public static HttpsURLConnection createConnection(String urlStr)
            throws Exception {
        TrustManager[] trustAll = new TrustManager[] {
            new VulnerableTrustManager()
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAll, new SecureRandom());

        HttpsURLConnection.setDefaultSSLSocketFactory(
            sslContext.getSocketFactory()
        );

        // Ebenfalls verwundbar: Hostname-Verifizierung deaktivieren
        HttpsURLConnection.setDefaultHostnameVerifier(
            (hostname, session) -> true  // Akzeptiert jeden Hostname!
        );

        URL url = new URL(urlStr);
        return (HttpsURLConnection) url.openConnection();
    }
}

Lösungscode

// SICHER: Ordnungsgemäße Zertifikatsverifizierung
SSL_CTX* create_context_safe() {
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
    if (ctx == NULL) {
        return NULL;
    }

    // Zertifikatsverifizierung aktivieren
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    // Vertrauenswürdige CA-Zertifikate laden
    if (!SSL_CTX_load_verify_locations(ctx, "/etc/ssl/certs/ca-certificates.crt", NULL)) {
        // Oder Standard-CA-Store verwenden
        if (!SSL_CTX_set_default_verify_paths(ctx)) {
            SSL_CTX_free(ctx);
            return NULL;
        }
    }

    // Minimale TLS-Version setzen
    SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);

    return ctx;
}

// SICHER: Mit Hostname-Verifizierung verbinden
int connect_safe(SSL_CTX* ctx, const char* host, int port) {
    SSL* ssl = SSL_new(ctx);
    if (ssl == NULL) {
        return -1;
    }

    // Erwarteten Hostname für Verifizierung setzen
    if (!SSL_set1_host(ssl, host)) {
        SSL_free(ssl);
        return -1;
    }

    // Hostname-Prüfung aktivieren
    SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);

    int fd = create_socket(host, port);
    if (fd < 0) {
        SSL_free(ssl);
        return -1;
    }

    SSL_set_fd(ssl, fd);

    // TLS-Handshake durchführen
    if (SSL_connect(ssl) <= 0) {
        handle_ssl_error(ssl);
        SSL_free(ssl);
        close(fd);
        return -1;
    }

    // Zertifikat verifizieren
    long verify_result = SSL_get_verify_result(ssl);
    if (verify_result != X509_V_OK) {
        fprintf(stderr, "Zertifikatsverifizierung fehlgeschlagen: %s\n",
                X509_verify_cert_error_string(verify_result));
        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(fd);
        return -1;
    }

    // Hostname verifizieren (redundant wenn SSL_set1_host funktioniert hat)
    X509* cert = SSL_get_peer_certificate(ssl);
    if (cert == NULL) {
        fprintf(stderr, "Kein Peer-Zertifikat\n");
        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(fd);
        return -1;
    }
    X509_free(cert);

    return fd;  // File-Descriptor zurückgeben, ssl wird für I/O verwendet
}

// SICHER: Vollständige Verifizierung mit OCSP-Prüfung
SSL_CTX* create_context_with_ocsp() {
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());

    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
    SSL_CTX_set_default_verify_paths(ctx);

    // OCSP-Stapling aktivieren
    SSL_CTX_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp);

    // OCSP-Callback setzen
    SSL_CTX_set_tlsext_status_cb(ctx, ocsp_callback);

    return ctx;
}

int ocsp_callback(SSL* ssl, void* arg) {
    const unsigned char* ocsp_response;
    int len = SSL_get_tlsext_status_ocsp_resp(ssl, &ocsp_response);

    if (len <= 0) {
        // Keine OCSP-Antwort - Richtlinie entscheiden
        return 1;  // Oder 0 um OCSP zu erfordern
    }

    // OCSP-Antwort verifizieren
    // ... Verifizierungscode ...

    return 1;  // Erfolg
}
# SICHER: Python mit ordnungsgemäßer Zertifikatsverifizierung
import ssl
import socket
import certifi

# SICHER: Standard-Kontext mit Verifizierung verwenden
context = ssl.create_default_context()
# Verifizierung ist standardmäßig aktiviert

# System- oder certifi-CA-Bundle verwenden
context.load_verify_locations(certifi.where())

# Mit Hostname-Verifizierung verbinden
with socket.create_connection((host, port)) as sock:
    with context.wrap_socket(sock, server_hostname=host) as ssock:
        # Zertifikat und Hostname verifiziert
        ssock.send(data)

# SICHER: Requests mit Verifizierung (Standard)
import requests

# Zertifikatsverifizierung ist standardmäßig aktiviert
response = requests.get(url)  # verify=True ist Standard

# Oder explizit CA-Bundle angeben
response = requests.get(url, verify='/path/to/ca-bundle.crt')

# SICHER: Vollständige SSL-Kontext-Einrichtung
def create_secure_context():
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

    # Zertifikatsverifizierung aktivieren
    context.verify_mode = ssl.CERT_REQUIRED

    # Hostname-Prüfung aktivieren
    context.check_hostname = True

    # Vertrauenswürdige CAs laden
    context.load_verify_locations(certifi.where())

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

    return context
// SICHER: Java mit ordnungsgemäßer Zertifikatsvalidierung
public class SafeHttpClient {

    public static HttpsURLConnection createSecureConnection(String urlStr)
            throws Exception {
        // Standard-SSLContext verwenden, der Zertifikate validiert
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, null, new SecureRandom());

        URL url = new URL(urlStr);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setSSLSocketFactory(sslContext.getSocketFactory());

        // Standard-Hostname-Verifier prüft Zertifikat CN/SAN
        // conn.setHostnameVerifier() nicht aufgerufen - verwendet sicheren Standard

        return conn;
    }

    // SICHER: Benutzerdefinierter Trust-Manager mit ordnungsgemäßer Validierung
    public static SSLContext createCustomContext(String trustStorePath,
                                                  String password)
            throws Exception {
        // Trust-Store laden
        KeyStore trustStore = KeyStore.getInstance("JKS");
        try (FileInputStream fis = new FileInputStream(trustStorePath)) {
            trustStore.load(fis, password.toCharArray());
        }

        // Trust-Manager erstellen
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm()
        );
        tmf.init(trustStore);

        // SSL-Kontext initialisieren
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());

        return sslContext;
    }
}

// SICHER: OkHttp mit Certificate-Pinning verwenden
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;

public class SafeOkHttpClient {

    public static OkHttpClient createPinnedClient() {
        CertificatePinner pinner = new CertificatePinner.Builder()
            .add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
            .build();

        return new OkHttpClient.Builder()
            .certificatePinner(pinner)
            .build();
    }
}

Ausgenutzt in der Praxis

Mobile App MITM-Angriffe

Mobile Anwendungen mit deaktivierter Zertifikatsvalidierung wurden in öffentlichen WLAN-Netzwerken ausgenutzt.

API-Credential-Diebstahl

Anwendungen, die Zertifikate nicht validierten, leakten API-Schlüssel an MITM-Angreifer.

Unternehmens-Datenpannen

Interne Tools mit deaktivierter Zertifikatsverifizierung exponierten sensible Unternehmensdaten.


Tools zum Testen/Ausnutzen

  • mitmproxy — HTTPS-Traffic abfangen
  • Burp Suite — Web-Sicherheitstests
  • SSLstrip — SSL/TLS-Downgrade-Angriffe
  • testssl.sh — SSL/TLS-Tests

CVE-Beispiele

  • CVEs in Anwendungen, die ungültige Zertifikate akzeptieren
  • Mobile-App-Zertifikatsvalidierungs-Bypasses
  • Bibliotheks-Schwachstellen, die MITM-Angriffe ermöglichen

Referenzen

  1. MITRE. "CWE-599: Missing Validation of OpenSSL Certificate." https://cwe.mitre.org/data/definitions/599.html
  2. OpenSSL Documentation. SSL_CTX_set_verify, SSL_get_verify_result.