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

exploitDog

github логотип

GHSA-w9hf-476r-443x

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

Описание

AMQP 1.0 management GET /bindings exposes full binding topology to any authenticated AMQP user without resource/management permission checks

Summary

The AMQP 1.0 HTTP-over-AMQP management endpoint GET /bindings (the rabbit_amqp_management handler) enumerates bindings between an arbitrary source exchange and destination queue/exchange in the caller's virtual host without performing any resource-level permission check. Unlike every sibling operation in the same module (which call check_resource_access / binding_checks), the GET handler ignores the authenticated User and returns the binding list unchanged. As a result, any authenticated AMQP 1.0 client that can open a management link pair — including users with no management/monitoring/policymaker/administrator tag — can enumerate the complete binding topology (source exchanges, destination queues/exchanges, routing keys, and binding arguments) of the virtual host they can access. The equivalent HTTP management API (GET /api/bindings) requires a management-tagged user; the AMQP 1.0 path does not, so this crosses a documented confidentiality boundary.

Details

Component: deps/rabbit/src/rabbit_amqp_management.erl, the HTTP-over-AMQP management API exposed via the /management AMQP 1.0 terminus (link pair). Verified at repository HEAD e3005f0ba2b699b31b667b0214a2cddfb7ba07dc and in released tags v4.0.0, v4.2.8, v4.3.2.

The vulnerable handler at deps/rabbit/src/rabbit_amqp_management.erl:380-405:

handle_http_req(<<"GET">>, [<<"bindings">>], QueryMap = #{<<"src">> := SrcXNameBin, <<"key">> := Key}, null, Vhost, _User, %% deliberately unused: NO permission check _ConnPid, PermCaches) -> {DstKind, DstNameBin} = case QueryMap of #{<<"dste">> := DstX} -> {exchange, DstX}; #{<<"dstq">> := DstQ} -> {queue, DstQ}; _ -> throw(<<"400">>, "missing 'dste' or 'dstq' in query: ~tp", [QueryMap]) end, SrcXName = exchange_resource(Vhost, SrcXNameBin), DstName = rabbit_misc:r(Vhost, DstKind, DstNameBin), Bindings0 = rabbit_binding:list_for_source_and_destination(SrcXName, DstName), Bindings = [B || B = #binding{key = K} <- Bindings0, K =:= Key], RespPayload = encode_bindings(Bindings), {<<"200">>, RespPayload, PermCaches}. %% PermCaches returned unchanged

_User is underscore-prefixed (unused) and PermCaches is returned unchanged. No check_resource_access (read/write/configure), check_resource_any_access, or binding_checks is invoked. The response (encode_bindings) discloses source name, destination name + kind (queue or exchange), the binding key, the binding arguments, and a resolvable binding location URI (compose_binding_uri, rabbit_amqp_management.erl:603-612).

This is asymmetric with every other operation in the same module, which all enforce permissions:

  • GET /queues/{name} (rabbit_amqp_management.erl:94): check_resource_any_access.
  • POST /bindings (rabbit_amqp_management.erl:347): binding_checks — checks write on destination, read on source, topic read.
  • DELETE /bindings/{segment} (rabbit_amqp_management.erl:370): binding_checks.
  • The binding_checks helper (rabbit_amqp_management.erl:665-685) is exactly the check the GET handler omits.

The confidentiality boundary crossing is confirmed by the management HTTP API, which exposes the same data only to management-tagged users. GET /api/bindings is gated by rabbit_mgmt_wm_bindings.erl:112-113 -> is_authorized_vhost, and the base is_authorized requires the caller to satisfy is_mgmt_user/1 (deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_access_control.erl): intersects(Tags, [administrator, monitoring, policymaker, management]). A plain AMQP producer/consumer account that has no management tags cannot call GET /api/bindings at all.

The AMQP 1.0 management link, by contrast, is gated only by check_paired/1 (rabbit_amqp_session.erl:3950-3963), which verifies only that the client set the paired=true link property. There is no tag, administrator, or management-permission check on handle_attach for management links (rabbit_amqp_session.erl:1340-1373) or on rabbit_amqp_management:handle_request/5 (rabbit_amqp_session.erl:2427). Therefore a non-management AMQP 1.0 user that has virtual-host access can open a management link pair and read the full binding topology.

Root cause: missing authorization check (the same class of "add missing permission check" bug the maintainers have been fixing across protocols, e.g. commits e8d2ad5c29 for AMQP 1.0 alternate-exchange and the stream-permission cluster 53af22f106/a2e66b9a4b/58f7bd081b/414674e394/7f7e9580cf/cd64faa2ac).

PoC

Static reproduction (no live broker started; this audit environment has no Erlang toolchain, so the steps below are a maintainer-runnable recipe derived directly from the verified source path).

Goal: demonstrate that a user with no management tags can read all bindings in a vhost via AMQP 1.0, while the same data is denied via the HTTP management API.

Preconditions / configuration (default RabbitMQ with the AMQP 1.0 listener enabled, which is the default for RabbitMQ 4.x):

  1. A vhost / with at least two exchanges and a binding that should be private to a privileged operator, e.g.:
    • exchange ops.private (the "secret" source)
    • queue ops.sink bound to ops.private with routing key secret.event
  2. A low-privilege AMQP user appuser with permissions only on its own resources, e.g. rabbitmqctl set_permissions -p / appuser "^appuser\." "^appuser\." "^appuser\." and NO management tags (rabbitmqctl set_user_tags appuser with no tags).

Positive trigger (uses an AMQP 1.0 client, e.g. the Apache Qpid Proton Python client or RabbitMQ rabbitmq_amqp_client):

# Open an AMQP 1.0 connection as appuser (no management tags) to amqp://appuser:...@localhost:5672/%2F # Attach a management link pair (target address = "/management", link property "paired" = true) # Send an HTTP-over-AMQP request: # method = GET # target = /bindings?src=ops.private&key=secret.event&dstq=ops.sink # The server responds with the binding record for ops.private -> ops.sink [secret.event].

Expected vulnerable output (observed behavior per handle_http_req GET /bindings):

200 OK amqp_value(map) { {utf8:"source"} = {utf8:"ops.private"}, {utf8:"destination_queue"} = {utf8:"ops.sink"}, {utf8:"binding_key"} = {utf8:"secret.event"}, {utf8:"arguments"} = map{}, {utf8:"location"} = {utf8:"/bindings/src=ops.private;dstq=ops.sink;key=secret.event;args="} }

Negative / control case (same user, same data, via the HTTP management API) — demonstrates the boundary the AMQP 1.0 path fails to enforce:

curl -u appuser:'<password>' 'http://localhost:15672/api/bindings/%2F/e/ops.private/q/ops.sink' # HTTP 401 Unauthorized ("User not authorised to access virtual host" / management tag required)

Expected control output: 401 Unauthorized — because appuser has no management tag and is_mgmt_user/1 rejects it before any binding data is returned. The AMQP 1.0 GET path returns the same data with no such gate, confirming the asymmetry.

Because this audit environment lacks an Erlang toolchain (erl/rebar3/mix are not installed), a live broker was not started; the PoC above is the exact maintainer-runnable recipe derived from the verified code path. The source-level proof is the unused _User parameter and the unchanged PermCaches in the GET handler, contrasted with the binding_checks call present in the POST/DELETE handlers and the is_mgmt_user gate in the HTTP API. Reproduction re-check after drafting this report: re-read deps/rabbit/src/rabbit_amqp_management.erl:380-405 at HEAD — _User is still unused and no check_resource_access/binding_checks call was added; the finding stands.

Cleanup: no broker state is modified by this read-only PoC; tear down the temporary appuser, the ops.private/ops.sink resources, or the whole test vhost as desired.

Impact

Confidentiality impact: a non-management AMQP 1.0 user can enumerate the complete routing topology of any virtual host it can connect to — every (source exchange, destination queue/exchange, routing key, binding arguments) triple. This discloses routing architecture that the RabbitMQ permission model is specifically designed to protect: it reveals which exchanges route to which queues (and with what keys), which is otherwise gated behind a management/monitoring/policymaker/administrator tag in the HTTP management API. In multi-tenant deployments where tenants share a vhost but are restricted to their own resources via fine-grained read/write/configure permissions, any tenant can map every other tenant's producers, consumers, and routing keys. Knowledge of routing keys and exchange-to-queue mappings also materially aids targeted message-injection, queue-enumeration, and lateral-movement planning. Integrity and availability are not directly affected by this read path.

Credits

  • Thai Son Dinh from VinSOC Labs (R&D)

Пакеты

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

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

6.3 Medium

CVSS4

Дефекты

CWE-285
CWE-862

6.3 Medium

CVSS4

Дефекты

CWE-285
CWE-862