From 4f661ff230ef230fe04d7471abd8309469129276 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 16 Jun 2023 15:36:06 -0400 Subject: [PATCH 01/11] rocketmq version lib --- lib/msf/core/auxiliary/rocketmq.rb | 77 +++++++++++++++++++ .../scanner/misc/rocketmq_version.rb | 64 +++------------ spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 23 ++++++ 3 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 lib/msf/core/auxiliary/rocketmq.rb create mode 100644 spec/lib/msf/core/auxiliary/rocketmq_spec.rb diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb new file mode 100644 index 0000000000..327c45f0d0 --- /dev/null +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -0,0 +1,77 @@ +# -*- coding: binary -*- + +module Msf + ### + # + # This module provides methods for working with Arista equipment + # + ### + module Auxiliary::Rocketmq + def initialize(info = {}) + super + register_options([ Opt::RPORT(9876) ], Msf::Auxiliary::Rocketmq) + end + + def send_version_request + # sends a version request to the service, and returns the data as a list of hashes. nil on error + # https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L68 + data = '{"code":105,"extFields":{"Signature":"/u5P/wZUbhjanu4LM/UzEdo2u2I=","topic":"TBW102","AccessKey":"rocketmq2"},"flag":0,"language":"JAVA","opaque":1,"serializeTypeCurrentRPC":"JSON","version":401}' + data_length = "\x00\x00\x00" + [data.length].pack('C') + header = "\x00\x00\x00" + [data.length + data_length.length].pack('C') + + begin + connect + vprint_status('Sending request') + sock.send(header + data_length + data, 0) + res = sock.recv(1024) + 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"}") + ensure + disconnect + end + + if res.nil? + vprint_error('No response received') + return nil + end + + unless res.include?('{') + vprint_error('Response contains unusable data') + return nil + end + + # remove a response header so we have json-ish data + res = res[8..] + + # we have 2 json objects appended to eachother, so we now need to split that out and make it usable + res = res.split('}{') + + jsonable = [] + # patch back in the { and } + res.each do |r| + r += '}' unless r.end_with?('}') + r = '{' + r unless r.start_with?('{') + jsonable.append(r) + end + + result = [] + jsonable.each do |j| + res = JSON.parse(j) + result.append(res) + rescue JSON::ParserError + vprint_error("Unable to parse json data: #{j}") + next + end + result + end + + def get_rocketmq_version(id) + # This function takes an ID (number) and looks through rocketmq's index of verison numbers to find the real version number + # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table + # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java + version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) + version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id})") + end + end +end diff --git a/modules/auxiliary/scanner/misc/rocketmq_version.rb b/modules/auxiliary/scanner/misc/rocketmq_version.rb index df3d0f75e0..8b8c85277d 100644 --- a/modules/auxiliary/scanner/misc/rocketmq_version.rb +++ b/modules/auxiliary/scanner/misc/rocketmq_version.rb @@ -6,6 +6,7 @@ class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report + include Msf::Auxiliary::Rocketmq def initialize(info = {}) super( @@ -31,74 +32,29 @@ class MetasploitModule < Msf::Auxiliary } ) ) - register_options([ Opt::RPORT(9876) ]) - end - - def get_version(id) - # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java - version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) - version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id})") end def run_host(_ip) - # https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L68 - data = '{"code":105,"extFields":{"Signature":"/u5P/wZUbhjanu4LM/UzEdo2u2I=","topic":"TBW102","AccessKey":"rocketmq2"},"flag":0,"language":"JAVA","opaque":1,"serializeTypeCurrentRPC":"JSON","version":401}' - data_length = "\x00\x00\x00" + [data.length].pack('C') - header = "\x00\x00\x00" + [data.length + data_length.length].pack('C') - - begin - connect - vprint_status('Sending request') - sock.send(header + data_length + data, 0) - res = sock.recv(1024) - 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"}") - ensure - disconnect - end + res = send_version_request if res.nil? - vprint_error('No response received') + print_error('Invalid or no response received') return end - unless res.include?('{') - vprint_error('Response contains unusable data') - return - end - - # remove a response header so we have json-ish data - res = res[8..] - - # we have 2 json objects appended to eachother, so we now need to split that out and make it usable - res = res.split('}{') - - jsonable = [] - # patch back in the { and } - res.each do |r| - r += '}' unless r.end_with?('}') - r = '{' + r unless r.start_with?('{') - jsonable.append(r) - end - parsed_data = {} # grab some data that we need/want out of the response - jsonable.each do |j| - begin - res = JSON.parse(j) - rescue JSON::ParserError - vprint_error("Unable to parse json data: #{j}") - next - end - parsed_data['version'] = get_version(res['version']).gsub('_', '.') if res['version'] - parsed_data['brokerDatas'] = res['brokerDatas'] if res['brokerDatas'] + res.each do |j| + parsed_data['version'] = get_rocketmq_version(j['version']).gsub('_', '.') if j['version'] + parsed_data['brokerDatas'] = j['brokerDatas'] if j['brokerDatas'] end - if parsed_data == {} + if parsed_data == {} || parsed_data['version'].nil? vprint_error('Unable to find version or other data within response.') return end - print_good("RocketMQ version #{parsed_data['version']} found with brokers: #{res['brokerDatas']}") + output = "RocketMQ version #{parsed_data['version']}" + output += " found with brokers: #{parsed_data['brokerDatas']}" if parsed_data['brokerDatas'] + print_good(output) end end diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb new file mode 100644 index 0000000000..6d2d105b86 --- /dev/null +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +RSpec.describe Msf::Auxiliary::Rocketmq do + subject do + mod = Msf::Module.new + mod.extend(Msf::Auxiliary::Rocketmq) + mod + end + + describe 'get_rocketmq_version' do + context 'correctly looks up id 401 as V4.9.4' do + it 'returns that version' do + expect(subject.get_rocketmq_version(401)).to eql('V4.9.4') + end + end + + context 'correctly looks up id 99999 as UNKNOWN_VERSION_ID_99999' do + it 'returns that version' do + expect(subject.get_rocketmq_version(99999)).to eql('UNKNOWN_VERSION_ID_99999') + end + end + end +end From 67225650de6c2e8c835efa3381dc6aa6b2b9279e Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 16 Jun 2023 16:13:36 -0400 Subject: [PATCH 02/11] convert _ to . --- lib/msf/core/auxiliary/rocketmq.rb | 2 +- modules/auxiliary/scanner/misc/rocketmq_version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 327c45f0d0..dbdfca5721 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -71,7 +71,7 @@ module Msf # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) - version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id})") + version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id})").gsub('_', '.') end end end diff --git a/modules/auxiliary/scanner/misc/rocketmq_version.rb b/modules/auxiliary/scanner/misc/rocketmq_version.rb index 8b8c85277d..8891ca07d9 100644 --- a/modules/auxiliary/scanner/misc/rocketmq_version.rb +++ b/modules/auxiliary/scanner/misc/rocketmq_version.rb @@ -45,7 +45,7 @@ class MetasploitModule < Msf::Auxiliary parsed_data = {} # grab some data that we need/want out of the response res.each do |j| - parsed_data['version'] = get_rocketmq_version(j['version']).gsub('_', '.') if j['version'] + parsed_data['version'] = get_rocketmq_version(j['version']) if j['version'] parsed_data['brokerDatas'] = j['brokerDatas'] if j['brokerDatas'] end From e49e70ce9350a4242f130c0636ba12851faddaa3 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 16 Jun 2023 16:26:35 -0400 Subject: [PATCH 03/11] update rocketmq tests --- lib/msf/core/auxiliary/rocketmq.rb | 2 +- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index dbdfca5721..9f0fe526bc 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -71,7 +71,7 @@ module Msf # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) - version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id})").gsub('_', '.') + version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id}").gsub('_', '.') end end end diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index 6d2d105b86..bfc9608cdc 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -14,9 +14,9 @@ RSpec.describe Msf::Auxiliary::Rocketmq do end end - context 'correctly looks up id 99999 as UNKNOWN_VERSION_ID_99999' do + context 'correctly looks up id 99999 as UNKNOWN.VERSION.ID.99999' do it 'returns that version' do - expect(subject.get_rocketmq_version(99999)).to eql('UNKNOWN_VERSION_ID_99999') + expect(subject.get_rocketmq_version(99999)).to eql('UNKNOWN.VERSION.ID.99999') end end end From 64b441be2a17a0c9f1723110d1c2f6f49c907da6 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Thu, 22 Jun 2023 01:29:33 -0400 Subject: [PATCH 04/11] Rspec tests, get_broker_port addition --- lib/msf/core/auxiliary/rocketmq.rb | 54 +++++++++++++++---- .../scanner/misc/rocketmq_version.rb | 11 +--- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 48 ++++++++++++++++- 3 files changed, 92 insertions(+), 21 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 9f0fe526bc..75bef4015b 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -3,10 +3,11 @@ module Msf ### # - # This module provides methods for working with Arista equipment + # This module provides methods for working with Apache RocketMQ # ### module Auxiliary::Rocketmq + include Msf::Exploit::Remote::Tcp def initialize(info = {}) super register_options([ Opt::RPORT(9876) ], Msf::Auxiliary::Rocketmq) @@ -21,7 +22,6 @@ module Msf begin connect - vprint_status('Sending request') sock.send(header + data_length + data, 0) res = sock.recv(1024) rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e @@ -41,6 +41,18 @@ module Msf return nil end + res + end + + def get_rocketmq_version(id) + # This function takes an ID (number) and looks through rocketmq's index of verison numbers to find the real version number + # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table + # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java + version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) + version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id}").gsub('_', '.') + end + + def parse_rocketmq_data(res) # remove a response header so we have json-ish data res = res[8..] @@ -63,15 +75,39 @@ module Msf vprint_error("Unable to parse json data: #{j}") next end - result + + parsed_data = {} + result.each do |j| + parsed_data['version'] = get_rocketmq_version(j['version']) if j['version'] + parsed_data['brokerDatas'] = j['brokerDatas'] if j['brokerDatas'] + end + + if parsed_data == {} || parsed_data['version'].nil? + vprint_error('Unable to find version or other data within response.') + return + end + parsed_data end - def get_rocketmq_version(id) - # This function takes an ID (number) and looks through rocketmq's index of verison numbers to find the real version number - # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table - # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java - version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) - version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id}").gsub('_', '.') + def get_broker_port(brokerDatas, rhost) + # Example of brokerData: + # [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}] + target_port = nil + brokerDatas.each do |brokerData| + brokerData.each do |key, broker_info| + next unless key == 'brokerAddrs' + broker_info.each do |_iterator, broker_endpoint| + next unless broker_endpoint.include?(rhost) + return broker_endpoint.match(/#{rhost}:(\d+)/)[1] + end + end + end + + if target_port.nil? + print_status('autodetection failed, assuming default port of 10911') + target_port = 10911 + end + target_port end end end diff --git a/modules/auxiliary/scanner/misc/rocketmq_version.rb b/modules/auxiliary/scanner/misc/rocketmq_version.rb index 8891ca07d9..d7f9d01276 100644 --- a/modules/auxiliary/scanner/misc/rocketmq_version.rb +++ b/modules/auxiliary/scanner/misc/rocketmq_version.rb @@ -42,17 +42,8 @@ class MetasploitModule < Msf::Auxiliary return end - parsed_data = {} + parsed_data = parse_rocketmq_data(res) # grab some data that we need/want out of the response - res.each do |j| - parsed_data['version'] = get_rocketmq_version(j['version']) if j['version'] - parsed_data['brokerDatas'] = j['brokerDatas'] if j['brokerDatas'] - end - - if parsed_data == {} || parsed_data['version'].nil? - vprint_error('Unable to find version or other data within response.') - return - end output = "RocketMQ version #{parsed_data['version']}" output += " found with brokers: #{parsed_data['brokerDatas']}" if parsed_data['brokerDatas'] print_good(output) diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index bfc9608cdc..eeb1778c3d 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' RSpec.describe Msf::Auxiliary::Rocketmq do subject do - mod = Msf::Module.new + mod = Msf::Auxiliary.new + mod.extend(Msf::Exploit::Remote::Tcp) mod.extend(Msf::Auxiliary::Rocketmq) mod end @@ -20,4 +21,47 @@ RSpec.describe Msf::Auxiliary::Rocketmq do end end end -end + + let(:expected_name_server_request) 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 + 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 + end + + let(:expected_parsed_data_response) do + {"brokerDatas" => [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}], + "version" => "V4.9.5"} + end + + let(:mock_sock) { double :'Rex::Socket::Tcp', send: expected_name_server_request, recv: expected_name_server_response, close: nil, shutdown: nil } + + before(:each) do + allow(subject).to receive(:connect).and_return(mock_sock) + allow(subject).to receive(:sock).and_return(mock_sock) + end + + describe '#send_version_request' do + it 'returns version info' do + expect(mock_sock).to receive(:send).with(expected_name_server_request, 0) + expect(subject.send_version_request).to eq(expected_name_server_response) + end + end + + describe '#parse_rocketmq_data' do + it 'correctly parses the response from the name server into version and brokeDatas info' do + expect(subject.parse_rocketmq_data(expected_name_server_response)).to eq(expected_parsed_data_response) + end + end + + describe '#get_broker_port' do + it 'returns the broker port associated with the given rport in the name server response ' do + expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.135')).to eq(10911) + end + + it 'returns the default broker port 10911 when rhost is not found in the name server response' do + expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.1')).to eq(10911) + end + end +end \ No newline at end of file From 46629ca1d2905c7eb21d1f1681ed6461353b7a7a Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Mon, 26 Jun 2023 14:01:12 -0400 Subject: [PATCH 05/11] responded to comments --- lib/msf/core/auxiliary/rocketmq.rb | 19 +++---- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 60 ++++++++++++-------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 75bef4015b..fd97792135 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -89,23 +89,22 @@ module Msf parsed_data end - def get_broker_port(brokerDatas, rhost) + def get_broker_port(broker_datas, rhost, default_broker_port) # Example of brokerData: # [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}] target_port = nil - brokerDatas.each do |brokerData| - brokerData.each do |key, broker_info| - next unless key == 'brokerAddrs' - broker_info.each do |_iterator, broker_endpoint| - next unless broker_endpoint.include?(rhost) - return broker_endpoint.match(/#{rhost}:(\d+)/)[1] - end + + broker_datas['brokerDatas'].each do |broker_data| + 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 + if target_port.nil? - print_status('autodetection failed, assuming default port of 10911') - target_port = 10911 + print_status("autodetection failed, assuming default port of #{default_broker_port}") + target_port = default_broker_port end target_port end diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index eeb1778c3d..bbb9bbd2e1 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -8,7 +8,37 @@ RSpec.describe Msf::Auxiliary::Rocketmq do mod end - describe 'get_rocketmq_version' do + 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 + 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 + end + + let(:expected_parsed_data_response) do + { + 'brokerDatas' => [ + { + 'brokerAddrs' => { + '0' => '172.16.199.135:10911' + }, + 'brokerName' => 'DESKTOP-8ATHH6O', + 'cluster' => 'DefaultCluster' + } + ], + 'version' => 'V4.9.5' + } + end + + let(:mock_sock) { double :'Rex::Socket::Tcp', send: nil, recv: expected_name_server_response, close: nil, shutdown: nil } + + before(:each) do + allow(subject).to receive(:connect).and_return(mock_sock) + allow(subject).to receive(:sock).and_return(mock_sock) + end + + describe '#get_rocketmq_version' do context 'correctly looks up id 401 as V4.9.4' do it 'returns that version' do expect(subject.get_rocketmq_version(401)).to eql('V4.9.4') @@ -22,29 +52,9 @@ RSpec.describe Msf::Auxiliary::Rocketmq do end end - let(:expected_name_server_request) 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 - 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 - end - - let(:expected_parsed_data_response) do - {"brokerDatas" => [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}], - "version" => "V4.9.5"} - end - - let(:mock_sock) { double :'Rex::Socket::Tcp', send: expected_name_server_request, recv: expected_name_server_response, close: nil, shutdown: nil } - - before(:each) do - allow(subject).to receive(:connect).and_return(mock_sock) - allow(subject).to receive(:sock).and_return(mock_sock) - end - describe '#send_version_request' do it 'returns version info' do - expect(mock_sock).to receive(:send).with(expected_name_server_request, 0) + expect(mock_sock).to receive(:send).with(mock_name_server_response, 0) expect(subject.send_version_request).to eq(expected_name_server_response) end end @@ -57,11 +67,11 @@ RSpec.describe Msf::Auxiliary::Rocketmq do describe '#get_broker_port' do it 'returns the broker port associated with the given rport in the name server response ' do - expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.135')).to eq(10911) + expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.135', 10911)).to eq(10911) end - it 'returns the default broker port 10911 when rhost is not found in the name server response' do - expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.1')).to eq(10911) + it 'returns the default broker port when rhost is not found in the name server response' do + expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.1', 10000)).to eq(10000) end end end \ No newline at end of file From f86f9c044041c4460bd0fa1ee26ebfafb37fdfd0 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Tue, 27 Jun 2023 16:39:16 -0400 Subject: [PATCH 06/11] Update lib/msf/core/auxiliary/rocketmq.rb Co-authored-by: adfoster-r7 <60357436+adfoster-r7@users.noreply.github.com> --- lib/msf/core/auxiliary/rocketmq.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index fd97792135..bd5d5ffe08 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -89,7 +89,7 @@ module Msf parsed_data end - def get_broker_port(broker_datas, rhost, default_broker_port) + 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"}] target_port = nil From 3e4b62a240038fbd6e356f9bf6cb23eca31ae3a8 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Tue, 27 Jun 2023 16:53:13 -0400 Subject: [PATCH 07/11] Update spec/lib/msf/core/auxiliary/rocketmq_spec.rb --- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index bbb9bbd2e1..12c43d92d9 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -67,7 +67,7 @@ RSpec.describe Msf::Auxiliary::Rocketmq do describe '#get_broker_port' do it 'returns the broker port associated with the given rport in the name server response ' do - expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.135', 10911)).to eq(10911) + expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.135')).to eq(10911) end it 'returns the default broker port when rhost is not found in the name server response' do From ce2629d4e16f42548d9e75f6c7e6e3279ea73c1e Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Tue, 27 Jun 2023 16:53:36 -0400 Subject: [PATCH 08/11] Update spec/lib/msf/core/auxiliary/rocketmq_spec.rb --- spec/lib/msf/core/auxiliary/rocketmq_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb index 12c43d92d9..c541734130 100644 --- a/spec/lib/msf/core/auxiliary/rocketmq_spec.rb +++ b/spec/lib/msf/core/auxiliary/rocketmq_spec.rb @@ -71,7 +71,7 @@ RSpec.describe Msf::Auxiliary::Rocketmq do end it 'returns the default broker port when rhost is not found in the name server response' do - expect(subject.get_broker_port(expected_parsed_data_response, '172.16.199.1', 10000)).to eq(10000) + 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 From 35f5b195127ec4b3055d913268dd556f1f2404bb Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Thu, 29 Jun 2023 15:23:27 -0400 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Christophe De La Fuente <56716719+cdelafuente-r7@users.noreply.github.com> --- lib/msf/core/auxiliary/rocketmq.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index bd5d5ffe08..4dbc863a69 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -13,9 +13,11 @@ module Msf register_options([ Opt::RPORT(9876) ], Msf::Auxiliary::Rocketmq) end - def send_version_request - # sends a version request to the service, and returns the data as a list of hashes. nil on error - # https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L68 + # Sends a version request to the service, and returns the data as a list of hashes or nil on error + # + # @see https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L68 + # @return [String, nil] The data as a list of hashes or nil on error + def send_version_request data = '{"code":105,"extFields":{"Signature":"/u5P/wZUbhjanu4LM/UzEdo2u2I=","topic":"TBW102","AccessKey":"rocketmq2"},"flag":0,"language":"JAVA","opaque":1,"serializeTypeCurrentRPC":"JSON","version":401}' data_length = "\x00\x00\x00" + [data.length].pack('C') header = "\x00\x00\x00" + [data.length + data_length.length].pack('C') @@ -45,7 +47,7 @@ module Msf end def get_rocketmq_version(id) - # This function takes an ID (number) and looks through rocketmq's index of verison numbers to find the real version number + # This function takes an ID (number) and looks through rocketmq's index of version numbers to find the real version number # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) @@ -102,11 +104,8 @@ module Msf end - if target_port.nil? - print_status("autodetection failed, assuming default port of #{default_broker_port}") - target_port = default_broker_port - end - target_port + print_status("autodetection failed, assuming default port of #{default_broker_port}") + default_broker_port end end end From cc1b7db77394deaaa549a430fa89b895025406bd Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Thu, 29 Jun 2023 15:52:03 -0400 Subject: [PATCH 10/11] Method documentation comments --- lib/msf/core/auxiliary/rocketmq.rb | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 4dbc863a69..52e9f2d7c6 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -16,7 +16,7 @@ module Msf # Sends a version request to the service, and returns the data as a list of hashes or nil on error # # @see https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L68 - # @return [String, nil] The data as a list of hashes or nil on error + # @return [String, nil] The data as a list of hashes or nil on error. def send_version_request data = '{"code":105,"extFields":{"Signature":"/u5P/wZUbhjanu4LM/UzEdo2u2I=","topic":"TBW102","AccessKey":"rocketmq2"},"flag":0,"language":"JAVA","opaque":1,"serializeTypeCurrentRPC":"JSON","version":401}' data_length = "\x00\x00\x00" + [data.length].pack('C') @@ -46,14 +46,22 @@ module Msf res end + # This function takes an ID (number) and looks through rocketmq's index of version numbers to find the real version number + # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table + # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java + # + # @param [Integer] id The version id found in the NameServer response. + # @return [String] The Apache RocketMQ version string. def get_rocketmq_version(id) - # This function takes an ID (number) and looks through rocketmq's index of version numbers to find the real version number - # Errors will result in "UNKNOWN_VERSION_ID_" and may be caused by needing to update the version table - # from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb')) version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id}").gsub('_', '.') end + # This function takes a response from the send_version_request function and parses as it doesn't get returned as + # proper json. It returns a Hash including RocketMQ versions info and Broker info if found + # + # @param [String] res Response form the send_version_request request + # @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..] @@ -91,6 +99,14 @@ module Msf parsed_data end + # This function takes the broker data from the name server response, the rhost address and a default Broker port + # number. The function searches the broker data for a broker instance listening on the rhost address and if found it + # returns the port found. If the search is unsuccessful it returns the default broker port. + # + # @param [Array] broker_datas An array containing a hash of Broker info + # @param [String] rhosts The RHOST address + # @param [Integer] default_broker_port The default broker port + # @return [Integer] the determined broker port 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"}] From 53a761a13dd2b89e3807116d1a178ee5f774e033 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Wed, 5 Jul 2023 11:13:08 -0400 Subject: [PATCH 11/11] Update lib/msf/core/auxiliary/rocketmq.rb Co-authored-by: Christophe De La Fuente <56716719+cdelafuente-r7@users.noreply.github.com> --- lib/msf/core/auxiliary/rocketmq.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/msf/core/auxiliary/rocketmq.rb b/lib/msf/core/auxiliary/rocketmq.rb index 52e9f2d7c6..01d4d7501f 100644 --- a/lib/msf/core/auxiliary/rocketmq.rb +++ b/lib/msf/core/auxiliary/rocketmq.rb @@ -110,7 +110,6 @@ module Msf 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"}] - target_port = nil broker_datas['brokerDatas'].each do |broker_data| broker_data['brokerAddrs'].values.each do |broker_endpoint|