From 6c85fb11bfe77ca5e20cb5ff378aa67a01bccb5f Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Thu, 6 Aug 2026 09:44:59 +0200 Subject: [PATCH] BUG/MINOR: ssl: reject server certificate names containing a NUL byte ssl_sock_srv_verifycbk() decodes each SAN dNSName and each CN of the server certificate with ASN1_STRING_to_UTF8(), which returns the decoded length, then passes the result to the hostname matcher as a plain NUL-terminated C string and throws the length away. A name encoded as "victim.com\0.attacker.com" is therefore compared as "victim.com" and matches. This defeats the point of "verify required" together with "verifyhost" or SNI on a server line: an attacker holding a certificate with such a name, and able to intercept the connection to the backend, passes the name check and can read and alter all the proxied traffic. This is the CVE-2009-2408 class of bug. It requires a CA to issue such a certificate, which modern CAs refuse to do, so the practical risk is low, but the check is cheap. Let's compare the decoded length with strlen() and ignore any name that does not match. This has been there since "verifyhost" was introduced in 1.5 by commit be55431f9 ("MINOR: ssl: Add statement 'verifyhost' to "server" statements"). It may be backported to all stable versions. Reported-by: Claude (ANT-2026-SNXPSVKX) --- src/ssl_sock.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 2bb9d3bcf..802d08597 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -5047,6 +5047,7 @@ static int ssl_sock_srv_verifycbk(int ok, X509_STORE_CTX *ctx) int i; __X509_NAME_CONST__ X509_NAME *cert_subject; char *str; + int len; if (ok == 0) return ok; @@ -5096,11 +5097,17 @@ static int ssl_sock_srv_verifycbk(int ok, X509_STORE_CTX *ctx) GENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i); if (name->type == GEN_DNS) { #if HA_OPENSSL_VERSION_NUMBER < 0x00907000L - if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.ia5) >= 0) { + if ((len = ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.ia5)) >= 0) { #else - if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) { + if ((len = ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName)) >= 0) { #endif - ok = ssl_sock_srv_hostcheck(str, servername); + /* the hostname matcher works on a NUL + * terminated string, so a name carrying + * an embedded NUL must be rejected or it + * would match its truncated form. + */ + if (strlen(str) == len) + ok = ssl_sock_srv_hostcheck(str, servername); OPENSSL_free(str); } } @@ -5114,8 +5121,10 @@ static int ssl_sock_srv_verifycbk(int ok, X509_STORE_CTX *ctx) __X509_NAME_CONST__ X509_NAME_ENTRY *entry = X509_NAME_get_entry(cert_subject, i); __X509_NAME_CONST__ ASN1_STRING *value; value = X509_NAME_ENTRY_get_data(entry); - if (ASN1_STRING_to_UTF8((unsigned char **)&str, value) >= 0) { - ok = ssl_sock_srv_hostcheck(str, servername); + if ((len = ASN1_STRING_to_UTF8((unsigned char **)&str, value)) >= 0) { + /* see above about embedded NULs */ + if (strlen(str) == len) + ok = ssl_sock_srv_hostcheck(str, servername); OPENSSL_free(str); } } -- 2.47.3