Логотип exploitDog
Консоль
Логотип exploitDog

exploitDog

github логотип

GHSA-qw3h-qqm9-jrw8

Опубликовано: 23 июл. 2026
Источник: github
Github: Не прошло ревью
CVSS4: 8.2

Описание

JWKS Fetch Ignores HTTP Response Status Code - Signing Key Destruction Causes Authentication DoS (CWE-252)

Summary

The JWKS key fetching mechanism in uaa_jwt.erl does not validate the HTTP response status code when downloading signing keys from the OAuth2 provider's JWKS endpoint. Non-200 responses (including 4xx and 5xx errors) are processed identically to successful responses. When the JWKS endpoint returns an error response with a valid-JSON body that lacks a keys field, all previously cached signing keys are destroyed, causing a persistent authentication denial of service.

Vulnerability Details

Files:

  • deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl, lines 50-63
  • deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl, lines 5-7
  • deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl, lines 98-107

Bug 1: HTTP status code ignored (uaa_jwt.erl:50-63):

case uaa_jwks:get(JwksUrl, SslOptions) of {ok, {_, _, JwksBody}} -> %% BUG: pattern ignores status code KeyList = maps:get(<<"keys">>, jose:decode(erlang:iolist_to_binary(JwksBody)), []), Keys = maps:from_list(lists:map(fun(Key) -> {maps:get(<<"kid">>, Key, undefined), {json, Key}} end, KeyList)), case replace_signing_keys(Keys, Id) of {error, _} = Err -> Err; _ -> ok end;

The Erlang httpc module returns {ok, {{HttpVersion, StatusCode, ReasonPhrase}, Headers, Body}}. The pattern {ok, {_, _, JwksBody}} matches ANY successful HTTP transaction regardless of status code — a 500 Internal Server Error is processed exactly like a 200 OK.

Bug 2: No HTTP response validation in JWKS client (uaa_jwks.erl:5-7):

get(JwksUrl, SslOptions) -> Options = [{timeout, 60000}] ++ [{ssl, SslOptions}], httpc:request(get, {JwksUrl, []}, Options, []).

The raw httpc response is returned directly with no status code validation. Additionally, httpc defaults to autoredirect = true, meaning HTTP redirects are silently followed.

Bug 3: Key replacement destroys cached keys (rabbit_oauth2_provider.erl:98-107):

do_replace_signing_keys(SigningKeys, root) -> KeyConfig = get_env(key_config, []), KeyConfig1 = proplists:delete(jwks, KeyConfig), %% deletes ALL cached JWKS keys KeyConfig2 = [{jwks, maps:merge( proplists:get_value(signing_keys, KeyConfig1, #{}), SigningKeys)} | KeyConfig1],

The existing jwks entry (containing all previously-fetched dynamic keys) is deleted via proplists:delete(jwks, ...) and replaced with maps:merge(static_signing_keys, new_keys). If new_keys is empty (from an error response), all dynamic JWKS keys are lost.

Attack Scenario

Precondition

RabbitMQ is configured with OAuth2 authentication using JWKS key discovery (the standard setup for Keycloak, Auth0, Azure AD, etc.).

Attack Chain

  1. The JWKS endpoint is temporarily degraded (maintenance, overload, or attacker-initiated DoS on the IdP)
  2. The degraded endpoint returns HTTP 500 with JSON body: {"error": "service unavailable"}
  3. Attacker sends a JWT to RabbitMQ with an unknown kid header value
  4. RabbitMQ's get_jwk/3 function doesn't find the key locally, triggers JWKS refresh (uaa_jwt.erl:120-127)
  5. JWKS fetch returns {ok, {{"HTTP/1.1", 500, "Internal Server Error"}, _, "{\"error\":\"service unavailable\"}"}}
  6. Pattern {ok, {_, _, JwksBody}} matches — status code 500 is ignored
  7. jose:decode(...) returns #{<<"error">> => <<"service unavailable">>}
  8. maps:get(<<"keys">>, ..., []) returns [] (no keys field in error response)
  9. maps:from_list([]) returns #{} (empty map)
  10. replace_signing_keys(#{}, Id) deletes all cached JWKS keys and stores only static keys (usually none)
  11. Result: All subsequent JWT authentication fails — legitimate tokens are rejected because their signing keys no longer exist in the cache

Key Insight: Attacker-Triggered Refresh

The JWKS refresh is triggered automatically when a JWT arrives with an unknown kid (uaa_jwt.erl:120). An attacker needs only:

  • Craft a JWT with a non-existent kid header
  • Send it as the password in an AMQP SASL PLAIN authentication
  • The server automatically attempts JWKS refresh

By repeatedly sending JWTs with unknown kid values, the attacker can trigger continuous JWKS refreshes. If ANY of these refreshes occur while the JWKS endpoint is degraded, all cached keys are destroyed.

Impact

  • Persistent authentication DoS: Once keys are destroyed, ALL OAuth2/JWT authentication fails for all users until a new successful JWKS refresh occurs
  • Amplification: A single attacker can deny access to all legitimate OAuth2 users across the entire RabbitMQ cluster
  • Cascade risk: During IdP maintenance windows, a single unauthenticated request with an unknown kid triggers key destruction
  • Recovery delay: Keys remain destroyed until another JWT with an unknown kid arrives (triggering another refresh) AND the JWKS endpoint has recovered

Steps to Reproduce

  1. Configure RabbitMQ with OAuth2 authentication (JWKS-based key discovery)
  2. Verify JWT authentication works normally
  3. Simulate JWKS endpoint degradation (return HTTP 500 with body {"error":"down"})
  4. Send AMQP connection with SASL PLAIN auth, username=unused, password=eyJhbGciOiJSUzI1NiIsImtpZCI6InVua25vd24ta2V5LWlkIn0.eyJzdWIiOiJ0ZXN0In0.fake (a JWT with unknown kid)
  5. Check RabbitMQ logs: "Downloaded 0 signing keys"
  6. Attempt legitimate JWT authentication — it fails (signing key not found)

Suggested Fix

Fix 1: Validate HTTP status code in uaa_jwt.erl:

case uaa_jwks:get(JwksUrl, SslOptions) of {ok, {{_, 200, _}, _, JwksBody}} -> %% Only process 200 OK responses KeyList = maps:get(<<"keys">>, jose:decode(erlang:iolist_to_binary(JwksBody)), []), ...; {ok, {{_, StatusCode, _}, _, _}} -> {error, {unexpected_status_code, StatusCode}}; {error, _} = Err -> ... end.

Fix 2: Disable auto-redirect in JWKS fetch (uaa_jwks.erl):

Options = [{timeout, 60000}, {autoredirect, false}] ++ [{ssl, SslOptions}],

Fix 3: Don't replace keys with empty set (uaa_jwt.erl):

Keys = maps:from_list(...), case maps:size(Keys) of 0 -> {error, empty_jwks_response}; _ -> replace_signing_keys(Keys, Id) end

References

  • CWE-252: Unchecked Return Value
  • CWE-754: Improper Check for Unusual or Exceptional Conditions
  • RFC 7517: JSON Web Key (JWK) — JWKS endpoint should return proper HTTP status codes
  • Commit tested: a155ee5

Пакеты

Наименование

rabbitmq

vmware
Затронутые версииВерсия исправления

>= 4.3.0, < 4.3.3

4.3.3

Наименование

rabbitmq

vmware
Затронутые версииВерсия исправления

>= 4.2.0, < 4.2.9

4.2.9

Наименование

rabbitmq

vmware
Затронутые версииВерсия исправления

>= 4.1.0, < 4.1.14

4.1.14

Наименование

rabbitmq

vmware
Затронутые версииВерсия исправления

>= 4.0.0, < 4.0.23

4.0.23

Наименование

rabbitmq

vmware
Затронутые версииВерсия исправления

>= 3.13.0, < 3.13.18

3.13.18

8.2 High

CVSS4

Дефекты

CWE-252

8.2 High

CVSS4

Дефекты

CWE-252