Missing Validation of OpenSSL Certificate
Description
Missing Validation of OpenSSL Certificate occurs when an application using OpenSSL fails to properly verify SSL/TLS certificates during secure connections. This includes failing to check the certificate chain, not verifying the hostname matches the certificate's Common Name or Subject Alternative Names, accepting expired certificates, or not checking certificate revocation status. The application establishes connections without proper identity verification.
Risk
Without certificate validation, attackers can perform man-in-the-middle attacks using self-signed or improperly issued certificates. Connections believed to be secure actually provide no authentication of the remote server. Sensitive data (credentials, personal information, financial data) transmitted over these connections can be intercepted and read. The application may connect to malicious servers impersonating legitimate services.
Solution
Enable certificate verification with SSL_CTX_set_verify() and SSL_VERIFY_PEER. Load trusted CA certificates with SSL_CTX_load_verify_locations(). Verify hostname matches certificate using SSL_set1_host() or manual verification. Check SSL_get_verify_result() after connection. Consider checking certificate revocation status via OCSP or CRLs. Use modern TLS versions and disable insecure protocols.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Data Interception Attackers can intercept encrypted communications. |
| Authentication | Scope: Server Impersonation Cannot verify connecting to intended server. |
| Integrity | Scope: Data Tampering MITM attackers can modify data in transit. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: No certificate verification
SSL_CTX* create_context_vulnerable() {
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
// No verification set - accepts ANY certificate!
// SSL_CTX_set_verify not called
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);
// Connects without verifying server identity!
if (SSL_connect(ssl) <= 0) {
handle_error();
return;
}
// MITM attacker can present any certificate
send_sensitive_data(ssl);
}
// VULNERABLE: Verification disabled
SSL_CTX* create_context_insecure() {
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
// Explicitly disables verification!
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
return ctx;
}
// VULNERABLE: Verification enabled but no CA loaded
SSL_CTX* create_context_no_ca() {
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
// Verification requested but will always fail
// because no trusted CAs are loaded
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
// Missing: SSL_CTX_load_verify_locations()
return ctx;
}
// VULNERABLE: No hostname verification
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) {
// Certificate might be valid but for different host!
// Attacker with any valid cert can intercept
if (SSL_get_verify_result(ssl) == X509_V_OK) {
// BUG: Didn't verify hostname matches certificate
send_data(ssl);
}
}
}
// VULNERABLE: Ignoring verification result
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); // May fail verification
// BUG: Never checked SSL_get_verify_result()!
send_sensitive_data(ssl);
}
# VULNERABLE: Python with disabled certificate verification
import ssl
import urllib.request
# VULNERABLE: Disable all certificate checks
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
response = urllib.request.urlopen(url, context=context)
# VULNERABLE: Using deprecated/insecure methods
import requests
# Disables certificate verification!
response = requests.get(url, verify=False)
# VULNERABLE: Socket without verification
import socket
import ssl
def connect_vulnerable(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
# No certificate verification
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
// VULNERABLE: Java TrustManager that accepts all certificates
public class VulnerableTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// Does nothing - accepts all client certificates
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// Does nothing - accepts all server certificates!
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
// VULNERABLE: Using the insecure trust manager
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()
);
// Also vulnerable: disable hostname verification
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> true // Accepts any hostname!
);
URL url = new URL(urlStr);
return (HttpsURLConnection) url.openConnection();
}
}
Fixed Code
// SAFE: Proper certificate verification
SSL_CTX* create_context_safe() {
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
if (ctx == NULL) {
return NULL;
}
// Enable certificate verification
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
// Load trusted CA certificates
if (!SSL_CTX_load_verify_locations(ctx, "/etc/ssl/certs/ca-certificates.crt", NULL)) {
// Or use default CA store
if (!SSL_CTX_set_default_verify_paths(ctx)) {
SSL_CTX_free(ctx);
return NULL;
}
}
// Set minimum TLS version
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
return ctx;
}
// SAFE: Connect with hostname verification
int connect_safe(SSL_CTX* ctx, const char* host, int port) {
SSL* ssl = SSL_new(ctx);
if (ssl == NULL) {
return -1;
}
// Set expected hostname for verification
if (!SSL_set1_host(ssl, host)) {
SSL_free(ssl);
return -1;
}
// Enable hostname checking
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);
// Perform TLS handshake
if (SSL_connect(ssl) <= 0) {
handle_ssl_error(ssl);
SSL_free(ssl);
close(fd);
return -1;
}
// Verify certificate
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));
SSL_shutdown(ssl);
SSL_free(ssl);
close(fd);
return -1;
}
// Verify hostname (redundant if SSL_set1_host worked)
X509* cert = SSL_get_peer_certificate(ssl);
if (cert == NULL) {
fprintf(stderr, "No peer certificate\n");
SSL_shutdown(ssl);
SSL_free(ssl);
close(fd);
return -1;
}
X509_free(cert);
return fd; // Return file descriptor, ssl is used for I/O
}
// SAFE: Complete verification with OCSP checking
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);
// Enable OCSP stapling
SSL_CTX_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp);
// Set OCSP callback
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) {
// No OCSP response - decide policy
return 1; // Or 0 to require OCSP
}
// Verify OCSP response
// ... verification code ...
return 1; // Success
}
# SAFE: Python with proper certificate verification
import ssl
import socket
import certifi
# SAFE: Using default context with verification
context = ssl.create_default_context()
# Verification is enabled by default
# Use system or certifi CA bundle
context.load_verify_locations(certifi.where())
# Connect with hostname verification
with socket.create_connection((host, port)) as sock:
with context.wrap_socket(sock, server_hostname=host) as ssock:
# Certificate and hostname verified
ssock.send(data)
# SAFE: Requests with verification (default)
import requests
# Certificate verification is on by default
response = requests.get(url) # verify=True is default
# Or explicitly specify CA bundle
response = requests.get(url, verify='/path/to/ca-bundle.crt')
# SAFE: Complete SSL context setup
def create_secure_context():
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# Enable certificate verification
context.verify_mode = ssl.CERT_REQUIRED
# Enable hostname checking
context.check_hostname = True
# Load trusted CAs
context.load_verify_locations(certifi.where())
# Set minimum TLS version
context.minimum_version = ssl.TLSVersion.TLSv1_2
return context
// SAFE: Java with proper certificate validation
public class SafeHttpClient {
public static HttpsURLConnection createSecureConnection(String urlStr)
throws Exception {
// Use default SSLContext which validates certificates
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());
// Default hostname verifier checks certificate CN/SAN
// conn.setHostnameVerifier() not called - uses secure default
return conn;
}
// SAFE: Custom trust manager with proper validation
public static SSLContext createCustomContext(String trustStorePath,
String password)
throws Exception {
// Load trust store
KeyStore trustStore = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream(trustStorePath)) {
trustStore.load(fis, password.toCharArray());
}
// Create trust manager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
);
tmf.init(trustStore);
// Initialize SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
}
// SAFE: Using OkHttp with certificate pinning
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();
}
}
Exploited in the Wild
Mobile App MITM Attacks
Mobile applications with disabled certificate validation were exploited on public WiFi networks.
API Credential Theft
Applications that didn't validate certificates leaked API keys to MITM attackers.
Enterprise Data Breaches
Internal tools with certificate verification disabled exposed sensitive corporate data.
Tools to test/exploit
-
mitmproxy — intercept HTTPS traffic.
-
Burp Suite — web security testing.
-
SSLstrip — SSL/TLS downgrade attacks.
-
testssl.sh — SSL/TLS testing.
CVE Examples
-
CVEs in applications accepting invalid certificates.
-
Mobile app certificate validation bypasses.
-
Library vulnerabilities allowing MITM attacks.
References
-
MITRE. "CWE-599: Missing Validation of OpenSSL Certificate." https://cwe.mitre.org/data/definitions/599.html
-
OpenSSL Documentation. SSL_CTX_set_verify, SSL_get_verify_result.