rapid7/metasploit-framework

View on GitHub
modules/auxiliary/scanner/misc/ibm_mq_channel_brute.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 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
  end

end