Files
metasploit-gs/lib/msf/core/option_container.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

347 lines
9.3 KiB
Ruby
Raw Normal View History

# -*- coding: binary -*-
2005-05-21 17:57:00 +00:00
module Msf
2015-04-16 11:02:01 -05:00
autoload :Opt, 'msf/core/opt'
autoload :OptBase, 'msf/core/opt_base'
autoload :OptAddress, 'msf/core/opt_address'
2017-05-02 09:32:11 -05:00
autoload :OptAddressLocal, 'msf/core/opt_address_local'
2015-04-16 11:02:01 -05:00
autoload :OptAddressRange, 'msf/core/opt_address_range'
autoload :OptBool, 'msf/core/opt_bool'
autoload :OptEnum, 'msf/core/opt_enum'
autoload :OptInt, 'msf/core/opt_int'
2017-08-05 02:21:31 -05:00
autoload :OptFloat, 'msf/core/opt_float'
2015-04-16 11:02:01 -05:00
autoload :OptPath, 'msf/core/opt_path'
autoload :OptPort, 'msf/core/opt_port'
autoload :OptRaw, 'msf/core/opt_raw'
autoload :OptRegexp, 'msf/core/opt_regexp'
autoload :OptString, 'msf/core/opt_string'
2005-05-21 17:57:00 +00:00
2006-01-05 03:57:12 +00:00
#
# The options purpose in life is to associate named options with arbitrary
# values at the most simplistic level. Each {Msf::Module} contains an
# OptionContainer that is used to hold the various options that the module
# depends on. Example of options that are stored in the OptionContainer are
# rhost and rport for payloads or exploits that need to connect to a host
# and port, for instance.
#
# The core supported option types are:
#
# * {OptString} - Multi-byte character string
# * {OptRaw} - Multi-byte raw string
# * {OptBool} - Boolean true or false indication
# * {OptPort} - TCP/UDP service port
# * {OptAddress} - IP address or hostname
# * {OptPath} - Path name on disk or an Object ID
# * {OptInt} - An integer value
2017-08-05 02:21:31 -05:00
# * {OptFloat} - A float value
# * {OptEnum} - Select from a set of valid values
# * {OptAddressRange} - A subnet or range of addresses
# * {OptRegexp} - Valid Ruby regular expression
2006-01-05 03:57:12 +00:00
#
class OptionContainer < Hash
2013-08-30 16:28:33 -05:00
#
# Merges in the supplied options and converts them to a OptBase
# as necessary.
#
def initialize(opts = {})
self.sorted = []
self.groups = {}
2013-08-30 16:28:33 -05:00
add_options(opts)
2014-04-02 23:51:33 +02:00
end
2013-08-30 16:28:33 -05:00
#
# Return the value associated with the supplied name.
#
def [](name)
return get(name)
end
2013-08-30 16:28:33 -05:00
#
# Return the option associated with the supplied name.
#
def get(name)
begin
return fetch(name)
rescue
end
end
2013-08-30 16:28:33 -05:00
#
# Returns whether or not the container has any options,
# excluding advanced (and evasions).
#
def has_options?
each_option { |name, opt|
return true if (opt.advanced? == false)
2005-05-21 17:57:00 +00:00
}
2005-05-21 17:57:00 +00:00
return false
end
#
# Returns whether or not the container has any advanced
# options.
#
def has_advanced_options?
each_option { |name, opt|
return true if (opt.advanced? == true)
}
2005-05-21 17:57:00 +00:00
return false
end
#
# Returns whether or not the container has any evasion
# options.
#
def has_evasion_options?
each_option { |name, opt|
return true if (opt.evasion? == true)
}
return false
2005-05-21 17:57:00 +00:00
end
#
# Removes an option.
#
# @param [String] name the option name
def remove_option(name)
delete(name)
sorted.each_with_index { |e, idx|
sorted[idx] = nil if (e[0] == name)
}
sorted.delete(nil)
end
2013-08-30 16:28:33 -05:00
#
# Adds one or more options.
#
def add_options(opts, owner = nil, advanced = false, evasion = false)
return false if (opts == nil)
2021-06-03 11:43:09 +01:00
if opts.kind_of?(Array)
add_options_array(opts, owner, advanced, evasion)
2011-06-30 06:52:52 +00:00
else
add_options_hash(opts, owner, advanced, evasion)
2011-06-30 06:52:52 +00:00
end
2005-05-21 17:57:00 +00:00
end
2013-08-30 16:28:33 -05:00
#
# Add options from a hash of names.
#
def add_options_hash(opts, owner = nil, advanced = false, evasion = false)
opts.each_pair { |name, opt|
add_option(opt, name, owner, advanced, evasion)
}
end
2013-08-30 16:28:33 -05:00
#
# Add options from an array of option instances or arrays.
#
def add_options_array(opts, owner = nil, advanced = false, evasion = false)
opts.each { |opt|
add_option(opt, nil, owner, advanced, evasion)
}
end
2013-08-30 16:28:33 -05:00
#
# Adds an option.
#
def add_option(option, name = nil, owner = nil, advanced = false, evasion = false)
2021-06-03 11:43:09 +01:00
if option.kind_of?(Array)
option = option.shift.new(name, option)
2021-06-03 11:43:09 +01:00
elsif !option.kind_of?(OptBase)
raise ArgumentError,
"The option named #{name} did not come in a compatible format.",
caller
end
option.advanced = advanced
option.evasion = evasion
option.owner = owner
2013-08-30 16:28:33 -05:00
self.store(option.name, option)
2013-08-30 16:28:33 -05:00
# Re-calculate the sorted list
self.sorted = self.sort
2012-01-23 12:25:52 -06:00
end
2013-08-30 16:28:33 -05:00
#
# Alias to add advanced options that sets the proper state flag.
#
def add_advanced_options(opts, owner = nil)
return false if (opts == nil)
2013-08-30 16:28:33 -05:00
add_options(opts, owner, true)
2012-01-23 12:25:52 -06:00
end
2013-08-30 16:28:33 -05:00
#
# Alias to add evasion options that sets the proper state flag.
#
def add_evasion_options(opts, owner = nil)
return false if (opts == nil)
add_options(opts, owner, false, true)
2005-06-05 05:42:14 +00:00
end
2013-08-30 16:28:33 -05:00
#
# Make sures that each of the options has a value of a compatible
# format and that all the required options are set.
def validate(datastore)
# First mutate the datastore and normalize all valid values before validating permutations of RHOST/etc.
each_pair do |name, option|
if option.valid?(datastore[name]) && (val = option.normalize(datastore[name])) != nil
# This *will* result in a module that previously used the
# global datastore to have its local datastore set, which
# means that changing the global datastore and re-running
# the same module will now use the newly-normalized local
# datastore value instead. This is mostly mitigated by
# forcing a clone through mod.replicant, but can break
# things in corner cases.
datastore[name] = val
end
end
# Validate all permutations of rhost combinations
2021-10-01 14:24:11 -04:00
if include?('RHOSTS') && !(datastore['RHOSTS'].blank? && !self['RHOSTS'].required)
2021-06-03 11:43:09 +01:00
error_options = Set.new
error_reasons = Hash.new do |hash, key|
hash[key] = []
end
rhosts_walker = Msf::RhostsWalker.new(datastore['RHOSTS'], datastore)
rhosts_count = rhosts_walker.count
unless rhosts_walker.valid?
2023-11-22 11:41:25 +11:00
errors = rhosts_walker.to_enum(:errors).to_a
grouped = errors.group_by { |err| err.cause.nil? ? nil : (err.cause.class.const_defined?(:MESSAGE) ? err.cause.class::MESSAGE : nil) }
2021-06-03 11:43:09 +01:00
error_options << 'RHOSTS'
2023-11-22 11:41:25 +11:00
if grouped.any?
grouped.each do | message, error_subset |
invalid_values = error_subset.map(&:value).take(5)
message = 'Unexpected values' if message.nil?
error_reasons['RHOSTS'] << "#{message}: #{invalid_values.join(', ')}"
end
2021-06-03 11:43:09 +01:00
end
end
2021-06-01 10:52:26 +01:00
2021-06-03 11:43:09 +01:00
rhosts_walker.each do |datastore|
each_pair do |name, option|
unless option.valid?(datastore[name])
error_options << name
if rhosts_count > 1
error_reasons[name] << "for rhosts value #{datastore['UNPARSED_RHOSTS']}"
end
2021-06-03 11:43:09 +01:00
end
2021-06-01 10:52:26 +01:00
end
end
2021-06-03 11:43:09 +01:00
unless error_options.empty?
raise Msf::OptionValidateError.new(error_options.to_a, reasons: error_reasons),
"One or more options failed to validate: #{error_options.to_a.join(', ')}."
end
2021-06-01 10:52:26 +01:00
else
2021-06-03 11:43:09 +01:00
error_options = []
each_pair do |name, option|
unless option.valid?(datastore[name])
2021-06-03 11:43:09 +01:00
error_options << name
2021-06-01 10:52:26 +01:00
end
2021-06-03 11:43:09 +01:00
end
2021-06-01 10:52:26 +01:00
2021-06-03 11:43:09 +01:00
unless error_options.empty?
raise Msf::OptionValidateError.new(error_options),
"One or more options failed to validate: #{error_options.join(', ')}."
end
end
2013-08-30 16:28:33 -05:00
2021-06-01 10:52:26 +01:00
true
end
2013-08-30 16:28:33 -05:00
#
# Creates string of options that were used from the datastore in VAR=VAL
# format separated by commas.
#
def options_used_to_s(datastore)
used = ''
2013-08-30 16:28:33 -05:00
each_pair { |name, option|
next if (datastore[name] == nil)
2013-08-30 16:28:33 -05:00
used += ", " if (used.length > 0)
used += "#{name}=#{datastore[name]}"
}
2013-08-30 16:28:33 -05:00
return used
end
2013-08-30 16:28:33 -05:00
#
# Enumerates each option name
#
def each_option(&block)
each_pair(&block)
end
2013-08-30 16:28:33 -05:00
#
# 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"
#
def each(&block)
list = []
self.keys.sort.each do |sidx|
list << [sidx, self[sidx]]
2005-05-21 17:57:00 +00:00
end
list.each(&block)
2005-05-21 17:57:00 +00:00
end
2013-08-30 16:28:33 -05:00
#
# Merges the options in this container with another option container and
# returns the sorted results.
#
def merge_sort(other_container)
result = self.dup
2013-08-30 16:28:33 -05:00
other_container.each { |name, opt|
if (result.get(name) == nil)
result[name] = opt
end
}
2013-08-30 16:28:33 -05:00
result.sort
end
2013-08-30 16:28:33 -05:00
# Adds an option group to the container
#
# @param option_group [Msf::OptionGroup]
def add_group(option_group)
groups[option_group.name] = option_group
end
# Removes an option group from the container by name
#
# @param group_name [String]
def remove_group(group_name)
groups.delete(group_name)
end
#
# The sorted array of options.
#
attr_reader :sorted
2013-08-30 16:28:33 -05:00
# @return [Hash<String, Msf::OptionGroup>]
attr_reader :groups
protected
2013-08-30 16:28:33 -05:00
attr_writer :sorted # :nodoc:
attr_writer :groups
end
end