Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/core/auxiliary/rocketmq.rb
Views: 11784
# -*- coding: binary -*-12module Msf3###4#5# This module provides methods for working with Apache RocketMQ6#7###8module Auxiliary::Rocketmq9include Msf::Exploit::Remote::Tcp10def initialize(info = {})11super12register_options([ Opt::RPORT(9876) ], Msf::Auxiliary::Rocketmq)13end1415# Sends a version request to the service, and returns the data as a list of hashes or nil on error16#17# @see https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT/blob/e27693a854a8e3b2863dc366f36002107e3595de/check.py#L6818# @return [String, nil] The data as a list of hashes or nil on error.19def send_version_request20data = '{"code":105,"extFields":{"Signature":"/u5P/wZUbhjanu4LM/UzEdo2u2I=","topic":"TBW102","AccessKey":"rocketmq2"},"flag":0,"language":"JAVA","opaque":1,"serializeTypeCurrentRPC":"JSON","version":401}'21data_length = "\x00\x00\x00" + [data.length].pack('C')22header = "\x00\x00\x00" + [data.length + data_length.length].pack('C')2324begin25connect26sock.send(header + data_length + data, 0)27res_length = sock.timed_read(4)&.unpack1('N')28return nil if res_length.nil?2930res = sock.timed_read(res_length)31rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e32print_error("Unable to connect: #{e.class} #{e.message}\n#{e.backtrace * "\n"}")33elog('Error sending the rocketmq version request', error: e)34return nil35ensure36disconnect37end3839if res.nil?40vprint_error('No response received')41return nil42end4344unless res.include?('{')45vprint_error('Response contains unusable data')46return nil47end4849res50end5152# This function takes an ID (number) and looks through rocketmq's index of version numbers to find the real version number53# Errors will result in "UNKNOWN_VERSION_ID_<id>" and may be caused by needing to update the version table54# from https://github.com/apache/rocketmq/blob/develop/common/src/4d82b307ef50f5cba5717d0ebafeb3cabf336873/java/org/apache/rocketmq/common/MQVersion.java55#56# @param [Integer] id The version id found in the NameServer response.57# @return [String] The Apache RocketMQ version string.58def get_rocketmq_version(id)59version_list = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'rocketmq_versions_list.json'), mode: 'rb'))60version_list.fetch(id, "UNKNOWN_VERSION_ID_#{id}").gsub('_', '.')61end6263# This function takes a response from the send_version_request function and parses as it doesn't get returned as64# proper json. It returns a Hash including RocketMQ versions info and Broker info if found65#66# @param [String] res Response form the send_version_request request67# @return [Hash] Hash including RocketMQ versions info and Broker info if found68def parse_rocketmq_data(res)69# remove a response header so we have json-ish data70res = res.split(/\x00_/)[1]71unless res.starts_with?("{")72print_error("Failed to successfully remove the response header and now cannot parse the response.")73return nil74end7576# we have 2 json objects appended to each other, so we now need to split that out and make it usable77res = res.split('}{')7879jsonable = []80# patch back in the { and }81res.each do |r|82r += '}' unless r.end_with?('}')83r = '{' + r unless r.start_with?('{')84jsonable.append(r)85end8687result = []88jsonable.each do |j|89res = JSON.parse(j)90result.append(res)91rescue JSON::ParserError92vprint_error("Unable to parse json data: #{j}")93next94end9596parsed_data = {}97result.each do |j|98parsed_data['version'] = get_rocketmq_version(j['version']) if j['version']99parsed_data['brokerDatas'] = j['brokerDatas'] if j['brokerDatas']100end101102if parsed_data == {} || parsed_data['version'].nil?103vprint_error('Unable to find version or other data within response.')104return105end106parsed_data107end108109# This function takes the broker data from the name server response, the rhost address and a default Broker port110# number. The function searches the broker data for a broker instance listening on the rhost address and if found it111# returns the port found. If the search is unsuccessful it returns the default broker port.112#113# @param [Array] broker_datas An array containing a hash of Broker info114# @param [String] rhosts The RHOST address115# @param [Integer] default_broker_port The default broker port116# @return [Integer] the determined broker port117def get_broker_port(broker_datas, rhost, default_broker_port: 10911)118# Example of brokerData:119# [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}]120121if broker_datas['brokerDatas'].blank?122print_status("brokerDatas field is missing from the response, assuming default broker port of #{default_broker_port}")123return default_broker_port124end125broker_datas['brokerDatas'].each do |broker_data|126if broker_data['brokerAddrs'].blank?127print_status("brokerAddrs field is missing from the response, assuming default broker port of #{default_broker_port}")128return default_broker_port129end130broker_data['brokerAddrs'].values.each do |broker_endpoint|131next unless broker_endpoint.start_with?("#{rhost}:")132return broker_endpoint.match(/\A#{rhost}:(\d+)\z/)[1].to_i133end134end135136print_status("autodetection failed, assuming default port of #{default_broker_port}")137default_broker_port138end139end140end141142143