]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: ssl: reject server certificate names containing a NUL byte
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 6 Aug 2026 07:44:59 +0000 (09:44 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 7 Aug 2026 10:03:01 +0000 (10:03 +0000)
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

index 2bb9d3bcf6ed21ef0ee3bc9c86e00ff40be21332..802d08597e8eebe82d977f12277aa2bfe613f8be 100644 (file)
@@ -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);
                }
        }