diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index 26caf1b2f3a37..7b1d452eab6c2 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -569,53 +569,18 @@ def self.dump_generic_module(mod, indent = '') # @param missing [Boolean] dump only empty required options. # @return [String] the string form of the information. def self.dump_options(mod, indent = '', missing = false) - options = mod.options.map { |_name, option| option } - options_grouped_by_conditions = options.group_by(&:conditions) + all_options = mod.options.map { |_name, option| option } + + conditional_options, option_groups = all_options.partition { |option| option.group.nil? } + options_grouped_by_conditions = conditional_options.group_by(&:conditions) + options_by_group = option_groups.group_by { |option| option.group if mod.options.keys.include?(option.name) } options_with_conditions = ''.dup options_without_conditions = ''.dup + options_group = ''.dup options_grouped_by_conditions.each do |conditions, options| - tbl = Rex::Text::Table.new( - 'Indent' => indent.length, - 'Columns' => - [ - 'Name', - 'Current Setting', - 'Required', - 'Description' - ]) - - options.sort_by(&:name).each do |opt| - name = opt.name - if mod.datastore.is_a?(Msf::DataStoreWithFallbacks) - val = mod.datastore[name] - else - val = mod.datastore[name].nil? ? opt.default : mod.datastore[name] - end - - next if (opt.advanced?) - next if (opt.evasion?) - next if (missing && opt.valid?(val)) - - desc = opt.desc.dup - - # Hint at RPORT proto by regexing mixins - if name == 'RPORT' && opt.kind_of?(Msf::OptPort) - mod.class.included_modules.each do |m| - case m.name - when /tcp/i, /HttpClient$/ - desc << ' (TCP)' - break - when /udp/i - desc << ' (UDP)' - break - end - end - end - - tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", desc ] - end + tbl = options_table(missing, mod, options, indent) next if conditions.any? && tbl.rows.empty? @@ -627,10 +592,59 @@ def self.dump_options(mod, indent = '', missing = false) end end - result = "#{options_without_conditions}#{options_with_conditions}" + options_by_group.each do |group, options| + tbl = options_table(missing, mod, options, indent) + options_group << "\n\n#{indent}#{group.description}:\n\n" + options_group << tbl.to_s + end + + result = "#{options_without_conditions}#{options_with_conditions}#{options_group}" result end + def self.options_table(missing, mod, options, indent) + tbl = Rex::Text::Table.new( + 'Indent' => indent.length, + 'Columns' => + [ + 'Name', + 'Current Setting', + 'Required', + 'Description' + ]) + options.sort_by(&:name).each do |opt| + # Skip over advanced or evasion options + next if (opt.advanced? || opt.evasion?) + + name = opt.name + if mod.datastore.is_a?(Msf::DataStoreWithFallbacks) + val = mod.datastore[name] + else + val = mod.datastore[name].nil? ? opt.default : mod.datastore[name] + end + next if (missing && opt.valid?(val)) + + desc = opt.desc.dup + + # Hint at RPORT proto by regexing mixins + if name == 'RPORT' && opt.kind_of?(Msf::OptPort) + mod.class.included_modules.each do |m| + case m.name + when /tcp/i, /HttpClient$/ + desc << ' (TCP)' + break + when /udp/i + desc << ' (UDP)' + break + end + end + end + + tbl << [name, opt.display_value(val), opt.required? ? "yes" : "no", desc] + end + tbl + end + # Dumps the advanced options associated with the supplied module. # # @param mod [Msf::Module] the module. diff --git a/lib/msf/core/opt.rb b/lib/msf/core/opt.rb index 964e6dbcc5f57..bf3731721ea0c 100644 --- a/lib/msf/core/opt.rb +++ b/lib/msf/core/opt.rb @@ -44,8 +44,8 @@ def self.RHOSTS(default= nil, required=true, desc="The target host(s), see https Msf::OptRhosts.new('RHOSTS', [ required, desc, default ], aliases: [ 'RHOST' ]) end - def self.RHOST(default=nil, required=true, desc="The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html") - Msf::OptRhosts.new('RHOSTS', [ required, desc, default ], aliases: [ 'RHOST' ]) + def self.RHOST(default=nil, required=true, desc="The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html", **kwargs) + Msf::OptRhosts.new('RHOSTS', [ required, desc, default ], aliases: [ 'RHOST' ], **kwargs) end # @return [OptPort] diff --git a/lib/msf/core/opt_base.rb b/lib/msf/core/opt_base.rb index dccda5031d8bf..fb302a404c994 100644 --- a/lib/msf/core/opt_base.rb +++ b/lib/msf/core/opt_base.rb @@ -26,7 +26,7 @@ class OptBase # def initialize(in_name, attrs = [], required: false, desc: nil, default: nil, conditions: [], enums: [], regex: nil, aliases: [], max_length: nil, - fallbacks: []) + fallbacks: [], group: nil) self.name = in_name self.advanced = false self.evasion = false @@ -34,6 +34,7 @@ def initialize(in_name, attrs = [], self.max_length = max_length self.conditions = conditions self.fallbacks = fallbacks + self.group = group if attrs.is_a?(String) || attrs.length == 0 self.required = required @@ -230,6 +231,9 @@ def invalid_value_length?(value) # attr_accessor :max_length + # @return [Msf::OptionGroup, nil] The option group that this option belongs to if any + attr_accessor :group + protected attr_writer :required, :desc, :default # :nodoc: diff --git a/lib/msf/core/opt_condition.rb b/lib/msf/core/opt_condition.rb index fcfd2744a176a..bb189031b383a 100644 --- a/lib/msf/core/opt_condition.rb +++ b/lib/msf/core/opt_condition.rb @@ -3,9 +3,10 @@ module Msf module OptCondition # Check a condition's result - # @param [Msf::Module] mod The module module - # @param [Msf::OptBase] opt the option which has conditions present - # @return [String] + # @param [String] left_value The left hand side of the condition + # @param [String] operator The conditions comparison operator + # @param [String] right_value The right hand side of the condition + # @return [Boolean] def self.eval_condition(left_value, operator, right_value) case operator.to_sym when :== @@ -16,6 +17,8 @@ def self.eval_condition(left_value, operator, right_value) right_value.include?(left_value) when :nin !right_value.include?(left_value) + else + raise ArgumentError("Operator: #{operator} is invalid") end end diff --git a/lib/msf/core/option_group.rb b/lib/msf/core/option_group.rb new file mode 100644 index 0000000000000..9dd3628a26a5d --- /dev/null +++ b/lib/msf/core/option_group.rb @@ -0,0 +1,13 @@ +# -*- coding: binary -*- + +module Msf + class OptionGroup + + attr_accessor :name, :description + + def initialize(name:, description:) + self.name = name + self.description = description + end + end +end diff --git a/lib/msf/core/optional_session.rb b/lib/msf/core/optional_session.rb index 92080142fb6d7..93903b25fa58b 100644 --- a/lib/msf/core/optional_session.rb +++ b/lib/msf/core/optional_session.rb @@ -7,5 +7,9 @@ module Msf module OptionalSession include Msf::SessionCompatibility + + def session_enabled?(session_feature_name) + framework.features.enabled?(session_feature_name) + end end end diff --git a/lib/msf/core/optional_session/smb.rb b/lib/msf/core/optional_session/smb.rb index 9a1c7cc0fa97e..0b73e1f55d6cf 100644 --- a/lib/msf/core/optional_session/smb.rb +++ b/lib/msf/core/optional_session/smb.rb @@ -5,6 +5,9 @@ module OptionalSession module SMB include Msf::OptionalSession + FEATURE_NAME = Msf::FeatureManager::SMB_SESSION_TYPE + RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT SMBDomain SMBUser SMBPass THREADS] + def initialize(info = {}) super( update_info( @@ -13,24 +16,36 @@ def initialize(info = {}) ) ) - - if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE) + if session_enabled? + session_group = Msf::OptionGroup.new(name: 'SESSION', description: 'Used when connecting via an existing SESSION') + rhost_group = Msf::OptionGroup.new(name: 'RHOST', description: 'Used when making a new connection via RHOSTS') register_options( [ - Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]), + Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ], group: session_group), Msf::Opt::RHOST(nil, false), - Msf::Opt::RPORT(443, false) + Msf::Opt::RPORT(445, false), ] ) + + RHOST_GROUP_OPTIONS.each do |option_name| + if options[option_name] + options[option_name].group = rhost_group + end + end + add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr') end end def session - return nil unless framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE) + return nil unless session_enabled? super end + + def session_enabled? + super(FEATURE_NAME) + end end end end