ActiveMQ Classic ships with a web console on port 8161 and well-known default credentials: admin/admin. Combined with a management interface reachable from the network, that equals full broker control for anyone who finds the port: browse and purge queues, move messages, read message bodies, create destinations, and in some versions reach the Jolokia JMX REST API for much worse.

Newer releases have improved the defaults. Since 5.16.x and 5.18.x, the embedded Jetty console binds to 127.0.0.1 and the remote JMX connector is disabled (createConnector="false") out of the box. These defaults are routinely undone: operators reconfigure the bind address for remote management, container images override it, and older brokers never had the protection. A large share of production brokers still expose 8161 with credentials that are in every scanner’s wordlist.

This guide is an audit and lockdown procedure: determine what your brokers actually expose, close the hole without losing the ability to operate, and monitor so it stays closed.

Why this exposure is worse than it looks

The web console is not a read-only dashboard. An authenticated admin session can browse message bodies (which often contain business data or credentials from upstream systems), purge queues, delete destinations, and send arbitrary messages. Through the Jolokia endpoint under /api/jolokia, an authenticated user can invoke JMX operations remotely, which is a short path to code execution on the broker host.

The exposure compounds with deserialization and RCE CVEs that require valid credentials or network reachability as a precondition:

  • CVE-2023-46604: critical RCE in OpenWire ClassInfo deserialization (CVSS 10.0), actively exploited in the wild. Patched in 5.15.16, 5.16.7, 5.17.6, and 5.18.3. An exposed OpenWire port (61616) is its own emergency; an exposed console with admin/admin is how attackers enumerate and validate targets.
  • CVE-2015-5254: Commons Collections deserialization RCE. Mitigate by whitelisting org.apache.activemq.SERIALIZABLE_PACKAGES.
  • Recent advisories (for example, unauthenticated /api/* contexts in early 6.x releases, and Jolokia-based RCE requiring valid credentials) keep the same shape: the attack is trivial when the console is reachable and the password is admin.

The broker’s management plane is a separate attack surface from its data plane, and both need explicit scoping.

flowchart LR
  attacker[Network attacker] -->|8161 with admin/admin| console[Jetty web console]
  attacker -->|61616 OpenWire| broker[Broker core]
  console -->|browse, purge, send| broker
  console -->|/api/jolokia JMX ops| jmx[JMX / MBeans]
  jmx -->|invoke operations| host[Broker host code exec]
  broker -->|deserialization CVEs| host

Step 1: map what is actually listening

Do not trust the config file. Trust the socket table. Run this on every broker host:

# What is listening on management and transport ports, and on which address
ss -tlnp | grep -E ':(8161|1099|61616|5672|61613|1883|61614)'

# Who is currently connected to the management interfaces
ss -tn state established '( sport = :8161 )'
ss -tn state established '( sport = :1099 )'

What you are looking for:

FindingMeaningRisk
127.0.0.1:8161Console bound to localhost onlyLow; matches 5.16+/5.18.x default
0.0.0.0:8161 or a routable IPConsole reachable from the networkHigh until credentials and network policy are verified
0.0.0.0:1099Remote JMX connector enabledHigh; remote JMX without TLS and auth is arbitrary code execution territory
0.0.0.0:61616OpenWire reachable from networkExpected for clients, but must be patched against CVE-2023-46604 and authenticated

If the broker runs in a container, check the published ports on the host too. ss inside the container shows the bind, but the host’s port mapping determines real reachability:

docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep activemq

Some container images also override the localhost bind: the official ActiveMQ Classic 6.x image sets -Djetty.host=0.0.0.0, making the console remotely reachable by default in Docker even though a bare-metal install of the same version would not be.

Also test reachability from a host that should not have access. A bind to 0.0.0.0 behind a security group that only allows the management VLAN is a different risk than the same bind open to the whole corporate network.

Step 2: test the credentials

From an allowed management host:

# Does the console answer with default credentials?
curl -s -o /dev/null -w '%{http_code}\n' \
  -u admin:admin http://<broker-host>:8161/admin/

# Does Jolokia answer with default credentials? (read-only query)
curl -s -u admin:admin \
  'http://<broker-host>:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/BrokerId'

A 200 on either, using admin/admin, is a finding. A 200 from a host outside your management network is an incident: rotate the credentials, review access logs, and check for destinations, consumers, or network connectors you did not create.

Console credentials live in conf/jetty-realm.properties in a simple username: password, role format. Broker transport authentication (OpenWire and friends) is separate, typically configured in activemq.xml via a JAAS or simple authentication plugin, and it commonly also ships with admin/admin. Audit both planes. They are independent: fixing the console does nothing for port 61616 and vice versa.

While you are in the config, check the two defaults that matter most:

# Jetty bind host in conf/jetty.xml (absence or 0.0.0.0 means network-reachable)
grep -n -A2 'jettyPort' conf/jetty.xml | grep -i host

# Remote JMX connector in conf/activemq.xml
grep -n 'createConnector' conf/activemq.xml

On 5.16+/5.18.x defaults, Jetty has a localhost bind and the management context has createConnector="false". If either line says otherwise, someone opened it deliberately or it was never closed.

Step 3: lock it down

In order of preference:

1. Keep management on localhost or a management VLAN. If operators need the console remotely, reach it through an SSH tunnel (ssh -L 8161:localhost:8161 broker-host) or a jump host rather than binding Jetty to 0.0.0.0. If a network bind is unavoidable, restrict it to a specific management interface IP in conf/jetty.xml and enforce a host firewall or security group rule that allows only known management sources.

2. Change the default credentials on both planes. Edit conf/jetty-realm.properties for the console and the authentication plugin in activemq.xml for transports. Use unique per-broker credentials so one leaked password is not a fleet-wide compromise. A broker restart is required for config changes; plan it like any other restart, and in shared-storage HA remember the standby will acquire the store lock while the active is down.

3. Disable remote JMX unless you need it. Keep createConnector="false". For monitoring, prefer local JMX collection or a read-only Jolokia policy over opening 1099 to the network.

4. Restrict what authenticated users can do. Default deployments historically left low-privilege web users able to reach /admin/* and Jolokia broker-management operations. Patch to current versions and scope roles so only genuine admin accounts hold the admin role.

5. Turn the console off if nobody uses it. If all operations go through JMX, CLI, or config management, removing or not starting the web console webapp eliminates the surface entirely. Verify nothing in your runbooks depends on it first.

6. Patch. An exposed OpenWire port on an unpatched broker is exploitable regardless of console hygiene. Confirm you are on or past the CVE-2023-46604 fix lines (5.15.16, 5.16.7, 5.17.6, 5.18.3 or later in each train).

Step 4: verify and keep it closed

After changes:

# Bind addresses are what you intended
ss -tlnp | grep -E ':(8161|1099)'

# Default credentials now fail
curl -s -o /dev/null -w '%{http_code}\n' -u admin:admin http://localhost:8161/admin/

# New credentials work from an allowed source only
curl -s -o /dev/null -w '%{http_code}\n' -u <user>:<pass> http://localhost:8161/admin/

Expect 401/403 for the default creds and a refusal or timeout from non-management hosts. Then add permanent tripwires:

SignalWhy it mattersWarning sign
Established connections to 8161/1099Management access should only come from known hostsAny session from a non-allowlisted source
Authentication failure rate in the broker logDefault creds mean scanners will knockSustained failures, or failures from multiple source IPs with different usernames
Authorization denial log linesAuthenticated user probing beyond its roleRepeated not authorized entries for admin operations
Unexpected destination creationAuto-create on first use plus broad access lets attackers or rogue clients shape the brokerDestinations outside expected naming patterns
Bind/config driftThe hole is usually reopened by a well-meaning change8161 or 1099 suddenly listening on 0.0.0.0

Treat any access to a supposedly localhost-only interface from an untrusted network as page-worthy. Sporadic auth failures from a single source are a ticket; a broad failure burst right after a credential rotation usually means a client you forgot to update, not an attack.

Common pitfalls

  • Trusting conf/jetty.xml over ss. Container entrypoints, systemd drop-ins, and -Djetty.host on the command line all override what the XML suggests. The socket table is the truth.
  • Fixing the console and forgetting 61616. Transport authentication is a separate config with its own defaults. Attackers do not care which plane they enter through.
  • Rotating credentials without checking what breaks. Monitoring that authenticates to Jolokia with admin/admin will fail after rotation. Update collectors at the same time.
  • Assuming HA standby is safe. Both brokers in a pair run Jetty. Audit both.
  • Reopening the bind for a one-off debug session and never closing it. This is how most exposures happen. If you open it, put the close step in the same change ticket.

How Netdata helps

  • Netdata’s per-second network and socket visibility on the broker host makes a surprise listener or an unexpected established session on 8161 or 1099 visible immediately, rather than at the next quarterly audit.
  • Correlating connection spikes to management ports with authentication failure patterns in the broker log distinguishes a scanner from a broken client after credential rotation.
  • Broker-level signals (destination count, consumer count, connection count) act as integrity checks: an intruder with console access eventually moves one of these, and anomaly detection on them catches what the access logs miss.
  • Alerting on unexpected destination creation and auth failure bursts turns the lockdown from a point-in-time exercise into a continuously verified state.