From 85a833feba0e631eea225e768461a6e0880397a4 Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Mon, 25 May 2026 12:55:18 +0000 Subject: [PATCH] BUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN certificate injection ssl_sock_add_san_ext() builds the Subject Alternative Name extension by concatenating "DNS:" + servername and passing the result to X509V3_EXT_nconf_nid(). OpenSSL's nconf parser splits the value string on commas into multiple type:value SAN entries. The SNI comes from unauthenticated TLS ClientHello data -- an attacker can embed commas and colons (e.g., "host,dns:internal.corp,ip:10.0.0.1") to inject arbitrary GENERAL_NAME entries into certificates signed by HAProxy's configured CA. This is a CA issuance-policy violation: the operator expects one certificate per SNI hostname, but an attacker can obtain certificates containing additional hostnames/IPs/emails without access to the CA private key. Fix by adding ssl_sock_sni_is_valid() that validates the SNI contains only DNS-label-legal characters (alphanumeric, hyphens, dots). The check is performed at the start of ssl_sock_do_create_cert() before any allocation. Commas, colons, spaces, and other special characters cause certificate generation to fail, preventing SAN injection while allowing all valid hostname values. Must be backported in every maintained branches. --- src/ssl_gencert.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/ssl_gencert.c b/src/ssl_gencert.c index 459c0bafd..c201e0c7b 100644 --- a/src/ssl_gencert.c +++ b/src/ssl_gencert.c @@ -44,6 +44,29 @@ __decl_rwlock(ssl_ctx_lru_rwlock); #ifndef SSL_NO_GENERATE_CERTIFICATES +/* Validate that contains only DNS-label-legal characters + * (letters, digits, hyphens, dots). Rejects commas, colons, and other + * characters that OpenSSL's nconf parser would interpret as SAN entry + * separators or type prefixes, preventing certificate injection. */ +static int ssl_sock_sni_is_valid(const char *s) +{ + size_t i; + + if (!s || !*s) + return 0; + for (i = 0; s[i]; i++) { + unsigned char c = (unsigned char)s[i]; + if (!( + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || c == '.' + )) + return 0; + } + return 1; +} + /* Configure a DNS SAN extension on a certificate. */ int ssl_sock_add_san_ext(X509V3_CTX* ctx, X509* cert, const char *servername) { int failure = 0; @@ -100,6 +123,14 @@ static SSL_CTX *ssl_sock_do_create_cert(const char *servername, struct bind_conf int key_type; struct sni_ctx *sni_ctx; + /* Reject SNI values containing characters that OpenSSL's nconf + * parser would interpret as SAN entry separators (commas), type + * prefixes (colons), or other special constructs. This prevents + * attackers from injecting arbitrary GENERAL_NAME entries into + * certificates signed by HAProxy's configured CA. */ + if (!ssl_sock_sni_is_valid(servername)) + goto mkcert_error; + sni_ctx = ssl_sock_choose_sni_ctx(bind_conf, NULL, "", 1, 1); if (!sni_ctx) goto mkcert_error;