From 429eaff5cad5bf9182a2eedafaf66cbb8fca4451 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Fri, 26 Apr 2024 14:24:08 -0700 Subject: [PATCH 1/5] RocketMQ fixes --- lib/msf/core/auxiliary/rocketmq.rb | 21 +++++++++++++++---- .../http/apache_rocketmq_update_config.rb | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 676aa61e2ce2..3554eb62ba74 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -25,10 +25,12 @@ def send_version_request begin connect sock.send(header + data_length + data, 0) - res = sock.recv(1024) + res_length = sock.timed_read(4).unpack1('N') + res = sock.timed_read(res_length) rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e print_error("Unable to connect: #{e.class} #{e.message}\n#{e.backtrace * "\n"}") - elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") + elog('Error sending the rocketmq version request', error: e) + return nil ensure disconnect end @@ -64,7 +66,11 @@ def get_rocketmq_version(id) # @return [Hash] Hash including RocketMQ versions info and Broker info if found def parse_rocketmq_data(res) # remove a response header so we have json-ish data - res = res[8..] + res = res.split(/\x00_/)[1] + unless res.starts_with?("{") + print_error("Failed to successfully remove the response header and now cannot parse the response.") + return nil + end # we have 2 json objects appended to each other, so we now need to split that out and make it usable res = res.split('}{') @@ -111,14 +117,21 @@ def get_broker_port(broker_datas, rhost, default_broker_port: 10911) # Example of brokerData: # [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}] + if broker_datas['brokerDatas'].blank? + print_status("brokerDatas field is missing from the response, assuming default broker port of #{default_broker_port}") + return default_broker_port + end broker_datas['brokerDatas'].each do |broker_data| + if broker_data['brokerAddrs'].blank? + print_status("brokerAddrs field is missing from the response, assuming default broker port of #{default_broker_port}") + return default_broker_port + end broker_data['brokerAddrs'].values.each do |broker_endpoint| next unless broker_endpoint.start_with?("#{rhost}:") return broker_endpoint.match(/\A#{rhost}:(\d+)\z/)[1].to_i end end - print_status("autodetection failed, assuming default port of #{default_broker_port}") default_broker_port end diff --git a/modules/exploits/multi/http/apache_rocketmq_update_config.rb b/modules/exploits/multi/http/apache_rocketmq_update_config.rb index fd01864249b7..783fe9a834b1 100644 --- a/modules/exploits/multi/http/apache_rocketmq_update_config.rb +++ b/modules/exploits/multi/http/apache_rocketmq_update_config.rb @@ -72,6 +72,8 @@ def initialize(info = {}) def check @version_request_response = send_version_request + return Exploit::CheckCode::Unknown('Unable to determine the version') unless @version_request_response + @parsed_data = parse_rocketmq_data(@version_request_response) return Exploit::CheckCode::Unknown('RocketMQ did not respond to the request for version information') unless @parsed_data['version'] From 3b57fbf0522f01aca0061f9fb39fcd043effadba Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Fri, 26 Apr 2024 14:25:16 -0700 Subject: [PATCH 2/5] ActiveMQ fixes --- .../multi/misc/apache_activemq_rce_cve_2023_46604.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb b/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb index 4f8e45ff16c8..4baeff1b2b86 100644 --- a/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb +++ b/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb @@ -80,15 +80,19 @@ def initialize(info = {}) def check connect - res = sock.get_once + len = sock.timed_read(4).unpack1('N') + + return CheckCode::Unknown if len > 0x2000 # upper limit in case the service isn't ActiveMQ + + res = sock.timed_read(len) disconnect return CheckCode::Unknown unless res - len, _, magic = res.unpack('NCZ*') + _, magic = res.unpack('CZ*') - return CheckCode::Unknown unless res.length == len + 4 + return CheckCode::Unknown unless res.length == len return CheckCode::Unknown unless magic == 'ActiveMQ' @@ -110,6 +114,8 @@ def check end Exploit::CheckCode::Safe("Apache ActiveMQ #{version}") + rescue ::Timeout::Error + CheckCode::Unknown end def exploit From f5f1deaf5b07d53a9df54ec73e5774dd7097cb4b Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Fri, 26 Apr 2024 14:55:45 -0700 Subject: [PATCH 3/5] Untested attempt to fix spec --- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index c541734130db..aa0642da445a 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -9,11 +9,11 @@ end let(:mock_name_server_response) do - "\x00\x00\x00\xC7\x00\x00\x00\xC3{\"code\":105,\"extFields\":{\"Signature\":\"/u5P/wZUbhjanu4LM/UzEdo2u2I=\",\"topic\":\"TBW102\",\"AccessKey\":\"rocketmq2\"},\"flag\":0,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":401}".b + "\x00\x00\x00_{\"code\":105,\"extFields\":{\"Signature\":\"/u5P/wZUbhjanu4LM/UzEdo2u2I=\",\"topic\":\"TBW102\",\"AccessKey\":\"rocketmq2\"},\"flag\":0,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":401}".b end let(:expected_name_server_response) do - "\x00\x00\x01a\x00\x00\x00_{\"code\":0,\"flag\":1,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":403}{\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"172.16.199.135:10911\"},\"brokerName\":\"DESKTOP-8ATHH6O\",\"cluster\":\"DefaultCluster\"}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"DESKTOP-8ATHH6O\",\"perm\":7,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}".b + "\x00\x00\x00_{\"code\":0,\"flag\":1,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":403}{\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"172.16.199.135:10911\"},\"brokerName\":\"DESKTOP-8ATHH6O\",\"cluster\":\"DefaultCluster\"}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"DESKTOP-8ATHH6O\",\"perm\":7,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}".b end let(:expected_parsed_data_response) do From 6c74d14bb7534b2d526b2ffbb548e580421745d4 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Mon, 29 Apr 2024 08:54:57 -0700 Subject: [PATCH 4/5] Tested attempt to fix rspec --- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index aa0642da445a..ce961b460969 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -9,7 +9,7 @@ end let(:mock_name_server_response) do - "\x00\x00\x00_{\"code\":105,\"extFields\":{\"Signature\":\"/u5P/wZUbhjanu4LM/UzEdo2u2I=\",\"topic\":\"TBW102\",\"AccessKey\":\"rocketmq2\"},\"flag\":0,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":401}".b + "\x00\x00\x00\xC7\x00\x00\x00\xC3{\"code\":105,\"extFields\":{\"Signature\":\"/u5P/wZUbhjanu4LM/UzEdo2u2I=\",\"topic\":\"TBW102\",\"AccessKey\":\"rocketmq2\"},\"flag\":0,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":401}".b end let(:expected_name_server_response) do @@ -55,6 +55,9 @@ describe '#send_version_request' do it 'returns version info' do expect(mock_sock).to receive(:send).with(mock_name_server_response, 0) + expect(mock_sock).to receive(:timed_read).with(4).and_return([expected_name_server_response.length].pack('N')) + expect(mock_sock).to receive(:timed_read).with(expected_name_server_response.length).and_return(expected_name_server_response) + expect(subject.send_version_request).to eq(expected_name_server_response) end end @@ -74,4 +77,4 @@ expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.1', default_broker_port: 10000)).to eq(10000) end end -end \ No newline at end of file +end From 6055d8a005e1b33099b8e866fa18a1b61b2cc00b Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Mon, 29 Apr 2024 16:15:50 -0400 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Spencer McIntyre <58950994+smcintyre-r7@users.noreply.github.com> --- lib/msf/core/auxiliary/rocketmq.rb | 4 +++- .../exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 3554eb62ba74..9b9757ff241c 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -25,7 +25,9 @@ def send_version_request begin connect sock.send(header + data_length + data, 0) - res_length = sock.timed_read(4).unpack1('N') + res_length = sock.timed_read(4)&.unpack1('N') + return nil if res_length.nil? + res = sock.timed_read(res_length) rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e print_error("Unable to connect: #{e.class} #{e.message}\n#{e.backtrace * "\n"}") diff --git a/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb b/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb index 4baeff1b2b86..7cfbceee6bad 100644 --- a/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb +++ b/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb @@ -80,9 +80,9 @@ def initialize(info = {}) def check connect - len = sock.timed_read(4).unpack1('N') + len = sock.timed_read(4)&.unpack1('N') - return CheckCode::Unknown if len > 0x2000 # upper limit in case the service isn't ActiveMQ + return CheckCode::Unknown if len.nil? || len > 0x2000 # upper limit in case the service isn't ActiveMQ res = sock.timed_read(len)