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. 🔒❓➡️👨💻➡️🔓
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
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:
Always Validate Certificates: Ensure certificate validation is enabled in all production code making TLS/SSL connections.
Verify Hostname: Ensure the hostname in the certificate matches the hostname the application is connecting to.
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.
Check Expiry: Ensure the certificate is within its validity period.
Avoid Disabling Validation:Never disable certificate validation in production environments, even for internal connections. Use proper internal CAs or certificates if needed.
Keep Trust Stores Updated: Ensure the operating system and application trust stores (containing root CA certificates) are kept up-to-date.
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.
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.
Using java.net.HttpURLConnection, Apache HttpClient, javax.naming.ldap.LdapContext (JNDI), or database JDBC drivers over SSL without ensuring proper TrustManager configuration. Often involves developers installing custom TrustManagers that accept all certificates during development.
Even if the certificate chain is validated, failing to check if the certificate’s Common Name (CN) or Subject Alternative Name (SAN) matches the requested hostname allows MitM.
// http/ApiClientHostnameBypass.javapublic void callApiHostnameBypass() throws Exception { // Assume default SSLContext (validates chain) HttpsURLConnection connection = (HttpsURLConnection) new URL("[https://trusted.example.com](https://trusted.example.com)").openConnection(); // DANGEROUS: Disables the check ensuring the cert is actually for trusted.example.com. // Attacker presents valid cert for attacker.com, validation passes. connection.setHostnameVerifier((hostname, session) -> true); connection.connect(); // Connects to MitM server presenting attacker.com cert // ... read response ...}
Rely on JVM Defaults: Use the default SSLContext and TrustManager which uses the JVM’s trust store (cacerts). Keep the JRE updated for the latest CA certificates.
Custom Trust Store: If connecting to internal systems with a custom CA, configure a specific TrustManager that loads a custom trust store file containing only the trusted internal CA certificate(s).
Enable Hostname Verification: Ensure hostname verification is enabled (usually the default).
// http/ApiClientSecure.javaimport javax.net.ssl.HttpsURLConnection;import java.net.URL;// ... other imports ...public void callApiSecure() throws Exception { // SECURE: Use default SSLContext and TrustManager. // This validates against JVM's cacerts and performs hostname verification. HttpsURLConnection connection = (HttpsURLConnection) new URL("[https://secure.example.com](https://secure.example.com)").openConnection(); // No need to set custom TrustManager or HostnameVerifier for standard validation. connection.connect(); // Will throw SSLException if validation fails // ... read response ...}// Example with Custom Trust Store (if needed for internal CA)// KeyStore trustStore = KeyStore.getInstance("JKS");// trustStore.load(new FileInputStream("/path/to/my/truststore.jks"), "password".toCharArray());// TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());// tmf.init(trustStore);// SSLContext sslContext = SSLContext.getInstance("TLS");// sslContext.init(null, tmf.getTrustManagers(), null);// connection.setSSLSocketFactory(sslContext.getSocketFactory());// Hostname verification is still enabled by default.
Use an intercepting proxy (Burp, mitmproxy) with its own CA certificate trusted by the client machine/JVM. Attempt connections to target HTTPS services.
Secure: Connections should fail with SSLHandshakeException or similar certificate validation errors (unknown CA, hostname mismatch).
Vulnerable: Connections succeed without error, traffic can be intercepted.
Check code for custom X509TrustManager implementations or calls like setHostnameVerifier((h,s)->true).
Using HttpClient or HttpWebRequest. Certificate validation is generally enabled by default using the Windows certificate store. Vulnerabilities arise from using ServerCertificateCustomValidationCallback or older ServicePointManager.ServerCertificateValidationCallback to bypass checks.
Rely on Defaults: Use the default certificate validation provided by HttpClient / HttpWebRequest, which uses the OS trust store.
Custom Validation (Carefully): If custom validation is required (e.g., certificate pinning), implement the ServerCertificateCustomValidationCallback logic meticulously. Check sslPolicyErrors for None, verify the chain, check the hostname against the certificate’s subject/SANs, and check expiry. Never simply return true.
Avoid Global Callback: Do not use ServicePointManager.ServerCertificateValidationCallback.
Use an intercepting proxy (Burp, Fiddler, mitmproxy) with its root CA trusted by the client machine. Attempt connections to target HTTPS services.
Secure: Connections should fail with HttpRequestException (wrapping AuthenticationException or IOException) indicating certificate errors (untrusted root, hostname mismatch).
Vulnerable: Connections succeed.
Review code for ServerCertificateCustomValidationCallback or ServicePointManager.ServerCertificateValidationCallback. Ensure any custom callback performs thorough checks and doesn’t just return true.
Using curl, file_get_contents with SSL streams, or Guzzle without proper SSL context options. PHP defaults often changed across versions, sometimes requiring explicit enabling of peer verification.
curl: Ensure CURLOPT_SSL_VERIFYPEER is true (often the default in recent PHP/curl). Ensure CURLOPT_SSL_VERIFYHOST is 2 (checks hostname exists and matches). If using internal CAs, set CURLOPT_CAINFO to the path of your CA bundle file.
Stream Contexts: Ensure verify_peer and verify_peer_name SSL context options are true (defaults). If using internal CAs, set cafile context option.
Guzzle: Relies on curl defaults. Ensure verify option is true (default). Provide path to CA bundle via verify if needed.
Use an intercepting proxy (Burp, mitmproxy) trusted by the client system. Attempt connections via curl or file_get_contents.
Secure: Connection should fail with SSL/TLS validation errors.
Vulnerable: Connection succeeds.
Review code for curl_setopt calls with CURLOPT_SSL_VERIFYPEER/VERIFYHOST set to false/0. Check stream context options for verify_peer/verify_peer_name set to false. Check Guzzle client configuration for verify => false.
Using built-in https module or libraries like axios, request (deprecated), node-fetch. Node.js performs validation by default using bundled CAs. Vulnerability is explicitly disabling it via rejectUnauthorized: false.
Do not set rejectUnauthorized: false in production code. Rely on Node.js’s default validation. If connecting to internal systems with custom CAs, provide the CA certificate via the ca option in https.request or https.Agent.
Using built-in Net::HTTP, or gems like HTTParty, Faraday. Ruby’s Net::HTTP default settings for verification have changed over versions and can be complex. Explicit configuration is recommended.
# httparty_client.rbrequire 'httparty'class MyApiClient include HTTParty base_uri '[https://wrong-host.example.com](https://wrong-host.example.com)' # DANGEROUS: Globally disabling verification for this client. no_ssl_peer_verification # Deprecated alias for verify: false # OR using options per request: def get_data_unsafe(path) # DANGEROUS: verify: false disables checks for this request. self.class.get(path, verify: false) endend
Net::HTTP: Ensure verify_mode is set to OpenSSL::SSL::VERIFY_PEER (the default in modern Ruby). If using internal CAs, set ca_file or ca_path, or configure the default certificate store.
HTTParty/Faraday: Ensure the verify option is true (the default). Provide CA path via options (:ca_file, :ca_path, or Faraday adapter options) if needed. Avoid no_ssl_peer_verification.
Use an intercepting proxy (Burp, mitmproxy) trusted by the client. Attempt connections using Net::HTTP, HTTParty, etc.
Secure: Connection should fail with OpenSSL::SSL::SSLError.
Vulnerable: Connection succeeds.
Review code for http.verify_mode = OpenSSL::SSL::VERIFY_NONE, verify: false, or no_ssl_peer_verification. Check default settings for the Ruby version in use.