CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/auxiliary/rocketmq.rb
Views: 1904
1
# -*- coding: binary -*-
2
3
module Msf
4
###
5
#
6
# This module provides methods for working with Apache RocketMQ
7
#
8
###
9
module Auxiliary::Rocketmq
10
include Msf::Exploit::Remote::Tcp
11
def initialize(info = {})
12
super
13
register_options([ Opt::RPORT(9876) ], Msf::Auxiliary::Rocketmq)
14
end
15
16
# Sends a version request to the service, and returns the data as a list of hashes or nil on error
17
#
18
# @see https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L68
19
# @return [String, nil] The data as a list of hashes or nil on error.
20
def send_version_request
21
data = '{"code":105,"extFields":{"Signature":"/u5P/wZUbhjanu4LM/UzEdo2u2I=","topic":"TBW102","AccessKey":"rocketmq2"},"flag":0,"language":"JAVA","opaque":1,"serializeTypeCurrentRPC":"JSON","version":401}'
22
data_length = "\x00\x00\x00" + [data.length].pack('C')
23
header = "\x00\x00\x00" + [data.length + data_length.length].pack('C')
24
25
begin
26
connect
27
sock.send(header + data_length + data, 0)
28
res_length = sock.timed_read(4)&.unpack1('N')
29
return nil if res_length.nil?
30
31
res = sock.timed_read(res_length)
32
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
33
print_error("Unable to connect: #{e.class} #{e.message}\n#{e.backtrace * "\n"}")
34
elog('Error sending the rocketmq version request', error: e)
35
return nil
36
ensure
37
disconnect
38
end
39
40
if res.nil?
41
vprint_error('No response received')
42
return nil
43
end
44
45
unless res.include?('{')
46
vprint_error('Response contains unusable data')
47
return nil
48
end
49
50
res
51
end
52
53
# This function takes an ID (number) and looks through rocketmq's index of version numbers to find the real version number
54
# Errors will result in "UNKNOWN_VERSION_ID_<id>" and may be caused by needing to update the version table
55
# from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java
56
#
57
# @param [Integer] id The version id found in the NameServer response.
58
# @return [String] The Apache RocketMQ version string.
59
def get_rocketmq_version(id)
60
version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb'))
61
version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id}").gsub('_', '.')
62
end
63
64
# This function takes a response from the send_version_request function and parses as it doesn't get returned as
65
# proper json. It returns a Hash including RocketMQ versions info and Broker info if found
66
#
67
# @param [String] res Response form the send_version_request request
68
# @return [Hash] Hash including RocketMQ versions info and Broker info if found
69
def parse_rocketmq_data(res)
70
# remove a response header so we have json-ish data
71
res = res.split(/\x00_/)[1]
72
unless res.starts_with?("{")
73
print_error("Failed to successfully remove the response header and now cannot parse the response.")
74
return nil
75
end
76
77
# we have 2 json objects appended to each other, so we now need to split that out and make it usable
78
res = res.split('}{')
79
80
jsonable = []
81
# patch back in the { and }
82
res.each do |r|
83
r += '}' unless r.end_with?('}')
84
r = '{' + r unless r.start_with?('{')
85
jsonable.append(r)
86
end
87
88
result = []
89
jsonable.each do |j|
90
res = JSON.parse(j)
91
result.append(res)
92
rescue JSON::ParserError
93
vprint_error("Unable to parse json data: #{j}")
94
next
95
end
96
97
parsed_data = {}
98
result.each do |j|
99
parsed_data['version'] = get_rocketmq_version(j['version']) if j['version']
100
parsed_data['brokerDatas'] = j['brokerDatas'] if j['brokerDatas']
101
end
102
103
if parsed_data == {} || parsed_data['version'].nil?
104
vprint_error('Unable to find version or other data within response.')
105
return
106
end
107
parsed_data
108
end
109
110
# This function takes the broker data from the name server response, the rhost address and a default Broker port
111
# number. The function searches the broker data for a broker instance listening on the rhost address and if found it
112
# returns the port found. If the search is unsuccessful it returns the default broker port.
113
#
114
# @param [Array] broker_datas An array containing a hash of Broker info
115
# @param [String] rhosts The RHOST address
116
# @param [Integer] default_broker_port The default broker port
117
# @return [Integer] the determined broker port
118
def get_broker_port(broker_datas, rhost, default_broker_port: 10911)
119
# Example of brokerData:
120
# [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}]
121
122
if broker_datas['brokerDatas'].blank?
123
print_status("brokerDatas field is missing from the response, assuming default broker port of #{default_broker_port}")
124
return default_broker_port
125
end
126
broker_datas['brokerDatas'].each do |broker_data|
127
if broker_data['brokerAddrs'].blank?
128
print_status("brokerAddrs field is missing from the response, assuming default broker port of #{default_broker_port}")
129
return default_broker_port
130
end
131
broker_data['brokerAddrs'].values.each do |broker_endpoint|
132
next unless broker_endpoint.start_with?("#{rhost}:")
133
return broker_endpoint.match(/\A#{rhost}:(\d+)\z/)[1].to_i
134
end
135
end
136
137
print_status("autodetection failed, assuming default port of #{default_broker_port}")
138
default_broker_port
139
end
140
end
141
end
142
143