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

exploitDog

github логотип

GHSA-6588-rqcr-59pw

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

Описание

AMQP 1.0 post-body properties bypass user-ID validation and reach consumers

Summary

RabbitMQ's AMQP 1.0 server parser stops as soon as it encounters the first message body section. It does not validate the remaining message section order. The unparsed tail is stored and forwarded byte-for-byte.

An authenticated AMQP 1.0 publisher can send a valid body followed by a properties section containing a forged user-id. RabbitMQ treats the message as having no user-id when it executes its authenticated publisher check. Downstream AMQP 1.0 clients that do not enforce section ordering parse the late properties and expose the forged identity to the consumer. RabbitMQ's AMQP 1.0-to-0-9-1 conversion path also installs the late properties, exposing the forged user-id to AMQP 0-9-1 consumers.

This creates a message parser differential across RabbitMQ's validation boundary:

RabbitMQ validation view: user-id is absent Consumer view: user-id is attacker-controlled

The repository's own AMQP 1.0 client accepts properties after the body and exposes the forged user-id.

Affected revision

Confirmed against:

f02b3c6b52529d6809c56c2516f3dc4333605688

Confirmed with Erlang/OTP 27.

The affected release range has not been established. This report only claims the revision above.

Affected components

  • deps/amqp10_common/src/amqp10_binary_parser.erl
  • deps/rabbit/src/mc_amqp.erl
  • deps/rabbit/src/rabbit_amqp_session.erl
  • deps/rabbit/src/rabbit_access_control.erl
  • deps/amqp10_client/src/amqp10_msg.erl

Attack requirements

  • The attacker can authenticate to RabbitMQ over AMQP 1.0
  • The attacker has write permission to a target exchange or queue
  • A victim consumes the message through an AMQP 1.0 decoder that accepts known sections after the body, or through a cross-protocol conversion path that decodes the complete bare message
  • The victim uses the message user-id or other late metadata as an authenticated identity signal

The attacker does not need the RabbitMQ impersonator tag. The broker never observes the forged field during check_user_id.

AMQP section-order violation

AMQP 1.0 message sections have a defined order. Properties belong before application properties and before all body sections. The proof of concept sends:

1. data body 2. properties

The payload is:

00 53 75 a0 01 5a 00 53 73 c0 09 02 40 a0 05 61 64 6d 69 6e

Decoded:

00 53 75 data descriptor a0 01 5a binary body "Z" 00 53 73 properties descriptor c0 09 02 list8, size 9, field count 2 40 message-id = null a0 05 61 64 6d 69 6e user-id = binary "admin"

A normal order-insensitive decoder returns:

[ #'v1_0.data'{content = <<"Z">>}, #'v1_0.properties'{user_id = {binary, <<"admin">>}} ]

Root cause

In server_mode, the parser returns immediately when the first body descriptor is recognized:

pm(<<?DESCRIBED, ?CODE_SMALL_ULONG, ?DESCRIPTOR_CODE_DATA, _Rest/binary>>, true, B) -> reached_body(B, ?DESCRIPTOR_CODE_DATA).

reached_body/2 does not validate the body value or any trailing sections:

reached_body(Position, DescriptorCode) -> [{{pos, Position}, {body, DescriptorCode}}].

mc_amqp:init/1 uses this mode for inbound AMQP 1.0 messages:

init(Payload) -> Sections = amqp10_framing:decode_bin(Payload, [server_mode]), Msg = msg_body_encoded(Sections, Payload, #msg_body_encoded{}), Anns = essential_properties(Msg, new), {Msg, Anns}.

The body marker causes msg_body_encoded/3 to preserve the entire remaining payload:

msg_body_encoded([{{pos, Pos}, {body, Code}}], Payload, Msg) when is_binary(Payload) -> Bin = binary_part_bare_and_footer(Payload, Pos), Msg#msg_body_encoded{ bare_and_footer = Bin, bare_and_footer_body_pos = 0, body_code = Code }.

The illegal post-body properties are therefore absent from the broker's decoded properties record but remain present in bare_and_footer.

User-ID validation bypass

After message initialization, the AMQP session performs:

check_user_id(Mc1, User)

rabbit_access_control:check_user_id/2 explicitly permits an omitted user-id:

check_user_id(Message, ActualUser) -> case mc:user_id(Message) of undefined -> ok; {binary, ClaimedUserName} -> check_user_id0(ClaimedUserName, ActualUser) end.

If RabbitMQ had parsed the late properties, a non-impersonator authenticated as another user would be rejected:

check_user_id0(ClaimedUserName, #user{username = ActualUserName, tags = Tags}) -> case lists:member(impersonator, Tags) of true -> ok; false -> {refused, "user_id property set to '~ts' but authenticated user was '~ts'", [ClaimedUserName, ActualUserName]} end.

Because the server parser stopped at the body, mc:user_id(Message) returns undefined and validation succeeds.

Storage and forwarding

mc_amqp:prepare(store, ...) preserves bare_and_footer without reparsing. End-to-end queue tests confirmed that classic, quorum, and stream queues retain the late properties for AMQP 1.0 delivery. Classic and quorum queues also retain them through AMQP 0-9-1 conversion.

When RabbitMQ sends the message to an AMQP 1.0 consumer, mc_amqp:protocol_state/2 regenerates the header and message annotations but appends the raw bare message unchanged:

protocol_state(#v1{ message_annotations = MA0, bare_and_footer = BareAndFooter}, Anns) -> %% Header and message annotations are generated above. Sections = to_sections(Header, MA, []), [encode(Sections), BareAndFooter].

The forwarded section sequence is therefore:

header, data, properties

The illegal properties section remains visible to the consumer.

Consumer behavior

The RabbitMQ AMQP 1.0 client decodes all sections and folds them in wire order. Its message constructor does not enforce that properties precede the body:

parse_from_amqp(#'v1_0.properties'{} = Properties, Msg) -> Msg#amqp10_msg{properties = Properties}; parse_from_amqp(#'v1_0.data'{} = Data, Msg = #amqp10_msg{body = Body0}) -> Body = if Body0 =:= unset -> [Data]; is_list(Body0) -> [Data | Body0] end, Msg#amqp10_msg{body = Body}.

When properties occur after data, the body is retained and the later properties record is installed. The consumer sees {binary, <<"admin">>} as the message user-id.

Strict clients that reject out-of-order sections are not vulnerable to identity spoofing, but they can reject or disconnect on the stored message.

Minimal deterministic proof of concept

After building the core broker from the repository root with make -C deps/rabbit (gmake on systems where GNU Make is not the default), run:

erl -noshell -pa deps/*/ebin -eval ' Payload = << 0, 16#53, 16#75, 16#a0, 1, $Z, 0, 16#53, 16#73, 16#c0, 9, 2, 16#40, 16#a0, 5, "admin" >>, Fast = amqp10_binary_parser:parse_many(Payload, [server_mode]), Message = mc:init(mc_amqp, Payload, #{}), ServerUserId = mc:property(user_id, Message), Stored = mc:prepare(store, Message), Forwarded = iolist_to_binary(mc:protocol_state(Stored)), Sections = amqp10_framing:decode_bin(Forwarded), ConsumerUserId = element(3, lists:last(Sections)), io:format("FAST=~0p~nSERVER_USER_ID=~0p~nCONSUMER_USER_ID=~0p~n", [Fast, ServerUserId, ConsumerUserId]), [{{pos, 0}, {body, 117}}] = Fast, undefined = ServerUserId, {binary, <<"admin">>} = ConsumerUserId, halt(). '

Expected output:

FAST=[{{pos,0},{body,117}}] SERVER_USER_ID=undefined CONSUMER_USER_ID={binary,<<"admin">>}

This minimal proof directly compares the identity seen by RabbitMQ's validation path with the identity exposed after storage serialization and normal consumer decoding.

Security impact

Confirmed:

  • An internal user without the impersonator tag can publish an arbitrary late user-id after the body
  • RabbitMQ's publisher identity check sees the field as absent
  • A legal-order control carrying the same mismatched user-id is rejected
  • Classic, quorum, and stream queues preserve the late properties for AMQP 1.0 delivery
  • The repository AMQP 1.0 client exposes the forged user-id to applications
  • AMQP 0-9-1 conversion exposes the forged user-id through classic and quorum queues

This can violate application-level integrity when consumers rely on RabbitMQ's validated user-id semantics to identify the publisher.

This is a meaningful validation-boundary conflict despite the illegal section order. RabbitMQ documents that when user-id is set, its value is validated against the authenticated connection user, and that the administrator tag does not permit forgery. Consumers can therefore reasonably treat a delivered user-id as broker-validated. Strict AMQP 1.0 consumers that reject out-of-order sections are not vulnerable to identity spoofing, but can reject or disconnect on the stored entry.

The same parser differential can apply to other known sections placed after the body. For example, a late message-annotations section can disagree with the message annotations RabbitMQ parsed or generated. The precise impact depends on whether the downstream decoder accepts and prioritizes the later section.

Not demonstrated:

  • Broker exchange or routing-key manipulation
  • Resource or topic authorization bypass
  • Publishing to an unauthorized destination
  • RabbitMQ node compromise
  • Remote code execution

RabbitMQ routing and authorization use only metadata parsed before the body. The issue changes the metadata observed by downstream consumers, not the broker's routing decision.

Severity

Suggested severity: moderate when validated user-id is used as a security or audit identity by downstream consumers.

The attack requires an authenticated publisher with write permission, but it allows that publisher to impersonate another RabbitMQ username to consumers without the impersonator tag.

Severity is lower where consumers ignore user-id, treat all message properties as untrusted application input, or enforce AMQP section ordering independently.

Recommended remediation

  • Continue scanning after the first body marker sufficiently to validate legal section ordering
  • Reject properties, application-properties, annotations, or headers that occur after a body section
  • Reject a second body kind or any non-footer section after the body sequence
  • Validate the body value and footer rather than treating the entire tail as opaque untrusted bytes
  • Ensure check_user_id is based on the same canonical message interpretation delivered to consumers
  • Make the AMQP client reject illegal section ordering and duplicate singleton sections

An optimized implementation can still avoid materializing large body values. It only needs to walk encoded section boundaries safely and validate descriptor identity, cardinality, and order.

Regression tests

Add tests for:

  • Data followed by properties containing another user's user-id
  • Data followed by message annotations
  • Data followed by application properties
  • Data followed by header or delivery annotations
  • Sequence followed by properties
  • Value followed by properties
  • Duplicate properties before the body
  • Multiple legal data sections followed by footer
  • Classic, quorum, and stream persistence
  • Same-protocol AMQP 1.0 delivery
  • Cross-protocol delivery
  • A publisher with and without the impersonator tag
  • Consumers using the repository AMQP 1.0 client

The primary assertion should compare:

user-id observed by rabbit_access_control:check_user_id user-id observed by the downstream AMQP client

These values must never differ.

Пакеты

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

rabbitmq

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

>= 4.3.0, < 4.3.5

4.3.5

5.3 Medium

CVSS4

Дефекты

CWE-20
CWE-345
CWE-436

5.3 Medium

CVSS4

Дефекты

CWE-20
CWE-345
CWE-436