Class: Msf::DataStore

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/data_store.rb

Overview

The data store is just a bitbucket that holds keyed values. It is used by various classes to hold option values and other state information.

Direct Known Subclasses

ModuleDataStore

Defined Under Namespace

Classes: DataStoreSearchResult

Constant Summary collapse

GLOBAL_OPTION_DEFINITIONS =

Typed option definitions for globally-scoped datastore keys. These are registered on the framework datastore at startup so that normalization (e.g. OptBool converting “true” to true) applies even when no module is active.

OptionContainer.new([
  OptBool.new('ConsoleLogging',      [false, 'Log all console input and output', false]),
  OptInt.new('LogLevel',             [false, 'Verbosity of logs (default 0, max 3)', 0]),
  OptEnum.new('MinimumRank',         [false, 'The minimum rank of exploits that will run without explicit confirmation', 'manual', RankingName.values]),
  OptBool.new('SessionLogging',      [false, 'Log all input and output for sessions', false]),
  OptBool.new('TimestampOutput',     [false, 'Prefix all console output with a timestamp', false]),
  OptBool.new('VERBOSE',             [false, 'Enable detailed status messages - the specific behavior can differ per module', false]),
  OptString.new('Prompt',            [false, 'The prompt string', nil]),
  OptString.new('PromptChar',        [false, 'The prompt character', nil]),
  OptString.new('PromptTimeFormat',  [false, 'Format for timestamp escapes in prompts', nil]),
  OptString.new('MeterpreterPrompt', [false, 'The meterpreter prompt string', nil]),
  OptSessionTlvLogging.new('SessionTlvLogging', [false, 'Log all incoming and outgoing TLV packets', nil]),
])
GLOBAL_KEYS =

Backward-compatible list of known global key names, derived from the typed option definitions above.

GLOBAL_OPTION_DEFINITIONS.keys.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDataStore

Initializes the data store’s internal state.



37
38
39
40
41
42
43
44
45
46
# File 'lib/msf/core/data_store.rb', line 37

def initialize
  @options     = Hash.new
  @aliases     = Hash.new

  # default values which will be referenced when not defined by the user
  @defaults = Hash.new

  # values explicitly defined, which take precedence over default values
  @user_defined = Hash.new
end

Instance Attribute Details

#aliasesHash{String => String} (protected)

Returns The key is the old option name, the value is the new option name.

Returns:

  • (Hash{String => String})

    The key is the old option name, the value is the new option name



481
482
483
# File 'lib/msf/core/data_store.rb', line 481

def aliases
  @aliases
end

#defaultsHash{String => Msf::OptBase} (protected)

These defaults will be used if the user has not explicitly defined a specific datastore value. These will be checked as a priority to any options that also provide defaults.

Returns:

  • (Hash{String => Msf::OptBase})

    The hash of default values



478
479
480
# File 'lib/msf/core/data_store.rb', line 478

def defaults
  @defaults
end

#optionsHash{String => Msf::OptBase}

Returns The options associated with this datastore. Used for validating values/defaults/etc.

Returns:

  • (Hash{String => Msf::OptBase})

    The options associated with this datastore. Used for validating values/defaults/etc



49
50
51
# File 'lib/msf/core/data_store.rb', line 49

def options
  @options
end

#user_definedHash<String, Object>

Returns a hash of user-defined datastore values. The returned hash does not include default option values.

Returns:

  • (Hash<String, Object>)

    values explicitly defined on the data store which will override any default datastore values



56
57
58
# File 'lib/msf/core/data_store.rb', line 56

def user_defined
  @user_defined
end

Instance Method Details

#[](k) ⇒ Object

Case-insensitive wrapper around hash lookup



89
90
91
92
93
# File 'lib/msf/core/data_store.rb', line 89

def [](k)
  search_result = search_for(k)

  search_result.value
end

#[]=(k, v) ⇒ Object

Clears the imported flag for the supplied key since it’s being set directly.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/msf/core/data_store.rb', line 70

def []=(k, v)
  k = find_key_case(k)

  opt = @options[k]
  unless opt.nil?
    if opt.validate_on_assignment?
      unless opt.valid?(v, check_empty: false)
        raise Msf::OptionValidateError.new(["Value '#{v}' is not valid for option '#{k}'"])
      end
      v = opt.normalize(v)
    end
  end

  @user_defined[k] = v
end

#clearObject

Completely clear all values in the data store



389
390
391
392
393
394
395
396
# File 'lib/msf/core/data_store.rb', line 389

def clear
  self.options.clear
  self.aliases.clear
  self.defaults.clear
  self.user_defined.clear

  self
end

#copyMsf::DataStore

Return a copy of this datastore. Only string values will be duplicated, other values will share the same reference

Returns:



337
338
339
340
341
# File 'lib/msf/core/data_store.rb', line 337

def copy
  new_instance = self.class.new
  new_instance.copy_state(self)
  new_instance
end

#copy_state(other) ⇒ Msf::DataStore (protected)

Copy the state from the other Msf::DataStore. The state will be coped in a shallow fashion, other than imported and user_defined strings.

Parameters:

Returns:



489
490
491
492
493
494
495
496
# File 'lib/msf/core/data_store.rb', line 489

def copy_state(other)
  self.options = other.options.dup
  self.aliases = other.aliases.dup
  self.defaults = other.defaults.transform_values { |value| value.kind_of?(String) ? value.dup : value }
  self.user_defined = other.user_defined.transform_values { |value| value.kind_of?(String) ? value.dup : value }

  self
end

#default?(key) ⇒ TrueClass, FalseClass

Was this entry actually set or just using its default

Returns:

  • (TrueClass, FalseClass)


62
63
64
# File 'lib/msf/core/data_store.rb', line 62

def default?(key)
  search_for(key).default?
end

#delete(key) ⇒ Object

Deprecated.

use ##unset instead, or set the value explicitly to nil

Parameters:

  • key (String)

    The key to search for



123
124
125
# File 'lib/msf/core/data_store.rb', line 123

def delete(key)
  unset(key)
end

#each(&block) ⇒ Object Also known as: each_pair

Overrides the builtin ‘each’ operator to avoid the following exception on Ruby 1.9.2+

"can't add a new key into hash during iteration"


402
403
404
405
406
407
408
# File 'lib/msf/core/data_store.rb', line 402

def each(&block)
  list = []
  self.keys.sort.each do |sidx|
    list << [sidx, self[sidx]]
  end
  list.each(&block)
end

#each_key(&block) ⇒ Object



412
413
414
# File 'lib/msf/core/data_store.rb', line 412

def each_key(&block)
  self.keys.each(&block)
end

#find_key_case(k) ⇒ String

Case-insensitive key lookup

Returns:

  • (String)


420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/msf/core/data_store.rb', line 420

def find_key_case(k)
  # Scan each alias looking for a key
  search_k = k.downcase
  if self.aliases.has_key?(search_k)
    search_k = self.aliases[search_k]
  end

  # Check to see if we have an exact key match - otherwise we'll have to search manually to check case sensitivity
  if @user_defined.key?(search_k) || options.key?(search_k)
    return search_k
  end

  # Scan each key looking for a match
  each_key do |rk|
    if rk.casecmp(search_k) == 0
      return rk
    end
  end

  # Fall through to the non-existent value
  k
end

#from_file(path, name = 'global') ⇒ Object

Imports datastore values from the specified file path using the supplied name



321
322
323
324
325
326
327
328
329
330
331
# File 'lib/msf/core/data_store.rb', line 321

def from_file(path, name = 'global')
  begin
    ini = Rex::Parser::Ini.from_file(path)
  rescue
    return
  end

  if ini.group?(name)
    merge!(ini[name])
  end
end

#import_defaults_from_hash(hash, imported_by:) ⇒ nil

Update defaults from a hash. These merged values are not validated by default.

Parameters:

  • hash (Hash<String, Object>)

    The default values that should be used by the datastore

  • imported_by (Object)

    Who imported the defaults, not currently used

Returns:

  • (nil)


212
213
214
# File 'lib/msf/core/data_store.rb', line 212

def import_defaults_from_hash(hash, imported_by:)
  @defaults.merge!(hash)
end

#import_option(key, val, imported = true, imported_by = nil, option = nil) ⇒ Object

Deprecated.

TODO: Doesn’t normalize data in the same vein as: github.com/rapid7/metasploit-framework/pull/6644



219
220
221
222
223
224
225
226
227
228
# File 'lib/msf/core/data_store.rb', line 219

def import_option(key, val, imported = true, imported_by = nil, option = nil)
  store(key, val)

  if option
    option.aliases.each do |a|
      @aliases[a.downcase] = key.downcase
    end
  end
  @options[key] = option
end

#import_options(options, imported_by = nil, overwrite = true) ⇒ Object

This method is a helper method that imports the default value for all of the supplied options.



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/msf/core/data_store.rb', line 145

def import_options(options, imported_by = nil, overwrite = true)
  options.each_option do |name, option|
    if self.options[name].nil? || overwrite
      key = name
      option.aliases.each do |a|
        @aliases[a.downcase] = key.downcase
      end
      @options[key] = option
    end
  end
end

#import_options_from_hash(option_hash, imported = true, imported_by = nil) ⇒ nil

Deprecated.

use #merge! instead

Imports values from a hash and stores them in the datastore.

Returns:

  • (nil)


203
204
205
# File 'lib/msf/core/data_store.rb', line 203

def import_options_from_hash(option_hash, imported = true, imported_by = nil)
  merge!(option_hash)
end

#import_options_from_s(option_str, delim = nil) ⇒ Object

Imports option values from a whitespace separated string in VAR=VAL format.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/msf/core/data_store.rb', line 161

def import_options_from_s(option_str, delim = nil)
  hash = {}

  # Figure out the delimiter, default to space.
  if (delim.nil?)
    delim = /\s/

    if (option_str.split('=').length <= 2 or option_str.index(',') != nil)
      delim = ','
    end
  end

  # Split on the delimiter
  option_str.split(delim).each { |opt|
    var, val = opt.split('=', 2)

    next if (var =~ /^\s+$/)


    # Invalid parse?  Raise an exception and let those bastards know.
    if (var == nil or val == nil)
      var = "unknown" if (!var)

      raise Rex::ArgumentParseError, "Invalid option specified: #{var}",
        caller
    end

    # Remove trailing whitespaces from the value
    val.gsub!(/\s+$/, '')

    # Store the value
    hash[var] = val
  }

  merge!(hash)
end

#key?(key) ⇒ TrueClass, FalseClass Also known as: has_key?, include?, member?

Returns True if the key is present in the user defined values, or within registered options. False otherwise.

Parameters:

  • key (String)

Returns:

  • (TrueClass, FalseClass)

    True if the key is present in the user defined values, or within registered options. False otherwise.



245
246
247
248
# File 'lib/msf/core/data_store.rb', line 245

def key?(key)
  matching_key = find_key_case(key)
  keys.include?(matching_key)
end

#key_error_for(key) ⇒ Object (protected)

Raised when the specified key is not found

Parameters:

  • key (string)


500
501
502
# File 'lib/msf/core/data_store.rb', line 500

def key_error_for(key)
  ::KeyError.new "key not found: #{key.inspect}"
end

#keysArray<String>

Returns The array of user defined datastore values, and registered option names.

Returns:

  • (Array<String>)

    The array of user defined datastore values, and registered option names



231
232
233
# File 'lib/msf/core/data_store.rb', line 231

def keys
  (@user_defined.keys + @options.keys).uniq(&:downcase)
end

#lengthInteger Also known as: count, size

Returns The length of the registered keys.

Returns:

  • (Integer)

    The length of the registered keys



236
237
238
# File 'lib/msf/core/data_store.rb', line 236

def length
  keys.length
end

#merge(other) ⇒ Object

Override merge to ensure we merge the aliases and imported hashes

Parameters:

  • other (Msf::Datastore, Hash)


381
382
383
384
# File 'lib/msf/core/data_store.rb', line 381

def merge(other)
  ds = self.copy
  ds.merge!(other)
end

#merge!(other) ⇒ Object Also known as: update

Merge the other object into the current datastore’s aliases and imported hashes

Parameters:

  • other (Msf::Datastore, Hash)


347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/msf/core/data_store.rb', line 347

def merge!(other)
  if other.is_a?(DataStore)
    self.aliases.merge!(other.aliases)
    self.options.merge!(other.options)
    self.defaults.merge!(other.defaults)
    other.user_defined.each do |k, v|
      @user_defined[find_key_case(k)] = v
    end
  else
    other.each do |k, v|
      self[k] = v
    end
  end

  self
end

#remove_option(name) ⇒ nil

Removes an option and any associated value

Parameters:

  • name (String)

    the option name

Returns:

  • (nil)


132
133
134
135
136
137
138
139
# File 'lib/msf/core/data_store.rb', line 132

def remove_option(name)
  k = find_key_case(name)
  @user_defined.delete(k)
  @aliases.delete_if { |_, v| v.casecmp?(k) }
  @options.delete_if { |option_name, _v| option_name.casecmp?(k) || option_name.casecmp?(name) }

  nil
end

#reverse_merge!(other) ⇒ Object

Reverse Merge the other object into the current datastore’s aliases and imported hashes Equivalent to ActiveSupport’s reverse_merge! functionality.

Parameters:

  • other (Msf::Datastore)

Raises:

  • (ArgumentError)


371
372
373
374
375
# File 'lib/msf/core/data_store.rb', line 371

def reverse_merge!(other)
  raise ArgumentError, "invalid error type #{other.class}, expected ::Msf::DataStore" unless other.is_a?(Msf::DataStore)

  copy_state(other.merge(self))
end

#search_for(key) ⇒ DataStoreSearchResult

Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc.

Parameters:

  • key (String)

    The key to search for

Returns:



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/msf/core/data_store.rb', line 447

def search_for(key)
  k = find_key_case(key)
  return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k)

  option = @options.fetch(k) { @options.find { |option_name, _option| option_name.casecmp?(k) }&.last }
  if option
    # If the key isn't present - check any additional fallbacks that have been registered with the option.
    # i.e. handling the scenario of SMBUser not being explicitly set, but the option has registered a more
    # generic 'Username' fallback
    option.fallbacks.each do |fallback|
    fallback_search = search_for(fallback)
      if fallback_search.found?
        return search_result(:option_fallback, fallback_search.value, fallback_key: fallback)
      end
    end
  end

  # Checking for imported default values, ignoring case again
  imported_default_match = @defaults.find { |default_key, _default_value| default_key.casecmp?(k) }
  return search_result(:imported_default, imported_default_match.last) if imported_default_match
  return search_result(:option_default, option.default) if option

  search_result(:not_found, nil)
end

#search_result(result, value, fallback_key: nil) ⇒ Object (protected)



546
547
548
# File 'lib/msf/core/data_store.rb', line 546

def search_result(result, value, fallback_key: nil)
  DataStoreSearchResult.new(result, value, namespace: :global_data_store, fallback_key: fallback_key)
end

#store(k, v) ⇒ Object

Case-insensitive wrapper around store; Skips option validation entirely



98
99
100
# File 'lib/msf/core/data_store.rb', line 98

def store(k,v)
  @user_defined[find_key_case(k)] = v
end

#to_external_message_hObject

Hack on a hack for the external modules



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/msf/core/data_store.rb', line 279

def to_external_message_h
  datastore_hash = {}

  array_nester = ->(arr) do
    if arr.first.is_a? Array
      arr.map &array_nester
    else
      arr.map { |item| item.to_s.dup.force_encoding('UTF-8') }
    end
  end

  self.keys.each do |k|
    # TODO arbitrary depth
    if self[k].is_a? Array
      datastore_hash[k.to_s.dup.force_encoding('UTF-8')] = array_nester.call(self[k])
    else
      datastore_hash[k.to_s.dup.force_encoding('UTF-8')] = self[k].to_s.dup.force_encoding('UTF-8')
    end
  end
  datastore_hash
end

#to_file(path, name = 'global') ⇒ Object

Persists the contents of the data store to a file



304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/msf/core/data_store.rb', line 304

def to_file(path, name = 'global')
  ini = Rex::Parser::Ini.new(path)

  ini.add_group(name)

  # Save all user-defined options to the file.
  @user_defined.each_pair { |k, v|
    ini[name][k] = v
  }

  ini.to_file(path)
end

#to_hObject

Override Hash’s to_h method so we can include the original case of each key (failing to do this breaks a number of places in framework and pro that use serialized datastores)



270
271
272
273
274
275
276
# File 'lib/msf/core/data_store.rb', line 270

def to_h
  datastore_hash = {}
  self.keys.each do |k|
    datastore_hash[k.to_s] = self[k]
  end
  datastore_hash
end

#to_s(delim = ' ') ⇒ Object

Serializes the options in the datastore to a string.



257
258
259
260
261
262
263
264
265
# File 'lib/msf/core/data_store.rb', line 257

def to_s(delim = ' ')
  str = ''

  keys.sort.each { |key|
    str << "#{key}=#{self[key]}" + ((str.length) ? delim : '')
  }

  str
end

#unset(key) ⇒ Object

unset the current key from the datastore

Parameters:

  • key (String)

    The key to search for



113
114
115
116
117
118
119
# File 'lib/msf/core/data_store.rb', line 113

def unset(key)
  k = find_key_case(key)
  search_result = search_for(k)
  @user_defined.delete(k)

  search_result.value
end

#update_value(k, v) ⇒ Object

Updates a value in the datastore with the specified name, k, to the specified value, v. Skips option validation entirely.



106
107
108
# File 'lib/msf/core/data_store.rb', line 106

def update_value(k, v)
  store(k, v)
end