Improper Validation of Certificate with Host Mismatch
Description
Improper Validation of Certificate with Host Mismatch is a vulnerability that occurs when a product accepts certificates without verifying they belong to the intended host. A certificate may be completely valid, properly signed by a trusted CA, and not expired, yet be issued for a different domain than the one being connected to. Without hostname validation, attackers can obtain legitimate certificates for their own domains and use them during man-in-the-middle attacks or redirect scenarios. The product must validate the certificate's host-specific data, specifically the Common Name (CN) in the Subject field or, preferably, the Subject Alternative Name (SAN) extension.
Risk
Missing hostname validation renders the entire certificate verification process almost useless against targeted attacks. Attackers can easily obtain valid certificates from certificate authorities for domains they control. When a victim's application connects to what it believes is a trusted server, an attacker performing man-in-the-middle presents their legitimate certificate. Without hostname checking, the application accepts this certificate because it is technically valid, allowing the attacker to intercept all traffic. CVE-2012-5810 demonstrated real financial losses from a mobile banking application that didn't verify hostnames. Implementation errors like improper null byte handling can also defeat hostname validation even when attempted.
Solution
Always validate that the certificate's hostname matches the expected server hostname. Check both the Common Name (CN) in the Subject field and the Subject Alternative Name (SAN) extension - modern certificates use SAN, and RFC 6125 recommends checking SAN preferentially. Use TLS library functions designed for hostname validation rather than implementing custom checks. Handle edge cases including wildcards, internationalized domain names (IDN), and null bytes in certificate names. When using certificate pinning, validate all certificate properties including hostname before pinning occurs. Communicate verification results clearly to users so they understand when connections may be insecure. Test with certificates valid for different hostnames to ensure hostname validation is working.
Common Consequences
| Impact | Details |
|---|---|
| Access Control, Authentication | Scope: Access Control, Authentication Data from the validated system may actually originate from an attacker's host presenting a valid certificate for a different domain. This enables identity spoofing and man-in-the-middle attacks where attackers can intercept, read, and modify all communications. |
Example Code
Vulnerable Code (C/OpenSSL)
The following examples demonstrate improper hostname validation:
// Vulnerable: Missing hostname verification
#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)) {
// Vulnerable: Certificate chain validated, but hostname NOT checked!
// Attacker can use ANY valid certificate
return 1; // "Trusted" - but for wrong host!
}
return 0;
}
void vulnerable_connect(const char *hostname, int port) {
SSL *ssl = create_ssl_connection(hostname, port);
// Only checks if certificate is valid, not if it's for this host
if (SSL_get_verify_result(ssl) == X509_V_OK) {
// Proceeds without hostname check
send_sensitive_data(ssl);
}
}
# Vulnerable: Python with hostname check disabled
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()
# Vulnerable: Hostname check explicitly disabled
context.check_hostname = False
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(sock) # Missing server_hostname parameter!
ssl_sock.connect((hostname, port))
# Certificate chain validated, but hostname NOT verified
return ssl_sock
def vulnerable_requests():
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# Vulnerable: Custom adapter ignoring hostname
# Some implementations do this
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# Or custom SSL context without hostname check
return requests.get('https://example.com', verify=True)
// Vulnerable: Java with permissive hostname verifier
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
public class VulnerableHostnameCheck {
public static void vulnerable_setup() throws Exception {
// Vulnerable: HostnameVerifier that accepts all hostnames
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> true // Always returns true!
);
// Now any certificate is accepted for any hostname
URL url = new URL("https://secure.example.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// Attacker can intercept with certificate for "evil.attacker.com"
}
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();
// Vulnerable: Gets certificate but doesn't check hostname
SSLSession session = socket.getSession();
X509Certificate[] certs = (X509Certificate[]) session.getPeerCertificates();
// Certificate is valid but might be for different host!
sendSensitiveData(socket);
}
}
Fixed Code (C/OpenSSL)
// Fixed: Proper hostname verification
#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, "No certificate presented\n");
return 0;
}
// Check certificate chain validation
long verify_result = SSL_get_verify_result(ssl);
if (verify_result != X509_V_OK) {
fprintf(stderr, "Certificate verification failed: %s\n",
X509_verify_cert_error_string(verify_result));
X509_free(cert);
return 0;
}
// Fixed: Verify hostname matches certificate
// X509_check_host handles CN, SAN, wildcards, and 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 verification failed for: %s\n",
expected_hostname);
return 0;
}
return 1; // Certificate fully validated including hostname
}
// Alternative: Using SSL_set1_host for automatic verification
SSL* secure_connect_with_auto_verify(SSL_CTX *ctx, const char *hostname, int port) {
SSL *ssl = SSL_new(ctx);
// Set expected hostname for automatic verification
if (!SSL_set1_host(ssl, hostname)) {
fprintf(stderr, "Failed to set hostname for verification\n");
SSL_free(ssl);
return NULL;
}
// Connect and 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, "Connection failed\n");
return NULL;
}
// Hostname verification happens automatically during handshake
return ssl;
}
# Fixed: Python with proper hostname verification
import ssl
import socket
import certifi
def secure_connect(hostname, port):
context = ssl.create_default_context(cafile=certifi.where())
# Fixed: Ensure hostname verification is enabled (default in Python 3.7+)
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Fixed: Provide server_hostname for SNI and hostname verification
ssl_sock = context.wrap_socket(sock, server_hostname=hostname)
try:
ssl_sock.connect((hostname, port))
except ssl.CertificateError as e:
# This includes hostname mismatches
print(f"Certificate error (possibly hostname mismatch): {e}")
raise
return ssl_sock
def secure_requests():
import requests
# Fixed: verify=True enables both chain and hostname validation (default)
response = requests.get('https://example.com', verify=True)
# Or with explicit CA bundle
response = requests.get('https://example.com', verify=certifi.where())
return response
// Fixed: Java with proper hostname verification
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
public class SecureHostnameCheck {
public static void secure_setup() throws Exception {
// Fixed: Use default HostnameVerifier (validates hostname)
// Don't override with permissive verifier
// HttpsURLConnection uses HTTPS hostname verifier by default
}
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);
// Fixed: Set expected hostname for verification
SSLParameters params = socket.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS"); // Enables hostname check
socket.setSSLParameters(params);
socket.startHandshake();
// Fixed: Hostname automatically verified during handshake
// If mismatch, SSLException is thrown
sendSensitiveData(socket);
}
// Alternative using HttpsURLConnection
public void secure_https_connect(String url) throws Exception {
URL target = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) target.openConnection();
// Default hostname verifier validates against certificate
// Only override if you need STRICTER checking, never more permissive
conn.connect();
// Hostname verified automatically
}
}
The fix ensures hostname verification is enabled and the certificate's hostname (CN or SAN) matches the expected server.
Exploited in the Wild
Mobile Banking Hostname Bypass (Banking Apps, 2012)
CVE-2012-5810 documented a mobile banking application that didn't verify hostnames in TLS certificates, causing real financial losses when users connected through compromised networks and attackers intercepted banking transactions.
Null Byte in Certificate Name (Browsers, 2009)
CVE-2009-2408 documented browsers failing to handle null bytes in Common Name fields, allowing certificates like "www.bank.com\0.attacker.com" to pass hostname validation for "www.bank.com".
Python Library Hostname Regex (Python Libraries, 2012)
CVE-2012-3446 documented a Python library using incorrect regex for hostname matching, allowing bypasses through crafted certificate names.
Tools to Test/Exploit
-
testssl.sh — Command line tool for testing SSL/TLS including hostname verification.
-
mitmproxy — HTTPS proxy for testing with certificates for different hostnames.
-
badssl.com — Test site with various certificate error scenarios including hostname mismatches.
CVE Examples
-
CVE-2009-2408 — Browser fails to handle null bytes in Common Name, allowing HTTPS spoofing.
-
CVE-2012-5810 — Mobile banking app doesn't verify hostname, causing financial losses.
-
CVE-2012-3446 — Python library uses incorrect regex for hostname matching.
-
CVE-2012-0867 — Database program truncates Common Name during verification.
-
CVE-2003-0355 — Web browser doesn't validate Common Name.
References
-
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