Skip to main content

Overview

This vulnerability occurs when an application communicates over TLS/SSL (HTTPS, LDAPS, secure database connections, etc.) but fails to properly validate the certificate presented by the server. Common failures include:
  • Not checking the certificate at all.
  • Not verifying the certificate chain up to a trusted root Certificate Authority (CA).
  • Accepting expired certificates.
  • Accepting certificates for the wrong hostname (hostname mismatch).
  • Accepting self-signed certificates in production without explicit pinning.
Failing to validate certificates allows attackers to perform Man-in-the-Middle (MitM) attacks by presenting a fake or invalid certificate, intercepting, reading, and potentially modifying the supposedly secure communication. 🔒❓➡️👨‍💻➡️🔓

Business Impact

Improper certificate validation completely undermines the security benefits of TLS/SSL:
  • Data Interception: Attackers can eavesdrop on sensitive data (credentials, session tokens, PII) transmitted over the connection.
  • Data Tampering: Attackers can modify data in transit without detection.
  • Authentication Bypass: If certificate validation is part of client or server authentication, bypassing it breaks the authentication mechanism.
  • Phishing: Users might be presented with fake websites served over HTTPS with invalid certificates if their browser or client doesn’t warn them properly (though browsers are generally good at this, application-to-server connections often fail here).

Reference Details

CWE ID: CWE-295 (Related: CWE-296, CWE-297) OWASP Top 10 (2021): A07:2021 - Identification and Authentication Failures Severity: High to Critical

Framework-Specific Analysis and Remediation

This vulnerability typically occurs when developers use HTTP client libraries, LDAP clients, database connectors, or other network libraries and either explicitly disable certificate validation (often done during development to work with self-signed certificates) or use libraries with insecure defaults. Key Remediation Principles:
  1. Always Validate Certificates: Ensure certificate validation is enabled in all production code making TLS/SSL connections.
  2. Verify Hostname: Ensure the hostname in the certificate matches the hostname the application is connecting to.
  3. Check Chain of Trust: Ensure the certificate chain validates back to a trusted root CA present in the system’s or application’s trust store.
  4. Check Expiry: Ensure the certificate is within its validity period.
  5. Avoid Disabling Validation: Never disable certificate validation in production environments, even for internal connections. Use proper internal CAs or certificates if needed.
  6. Keep Trust Stores Updated: Ensure the operating system and application trust stores (containing root CA certificates) are kept up-to-date.

  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Using libraries like requests, urllib3, or ldap3 without verifying server certificates, often by setting verify=False.

Vulnerable Scenario 1: requests with verify=False

# api_client.py
import requests

def call_secure_api(url, data):
    try:
        # DANGEROUS: verify=False disables SSL certificate validation.
        # Allows connection to servers with invalid, expired, self-signed,
        # or wrong-hostname certificates, enabling MitM attacks.
        response = requests.post(url, json=data, verify=False)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"API Call Error: {e}")
        return None

Vulnerable Scenario 2: ldap3 Ignoring Certificate Validation

Connecting to LDAPS without proper TLS configuration.
# ldap_connector.py
from ldap3 import Server, Connection, Tls

def connect_ldap_unsafe(hostname, user, password):
    # DANGEROUS: tls_validate=ssl.CERT_NONE disables certificate check.
    # tls_cacerts_file might also be missing or incorrect.
    tls_config = Tls(validate=ssl.CERT_NONE) # Problematic line
    server = Server(hostname, use_ssl=True, tls=tls_config)
    try:
        conn = Connection(server, user=user, password=password, auto_bind=True)
        # Connection established, but could be MitM.
        print(conn.result)
        conn.unbind()
        return True
    except Exception as e:
        print(f"LDAP Connection Error: {e}")
        return False

Mitigation and Best Practices

  • requests: Ensure verify=True (the default). If connecting to internal systems with custom CAs, provide the path to the CA bundle file via verify='/path/to/ca-bundle.pem'.
  • ldap3: Set validate=ssl.CERT_REQUIRED and provide the correct CA certificate path via tls_cacerts_file or rely on the system’s trust store.

Secure Code Example

# api_client.py (Secure)
import requests
import os

# Optional: Path to custom CA bundle if needed, e.g., from env var
CA_BUNDLE_PATH = os.environ.get('REQUESTS_CA_BUNDLE') # requests uses this by default if set

def call_secure_api_secure(url, data):
    try:
        # SECURE: verify=True is the default.
        # If a custom CA is needed and REQUESTS_CA_BUNDLE env var isn't used:
        # response = requests.post(url, json=data, verify='/path/to/custom/ca.pem')
        response = requests.post(url, json=data, verify=True) # Explicit default
        response.raise_for_status()
        return response.json()
    except requests.exceptions.SSLError as e:
        print(f"SSL Validation Error: {e}") # Specific catch for SSL issues
        return None
    except requests.exceptions.RequestException as e:
        print(f"API Call Error: {e}")
        return None

# ldap_connector.py (Secure)
import ssl
from ldap3 import Server, Connection, Tls

def connect_ldap_secure(hostname, user, password):
    # SECURE: Require certificate validation.
    # Provide path to CA cert if not in system trust store.
    tls_config = Tls(validate=ssl.CERT_REQUIRED, ca_certs_file='/etc/ssl/certs/ca-certificates.crt') # Example path
    server = Server(hostname, use_ssl=True, tls=tls_config)
    try:
        # Connection will fail if cert validation fails
        conn = Connection(server, user=user, password=password, auto_bind=True)
        print("LDAP Connection Successful & Verified")
        conn.unbind()
        return True
    except Exception as e:
        print(f"LDAP Connection Error (Validation may have failed): {e}")
        return False

Testing Strategy

Use an intercepting proxy (like Burp Suite, mitmproxy) configured with its own root CA. Configure the client system/application to trust the proxy’s CA. Attempt to connect to the target HTTPS/LDAPS service through the proxy.
  • If verify=False or ssl.CERT_NONE: The connection will likely succeed without warnings, allowing the proxy to decrypt traffic (MitM successful).
  • If verify=True and system trust store used: The connection should fail if the target server presents the proxy’s certificate (as it won’t match the expected hostname or chain correctly).
  • If verify='/path/to/ca': The connection should fail unless the proxy can present a cert signed by that specific CA.