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.
This commit is contained in:
William Lallemand 2026-05-25 12:55:18 +00:00
parent 31cd3d13aa
commit 85a833feba

View file

@ -44,6 +44,29 @@ __decl_rwlock(ssl_ctx_lru_rwlock);
#ifndef SSL_NO_GENERATE_CERTIFICATES
/* Validate that <servername> 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;