class Metasploit::Credential::Importer::Core

Creates {Metasploit::Credential::Core} objects and their associated {Metasploit::Credential::Public}, {Metasploit::Credential::Private}, and {Metasploit::Credential::Realm} objects from a CSV file.

Successful import will also create a {Metasploit::Credential::Origin::Import}

Constants

BLANK_TOKEN

This token represents an explict Blank entry. An empty field instead indicates that we do not know what this value is

VALID_LONG_CSV_HEADERS

Valid headers for a CSV containing heterogenous {Metasploit::Credential::Private} types and values for {Metasploit::Credential::Realm}

VALID_SHORT_CSV_HEADERS

Valid headers for a “short” CSV containing only data for {Metasploit::Credential::Public} and {Metasploit::Credential::Private} objects

Attributes

csv_object[R]

@!attribute csv_object

The `CSV` instance created from `#input`
@return [CSV]
private_credential_type[RW]

@!attribute private_credential_type

The name of one of the subclasses of {Metasploit::Credential::Private}.  This will be the same for all the
{Metasploit::Credential::Private} objects created during the import.
@return [String]

Public Instance Methods

import!() click to toggle source

If no {#private_credential_type} is set, assumes that the CSV contains a mixture of private types and realms. Otherwise, assume that this is a short form import and process accordingly. @return [void]

# File lib/metasploit/credential/importer/core.rb, line 84
def import!
  if csv_object.first.headers.include? 'private_type'
    result =  import_long_form
  else
    result =  import_short_form
  end
  return result
end
import_long_form() click to toggle source

Performs an import of a “long” CSV - one that that contains realms and heterogenous private types Performs a pretty naive import from the data in {#csv_object}, allowing the import to have different private types per row, and attempting to reduce database lookups by storing found or created {Metasploit::Credential::Realm} objects in a lookup Hash that gets updated with every new Realm found, and then consulted in analysis of subsequent rows.

@return [void]

# File lib/metasploit/credential/importer/core.rb, line 100
def import_long_form
  all_creds_valid = true
  realms = Hash.new
  Metasploit::Credential::Core.transaction do
    core_opts = []
    rows = []
    csv_object.each do |row|

      next if row.header_row?
      next unless row['username'].present? || row['private_data'].present?

      username      = row['username'].present? ? row['username'] : ''

      realm_key     = row['realm_key']
      realm_value   = row['realm_value']  # Use the name of the Realm as a lookup for getting the object

      private_class = row['private_type'].present? ? row['private_type'].constantize : ''
      private_data  = row['private_data'].present? ? row['private_data'] : ''

      if realms[realm_value].nil?
        realms[realm_value]  = Metasploit::Credential::Realm.where(key: realm_key, value: realm_value).first_or_create
      end

      realm_object_for_row   = realms[realm_value]

      public_object = create_public_from_field(username)

      if private_class.present? &&  LONG_FORM_ALLOWED_PRIVATE_TYPE_NAMES.include?(private_class.name)
        if private_data.strip == BLANK_TOKEN
          private_object_for_row = Metasploit::Credential::BlankPassword.first_or_create
        elsif private_class == Metasploit::Credential::SSHKey
          private_object_for_row = Metasploit::Credential::SSHKey.where(data: key_data_from_file(private_data)).first_or_create
        else
          private_object_for_row = private_class.where(data: private_data).first_or_create
        end
      end
      all_creds_valid = all_creds_valid && public_object && private_object_for_row && (public_object.valid? && private_object_for_row.valid?)

      core_opts << {origin:origin, workspace_id: workspace.id,
       public: public_object,
       private: private_object_for_row,
       realm: realm_object_for_row}

      rows << row



    end
    if all_creds_valid
      core_opts.each_index do |index|
        row = rows[index]


        # Host and Service information for Logins
        host_address      = row['host_address']
        service_port      = row['service_port']
        service_protocol  = row['service_protocol']
        service_name      = row['service_name']
        # These were not initially included in the export, so handle
        # legacy cases:
        access_level      = row['access_level'].present? ? row['access_level'] : ''
        last_attempted_at = row['last_attempted_at'].present? ? row['last_attempted_at'] : ''
        status            = row['status'].present? ? row['status'] : ''

        if Metasploit::Credential::Core.where(core_opts[index]).blank?
          core = create_credential_core(core_opts[index])
        else
          core = Metasploit::Credential::Core.where(core_opts[index]).first
        end


        if host_address.present? && service_port.present? && service_protocol.present?
          login_opts = {
              core: core,
              address: host_address,
              port: service_port,
              protocol: service_protocol,
              workspace_id: workspace.id,
              service_name: service_name.present? ? service_name : ""
          }
          login_opts[:last_attempted_at] = last_attempted_at unless status.blank?
          login_opts[:status]            = status unless status.blank?
          login_opts[:access_level]      = access_level unless access_level.blank?

          create_credential_login(login_opts)

        end
      end
    end
    end
  return all_creds_valid
end
import_short_form() click to toggle source

Performs an import of a “short” form of CSV - one that contains only one type of {Metasploit::Credential::Private} and no {Metasploit::Credential::Realm} data @return [Boolean]

# File lib/metasploit/credential/importer/core.rb, line 197
def import_short_form
  core_opts = []
  all_creds_valid = true
  Metasploit::Credential::Core.transaction do
    csv_object.each do |row|
      next if row.header_row?

      username     = row['username'].present? ? row['username'] : ''
      private_data  = row['private_data'].present? ? row['private_data'] : ''

      public_object = create_public_from_field(username)

      if private_data.strip == BLANK_TOKEN
        private_object_for_row = Metasploit::Credential::BlankPassword.first_or_create
      else
        private_object_for_row = @private_credential_type.constantize.where(data: private_data).first_or_create
      end

      # need to check private_object_for_row.valid? to raise a user facing message if any cred had invalid private

      all_creds_valid = all_creds_valid && (public_object.valid? && private_object_for_row.valid?)


      core_opts << {origin:origin, workspace_id: workspace.id,
                                    public: public_object,
                                    private: private_object_for_row}
    end
    if all_creds_valid
      core_opts.each do |item|
        if Metasploit::Credential::Core.where(origin: item[:origin], workspace_id: item[:workspace_id], public: item[:public], private: item[:private]).blank?
          create_credential_core(item)
        end
      end
    end


  end
  return  all_creds_valid
end
key_data_from_file(key_file_name) click to toggle source

The key data inside the file at key_file_name @param key_file_name [String] @return [String]

# File lib/metasploit/credential/importer/core.rb, line 76
def key_data_from_file(key_file_name)
  full_key_file_path = File.join(File.dirname(input.path), Metasploit::Credential::Importer::Zip::KEYS_SUBDIRECTORY_NAME, key_file_name)
  File.open(full_key_file_path, 'r').read
end

Private Instance Methods

create_public_from_field(username) click to toggle source

Takes the username field and checks to see if it should be Blank or else a Username object

@param [String] :username the username field contents @return [Metasploit::Credential::Public] the Public created from the field

# File lib/metasploit/credential/importer/core.rb, line 244
def create_public_from_field(username)
  if username.strip == BLANK_TOKEN
    username = " "
  end
  create_credential_public(username: username)
end
csv_headers_are_correct?(csv_headers) click to toggle source

Returns true if the headers are correct, based on whether a private type has been chosen @param csv_headers [Array] the headers in the CSV contained in {#input} @return [Boolean]

# File lib/metasploit/credential/importer/core.rb, line 254
def csv_headers_are_correct?(csv_headers)
  if csv_headers.include? 'private_type'
    return csv_headers.map(&:to_sym) == VALID_LONG_CSV_HEADERS
  else
    return csv_headers.map(&:to_sym) == VALID_SHORT_CSV_HEADERS
  end
end
header_format_and_csv_wellformedness() click to toggle source

Invalid if CSV is malformed, headers are not in compliance, or CSV contains no data

@return [void]

# File lib/metasploit/credential/importer/core.rb, line 265
def header_format_and_csv_wellformedness
  begin
    if csv_object.header_row?
      csv_headers = csv_object.first.fields
      if csv_headers_are_correct?(csv_headers)
        next_row = csv_object.gets
        if next_row.present?
          csv_object.rewind
          true
        else
          errors.add(:input, :empty_csv)
        end
      else
        errors.add(:input, :incorrect_csv_headers)
      end
    else
      fail "CSV has already been accessed past index 0"
    end
  rescue ::CSV::MalformedCSVError
    errors.add(:input, :malformed_csv)
  end
end
private_type_is_allowed() click to toggle source

Returns true if the {#private_credential_type} is in {Metasploit::Credential::Importer::Base::ALLOWED_PRIVATE_TYPE_NAMES} @return [void]

# File lib/metasploit/credential/importer/core.rb, line 290
def private_type_is_allowed
  if Metasploit::Credential::Importer::Base::SHORT_FORM_ALLOWED_PRIVATE_TYPE_NAMES.include? @private_credential_type
    true
  else
    errors.add(:private_credential_type, :invalid_type)
  end
end