From: Remi Tricot-Le Breton Date: Thu, 6 Aug 2026 07:33:59 +0000 (+0200) Subject: BUG/MEDIUM: jwe: validate the secret length against the algorithm of the token X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ba26a4e85311761928b58b2c58a2c15da1e02cd6;p=thirdparty%2Fhaproxy.git BUG/MEDIUM: jwe: validate the secret length against the algorithm of the token The "alg" field of the JOSE header of a JWE token selects the key-wrapping algorithm, hence the AES key size, while the key itself is the secret configured by the operator for the "jwt_decrypt_secret" converter. That secret is stored in an exact-size heap allocation, and neither decrypt_cek_aeskw() nor aes_process() (used by the AES-GCM key wrap variant) checked that it was large enough for the selected cipher before handing its address to OpenSSL. So a client sending a token that declares A256KW or A256GCMKW while the configured secret is only 16 bytes long makes OpenSSL read 16 bytes past the end of that allocation and use them as key material. This is a remotely triggered heap over-read which may crash the worker, and whose bytes influence the decryption result. Let's check the secret length against the cipher's key length in both paths before initializing the cipher. Both were introduced in 3.4, by commits f0e64de75 ("MINOR: ssl: Factorize AES GCM data processing") and 416b87d5d ("MINOR: jwe: Add new jwt_decrypt_secret converter"). This must be backported to 3.4. Reported-by: Claude (ANT-2026-TS9WFQ5T) Reported-by: Claude (ANT-2026-9T34RNDD) --- diff --git a/src/jwe.c b/src/jwe.c index 3729d7525..3593ffb61 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -405,6 +405,13 @@ static int decrypt_cek_aeskw(struct buffer *cek, struct buffer *decrypted_cek, s EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); #endif + /* comes from the token's JOSE header, so the attacker picks + * the key size while the secret is operator-provided: refuse to run if + * the secret is shorter than what the cipher will read. + */ + if (b_data(secret) < EVP_CIPHER_key_length(cipher)) + goto end; + iv_size = EVP_CIPHER_iv_length(cipher); iv = alloc_trash_chunk(); if (!iv) diff --git a/src/ssl_sample.c b/src/ssl_sample.c index f35dc9e18..378ffabb9 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -335,6 +335,13 @@ int aes_process(struct buffer *data, struct buffer *nonce, struct buffer *key, i if (!ctx) goto err; + /* The key size may be dictated by the input (e.g. the "alg" field of a + * JWE token), so make sure the configured key is large enough for the + * selected cipher, otherwise OpenSSL would read past its end. + */ + if (b_data(key) < key_size / 8) + goto err; + switch(key_size) { case 128: sample_conv_aes_init(decrypt, ctx, (gcm ? EVP_aes_128_gcm() : EVP_aes_128_cbc()),