Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions lib/msf/core/module_data_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def initialize(m)
super()

@_module = m
@deregistered_keys = []
end

#
Expand All @@ -27,6 +28,64 @@ def copy
new_instance
end

# Grab any available deregistered_keys, otherwise return a blank array
#
# @param [Msf::DataStore] other The other datastore to copy state from
# @return [Msf::DataStore] the current datastore instance
def copy_state(other)
super
incoming = other.respond_to?(:deregistered_keys, true) ? other.deregistered_keys : []
incoming.each do |key|
@deregistered_keys << key unless @deregistered_keys.any? { |k| k.casecmp?(key) }
end

# Strip deregistered keys from @user_defined in case the parent's copy_state
# wrote them in (e.g. reverse_merge! calls copy_state with a merged DataStore
# that may contain values for deregistered keys).
@deregistered_keys.each do |key|
k = find_key_case(key)
@user_defined.delete(k)
end

# Need self here to return datastore instance from parent
self
end
Comment thread
cgranleese-r7 marked this conversation as resolved.

# Track deregistered keys
#
# @param [String] name
# @return [nil]
def remove_option(name)
super
# Resolve to the canonical cased key that the datastore actually uses so
# that filtering later works correctly regardless of what case or alias the
# caller passed to deregister_options.
canonical = find_key_case(name)
@deregistered_keys << canonical unless @deregistered_keys.any? { |k| k.casecmp?(canonical) }

# Need nil here to return original nil value from parent
nil
end
Comment thread
cgranleese-r7 marked this conversation as resolved.

# Imports options and clears any matching keys from the deregistered list.
# This allows a module to call deregister_options followed by register_options
# for the same key and ensure we remove the re-registered option from the
# tracked @deregistered_keys. Also clears aliases so that a deregistered
# key re-introduced as an alias of a new option is not incorrectly filtered.
#
# @param [Msf::OptionContainer] options
# @param [String, nil] imported_by
# @param [Boolean] overwrite
def import_options(options, imported_by = nil, overwrite = true)
options.each_option do |name, option|
@deregistered_keys.delete_if { |k| k.casecmp?(name) }
option.aliases.each do |alias_name|
@deregistered_keys.delete_if { |k| k.casecmp?(alias_name) }
end
end
super
end

# Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc.
# If a value is not present in the current datastore, the global parent store will be referenced instead
#
Expand All @@ -36,6 +95,9 @@ def search_for(key)
k = find_key_case(key)
return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k)

# If the module has not registered the key then return early as it has been deregistered
return search_result(:not_found, nil) if should_filter_key?(key)

# Preference globally set values over a module's option default
framework_datastore_search = search_framework_datastore(key)
return framework_datastore_search if framework_datastore_search.found? && !framework_datastore_search.default?
Expand All @@ -61,8 +123,39 @@ def search_for(key)
search_framework_datastore(k)
end

# Override merge! to strip deregistered keys from both DataStore and Hash
# merges. This is the primary guard against external callers injecting values
# for options the module has explicitly removed.
#
# @param [Msf::DataStore, Hash] other
# @return [Msf::ModuleDataStore]
def merge!(other)
super
@deregistered_keys.each do |key|
k = find_key_case(key)
@user_defined.delete(k)
@options.delete_if { |opt_name, _| opt_name.casecmp?(k) }
@aliases.delete_if { |_, v| v.casecmp?(k) }
end
self
end

protected

attr_reader :deregistered_keys

# Returns true when the write should be silently dropped because the key
# was explicitly deregistered via deregister_options.
# Normalises the incoming key via find_key_case so that differently-cased
# references to the same key are correctly matched against @deregistered_keys.
#
# @param [String] key
# @return [Boolean]
def should_filter_key?(key)
canonical = find_key_case(key)
@deregistered_keys.any? { |deregistered| deregistered.casecmp?(canonical) }
end
Comment thread
cgranleese-r7 marked this conversation as resolved.

# Search the framework datastore
#
# @param [String] key The key to search for
Expand Down
Loading
Loading