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
@!attribute csv_object
The `CSV` instance created from `#input` @return [CSV]
@!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
Source
# 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
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]
Source
# 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
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]
Source
# 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
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]
Source
# 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
The key data inside the file at key_file_name
@param key_file_name [String] @return [String]
Private Instance Methods
Source
# 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
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
Source
# 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
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]
Source
# 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
Invalid if CSV is malformed, headers are not in compliance, or CSV contains no data
@return [void]
Source
# 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
Returns true if the {#private_credential_type} is in {Metasploit::Credential::Importer::Base::ALLOWED_PRIVATE_TYPE_NAMES} @return [void]