From 25305ee1c9abcc03c547a77fddd90f15e01a8c13 Mon Sep 17 00:00:00 2001 From: Alan Foster Date: Tue, 7 Jul 2020 13:46:41 +0100 Subject: [PATCH 01/18] Add WrappedTable support with feature flag integration --- Gemfile | 2 + Gemfile.lock | 9 +- lib/msf/core.rb | 1 + lib/msf/core/feature_manager.rb | 68 +++++++++++ lib/msf/core/framework.rb | 7 ++ lib/msf/ui/console/command_dispatcher/core.rb | 115 ++++++++++++++++++ lib/msf/ui/console/table.rb | 35 +++--- spec/lib/msf/core/feature_manager_spec.rb | 79 ++++++++++++ 8 files changed, 295 insertions(+), 21 deletions(-) create mode 100644 lib/msf/core/feature_manager.rb create mode 100644 spec/lib/msf/core/feature_manager_spec.rb diff --git a/Gemfile b/Gemfile index b5731ee18f39..fb4042dbfd8d 100755 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,8 @@ source 'https://rubygems.org' gemspec name: 'metasploit-framework' gem 'sqlite3', '~>1.3.0' +# gem 'rex-text', path: '../rex-text' +gem 'rex-text', git: 'https://github.com/adfoster-r7/rex-text', branch: 'add-word-wrapping-to-rex-tables' # separate from test as simplecov is not run on travis-ci group :coverage do diff --git a/Gemfile.lock b/Gemfile.lock index f85e113b5f0f..9ce906649c6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/adfoster-r7/rex-text + revision: f7990ab39b8a347b0a16d2db9c52ea227b332457 + branch: add-word-wrapping-to-rex-tables + specs: + rex-text (0.2.27) + PATH remote: . specs: @@ -347,7 +354,6 @@ GEM rex-socket rex-text rex-struct2 (0.1.2) - rex-text (0.2.26) rex-zip (0.1.3) rex-text rexml (3.2.4) @@ -448,6 +454,7 @@ DEPENDENCIES pry-byebug rake redcarpet + rex-text! rspec-rails rspec-rerun rubocop (= 0.86.0) diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 9a07ef8d524f..92aa59055362 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -41,6 +41,7 @@ module Msf # Framework context and core classes require 'msf/core/framework' +require 'msf/core/feature_manager' require 'msf/core/db_manager' require 'msf/core/event_dispatcher' require 'msf/core/module_manager' diff --git a/lib/msf/core/feature_manager.rb b/lib/msf/core/feature_manager.rb new file mode 100644 index 000000000000..b6806d37715e --- /dev/null +++ b/lib/msf/core/feature_manager.rb @@ -0,0 +1,68 @@ +# -*- coding: binary -*- +# frozen_string_literal: true + +require 'msf/core/plugin' + +module Msf + ### + # + # The feature manager is responsible for managing feature flags that can change characteristics of framework. + # + ### + class FeatureManager + + include Framework::Offspring + + WRAPPED_TABLES = 'wrapped_tables' + DEFAULTS = [ + { + name: 'wrapped_tables', + description: 'When enabled Metasploit will wordwrap all tables to fit into the available terminal width', + enabled: false + }.freeze + ].freeze + + # + # Initializes the feature manager. + # + def initialize(framework) + @framework = framework + @flag_lookup = DEFAULTS.each_with_object({}) do |feature, acc| + key = feature[:name] + acc[key] = feature.dup + end + end + + def all + @flag_lookup.values + end + + def enabled?(name) + return false unless @flag_lookup[name] + + @flag_lookup[name][:enabled] == true + end + + def exists?(name) + @flag_lookup.key?(name) + end + + def names + all.map { |feature| feature[:name] } + end + + def set(name, value) + return false unless @flag_lookup[name] + + @flag_lookup[name][:enabled] = value + + if name == WRAPPED_TABLES + if value + Rex::Text::Table.wrap_tables! + else + Rex::Text::Table.unwrap_tables! + end + end + end + end +end diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index 4a4722e9c53e..73f307050fbc 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -81,6 +81,7 @@ def initialize(options={}) self.analyze = Analyze.new(self) self.plugins = PluginManager.new(self) self.browser_profiles = Hash.new + self.features = FeatureManager.new(self) # Configure the thread factory Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self) @@ -195,6 +196,11 @@ def version # framework objects to offer related objects/actions available. # attr_reader :analyze + # + # The framework instance's feature manager. The feature manager is responsible + # for configuring feature flags that can change characteristics of framework. + # + attr_reader :features # # The framework instance's dependency @@ -277,6 +283,7 @@ def search(match, logger: nil) attr_writer :db # :nodoc: attr_writer :browser_profiles # :nodoc: attr_writer :analyze # :nodoc: + attr_writer :features # :nodoc: private diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index e23400f09f22..664f9433cd67 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -113,6 +113,7 @@ def commands "color" => "Toggle color", "debug" => "Display information useful for debugging", "exit" => "Exit the console", + "features" => "Display the list of not yet released features that can be opted in to", "get" => "Gets the value of a context-specific variable", "getg" => "Gets the value of a global variable", "grep" => "Grep the output of another command", @@ -563,6 +564,120 @@ def cmd_exit(*args) alias cmd_quit cmd_exit + def cmd_features_help + print_line <<~CMD_FEATURE_HELP + Enable or disable unreleased features that Metasploit supports + + Usage: + features set feature_name [true/false] + features print + + Subcommands: + set - Enable or disable a given feature + print - show all available features and their current configuration + + Examples: + View available features: + features print + + Enable a feature: + features set new_feature true + + Disable a feature: + features set new_feature false + CMD_FEATURE_HELP + end + + # + # This method handles the features command which allows a user to opt into enabling + # features that are not yet released to everyone by default. + # + def cmd_features(*args) + args << 'print' if args.empty? + + action, *rest = args + case action + when 'set' + feature_name, value = rest + + unless framework.features.exists?(feature_name) + print_warning("Feature name '#{feature_name}' is not available. Either it has been removed, integrated by default, or does not exist.") + print_warning("Currently supported features: #{framework.features.names.join(', ')}") if framework.features.all.any? + print_warning('There are currently no features to toggle.') if framework.features.all.empty? + return + end + + unless %w[true false].include?(value) + print_warning('Please specify true or false to configure this feature.') + return + end + + framework.features.set(feature_name, value == 'true') + print_line("#{feature_name} => #{value}") + when 'print' + if framework.features.all.empty? + print_line 'There are no features to enable at this time. Either the features have been removed, or integrated by default.' + return + end + + features_table = Table.new( + Table::Style::Default, + 'Header' => 'Features table', + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Columns' => [ + '#', + 'Name', + 'Enabled', + 'Description', + ] + ) + + framework.features.all.each.with_index do |feature, index| + features_table << [ + index, + feature[:name], + feature[:enabled].to_s, + feature[:description] + ] + end + + print_line features_table.to_s + else + cmd_help + end + rescue StandardError => e + elog(e) + print_error(e.message) + end + + # + # Tab completion for the features command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array] the previously completed words on the command line. words is always + # at least 1 when tab completion has reached this stage since the command itself has been completed + def cmd_features_tabs(_str, words) + if words.length == 1 + return %w[set print] + end + + _command_name, action, *rest = words + ret = [] + case action + when 'set' + feature_name, _value = rest + + if framework.features.exists?(feature_name) + ret += %w[true false] + else + ret += framework.features.names + end + end + + ret + end + def cmd_history(*args) length = Readline::HISTORY.length diff --git a/lib/msf/ui/console/table.rb b/lib/msf/ui/console/table.rb index d97afb114117..3d5b6b4a1c81 100644 --- a/lib/msf/ui/console/table.rb +++ b/lib/msf/ui/console/table.rb @@ -18,13 +18,10 @@ module Style Default = 0 end - # - # Initializes a wrappered table with the supplied style and options. - # - def initialize(style, opts = {}) - self.style = style + def self.new(*args, &block) + style, opts = args - if (self.style == Style::Default) + if style == Style::Default opts['Indent'] = 3 if (!opts['Prefix']) opts['Prefix'] = "\n" @@ -32,28 +29,26 @@ def initialize(style, opts = {}) if (!opts['Postfix']) opts['Postfix'] = "\n" end + end - super(opts) + instance = super(opts, &block) + if style == Style::Default + instance.extend(DefaultStyle) end + instance end - # - # Print nothing if there are no rows if the style is default. - # - def to_s - if (style == Style::Default) + module DefaultStyle + # + # Print nothing if there are no rows if the style is default. + # + def to_s return '' if (rows.length == 0) - end - super + super + end end - -protected - - attr_accessor :style # :nodoc: - end - end end end diff --git a/spec/lib/msf/core/feature_manager_spec.rb b/spec/lib/msf/core/feature_manager_spec.rb new file mode 100644 index 000000000000..80da4ccf02a4 --- /dev/null +++ b/spec/lib/msf/core/feature_manager_spec.rb @@ -0,0 +1,79 @@ +# -*- coding:binary -*- + +require 'spec_helper' +require 'msf/core/feature_manager' + +RSpec.describe Msf::FeatureManager do + let(:mock_features) do + [ + { + name: 'filtered_options', + description: 'Add option filtering functionality to Metasploit', + enabled: false + }, + { + name: 'new_search_capabilities', + description: 'Add new search capabilities to Metasploit', + enabled: true + } + ] + end + let(:mock_framework) { instance_double(Msf::Framework) } + let(:subject) { described_class.new(mock_framework) } + + before(:each) do + stub_const('Msf::FeatureManager::DEFAULTS', mock_features) + end + + describe '#all' do + it { expect(subject.all).to eql mock_features } + end + + describe '#enabled?' do + it { expect(subject.enabled?('missing_option')).to be false } + it { expect(subject.enabled?('filtered_options')).to be false } + it { expect(subject.enabled?('new_search_capabilities')).to be true } + end + + describe '#exists?' do + it { expect(subject.exists?('missing_option')).to be false } + it { expect(subject.exists?('filtered_options')).to be true } + it { expect(subject.exists?('new_search_capabilities')).to be true } + end + + describe 'names' do + it { expect(subject.names).to eq ['filtered_options', 'new_search_capabilities'] } + end + + describe '#set' do + context 'when a flag is enabled' do + before(:each) do + subject.set('filtered_options', true) + end + + it { expect(subject.enabled?('missing_option')).to be false } + it { expect(subject.enabled?('filtered_options')).to be true } + it { expect(subject.enabled?('new_search_capabilities')).to be true } + end + + context 'when a flag is disabled' do + before(:each) do + subject.set('new_search_capabilities', false) + end + + it { expect(subject.enabled?('missing_option')).to be false } + it { expect(subject.enabled?('filtered_options')).to be false } + it { expect(subject.enabled?('new_search_capabilities')).to be false } + end + + context 'when the flag does not exist' do + before(:each) do + subject.set('missing_option', false) + end + + it { expect(subject.enabled?('missing_option')).to be false } + it { expect(subject.enabled?('filtered_options')).to be false } + it { expect(subject.enabled?('new_search_capabilities')).to be true } + end + end +end From fdb44990d3ef7420681916840fbe64e187d89d04 Mon Sep 17 00:00:00 2001 From: Alan Foster Date: Wed, 15 Jul 2020 20:59:17 +0100 Subject: [PATCH 02/18] Add feature configuration persistence --- lib/msf/core/feature_manager.rb | 39 ++++- lib/msf/core/framework.rb | 2 +- lib/msf/ui/console/command_dispatcher/core.rb | 8 +- lib/msf/ui/console/driver.rb | 6 + lib/msf/ui/debug.rb | 2 +- spec/lib/msf/core/feature_manager_spec.rb | 153 +++++++++++++++++- 6 files changed, 194 insertions(+), 16 deletions(-) diff --git a/lib/msf/core/feature_manager.rb b/lib/msf/core/feature_manager.rb index b6806d37715e..06476f0e1ccf 100644 --- a/lib/msf/core/feature_manager.rb +++ b/lib/msf/core/feature_manager.rb @@ -7,26 +7,26 @@ module Msf ### # # The feature manager is responsible for managing feature flags that can change characteristics of framework. - # + # Each feature will have a default value. The user can choose to override this default value if they wish. ### class FeatureManager - include Framework::Offspring + include Singleton + CONFIG_KEY = 'framework/features' WRAPPED_TABLES = 'wrapped_tables' DEFAULTS = [ { name: 'wrapped_tables', description: 'When enabled Metasploit will wordwrap all tables to fit into the available terminal width', - enabled: false + default_value: false }.freeze ].freeze # # Initializes the feature manager. # - def initialize(framework) - @framework = framework + def initialize @flag_lookup = DEFAULTS.each_with_object({}) do |feature, acc| key = feature[:name] acc[key] = feature.dup @@ -34,13 +34,16 @@ def initialize(framework) end def all - @flag_lookup.values + @flag_lookup.values.map do |feature| + feature.slice(:name, :description).merge(enabled: enabled?(feature[:name])) + end end def enabled?(name) return false unless @flag_lookup[name] - @flag_lookup[name][:enabled] == true + feature = @flag_lookup[name] + feature.key?(:user_preference) ? feature[:user_preference] : feature[:default_value] end def exists?(name) @@ -54,7 +57,7 @@ def names def set(name, value) return false unless @flag_lookup[name] - @flag_lookup[name][:enabled] = value + @flag_lookup[name][:user_preference] = value if name == WRAPPED_TABLES if value @@ -64,5 +67,25 @@ def set(name, value) end end end + + def load_config + conf = Msf::Config.load + conf.fetch(CONFIG_KEY, {}).each do |name, value| + set(name, value == 'true') + end + end + + def save_config + # Note, we intentionally omit features that have not explicitly been set by the user. + config = Msf::Config.load + old_config = config.fetch(CONFIG_KEY, {}) + new_config = @flag_lookup.values.each_with_object(old_config) do |feature, config| + next unless feature.key?(:user_preference) + + config.merge!(feature[:name] => feature[:user_preference].to_s) + end + + Msf::Config.save(CONFIG_KEY => new_config) + end end end diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index 73f307050fbc..be62b37e22d3 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -81,7 +81,7 @@ def initialize(options={}) self.analyze = Analyze.new(self) self.plugins = PluginManager.new(self) self.browser_profiles = Hash.new - self.features = FeatureManager.new(self) + self.features = FeatureManager.instance # Configure the thread factory Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 664f9433cd67..452607983943 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -601,7 +601,7 @@ def cmd_features(*args) feature_name, value = rest unless framework.features.exists?(feature_name) - print_warning("Feature name '#{feature_name}' is not available. Either it has been removed, integrated by default, or does not exist.") + print_warning("Feature name '#{feature_name}' is not available. Either it has been removed, integrated by default, or does not exist in this version of Metasploit.") print_warning("Currently supported features: #{framework.features.names.join(', ')}") if framework.features.all.any? print_warning('There are currently no features to toggle.') if framework.features.all.empty? return @@ -1242,6 +1242,12 @@ def cmd_save(*args) # Save the console config driver.save_config + begin + FeatureManager.instance.save_config + rescue StandardException => e + elog(e) + end + # Save the framework's datastore begin framework.save_config diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 13efc255ae19..e9a188fb06be 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -132,6 +132,12 @@ def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = { load_db_config(opts['Config']) + begin + FeatureManager.instance.load_config + rescue StandardException => e + elog(e) + end + if !framework.db || !framework.db.active if framework.db.error == "disabled" print_warning("Database support has been disabled") diff --git a/lib/msf/ui/debug.rb b/lib/msf/ui/debug.rb index 85067dfa60a7..fe37fb70201f 100644 --- a/lib/msf/ui/debug.rb +++ b/lib/msf/ui/debug.rb @@ -48,7 +48,7 @@ def self.datastore(framework, driver) # Delete all groups from the config ini that potentially have more up to date information ini.keys.each do |key| - unless key =~ %r{^framework/database} + unless key.start_with?("framework/database") || key.start_with?("framework/features") ini.delete(key) end end diff --git a/spec/lib/msf/core/feature_manager_spec.rb b/spec/lib/msf/core/feature_manager_spec.rb index 80da4ccf02a4..7187260fb081 100644 --- a/spec/lib/msf/core/feature_manager_spec.rb +++ b/spec/lib/msf/core/feature_manager_spec.rb @@ -9,24 +9,37 @@ { name: 'filtered_options', description: 'Add option filtering functionality to Metasploit', - enabled: false + default_value: false }, { name: 'new_search_capabilities', description: 'Add new search capabilities to Metasploit', - enabled: true + default_value: true } ] end - let(:mock_framework) { instance_double(Msf::Framework) } - let(:subject) { described_class.new(mock_framework) } + let(:subject) { described_class.send(:new) } before(:each) do stub_const('Msf::FeatureManager::DEFAULTS', mock_features) end describe '#all' do - it { expect(subject.all).to eql mock_features } + let(:expected_features) do + [ + { + name: 'filtered_options', + description: 'Add option filtering functionality to Metasploit', + enabled: false + }, + { + name: 'new_search_capabilities', + description: 'Add new search capabilities to Metasploit', + enabled: true + } + ] + end + it { expect(subject.all).to eql expected_features } end describe '#enabled?' do @@ -76,4 +89,134 @@ it { expect(subject.enabled?('new_search_capabilities')).to be true } end end + + describe "#load_config" do + before(:each) do + allow(Msf::Config).to receive(:load).and_return(Rex::Parser::Ini.from_s(config)) + subject.load_config + end + + context 'when the config file is empty' do + let(:config) do + <<~CONFIG + + CONFIG + end + + let(:expected_features) do + [ + { + name: 'filtered_options', + description: 'Add option filtering functionality to Metasploit', + enabled: false + }, + { + name: 'new_search_capabilities', + description: 'Add new search capabilities to Metasploit', + enabled: true + } + ] + end + + it { expect(subject.all).to eql expected_features } + end + + context 'when there are valid and invalid flags' do + let(:config) do + <<~CONFIG + [framework/features] + new_search_capabilities=false + missing_feature=true + CONFIG + end + + let(:expected_features) do + [ + { + name: 'filtered_options', + description: 'Add option filtering functionality to Metasploit', + enabled: false + }, + { + name: 'new_search_capabilities', + description: 'Add new search capabilities to Metasploit', + enabled: false + } + ] + end + + it { expect(subject.all).to eql expected_features } + end + end + + describe '#save_config' do + before(:each) do + allow(Msf::Config).to receive(:load).and_return(Rex::Parser::Ini.from_s(config)) + allow(Msf::Config).to receive(:save) + end + + context 'when there is no existing configuration' do + before(:each) do + subject.save_config + end + + let(:config) do + <<~CONFIG + [framework/features] + CONFIG + end + + let(:expected_config) do + { + "framework/features" => {} + } + end + + it { expect(Msf::Config).to have_received(:save).with(expected_config) } + end + + context 'when there is only a missing feature' do + before(:each) do + subject.save_config + end + + let(:config) do + <<~CONFIG + [framework/features] + missing_feature=true + CONFIG + end + + let(:expected_config) do + { + "framework/features" => { "missing_feature" => "true" } + } + end + + it { expect(Msf::Config).to have_received(:save).with(expected_config) } + end + + context 'when there are user preferences set' do + before(:each) do + subject.set('new_search_capabilities', true) + subject.save_config + end + + let(:config) do + <<~CONFIG + [framework/features] + new_search_capabilities=false + missing_feature=true + CONFIG + end + + let(:expected_config) do + { + "framework/features" => { "missing_feature" => "true", "new_search_capabilities"=>"true" } + } + end + + it { expect(Msf::Config).to have_received(:save).with(expected_config) } + end + end end From a644a58d9a6de643fa655237a8c4cf261ddefdf8 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Wed, 17 Jun 2020 14:59:19 +0100 Subject: [PATCH 03/18] Add rhost url macro --- lib/msf/ui/console/command_dispatcher/core.rb | 84 +++++++++++++------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 452607983943..9e51e93e15cd 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -134,7 +134,10 @@ def commands "unset" => "Unsets one or more context-specific variables", "unsetg" => "Unsets one or more global variables", "version" => "Show the framework and console library version numbers", - "spool" => "Write console output into a file as well the screen" + "spool" => "Write console output into a file as well the screen", + "set_macro" => "sets multiple options yay", + "setg_macro" => "sets multiple options yay" + } end @@ -1720,15 +1723,38 @@ def cmd_set_help print_line end - # - # Sets a name to a value in a context aware environment. - # - def cmd_set(*args) + def cmd_setg_macro(key, value) + cmd_set_macro(key, value, global: true) + end + + def cmd_set_macro(key, value, global: false) + case key.upcase + when "RHOST_URL" + set_rhost_url(value, global: global) + else + # type code here + end + end + def set_rhost_url(value, global: false) + rhost_url = URI(value) + set_option('RHOSTS', rhost_url.hostname, global: global) + set_option('RPORT', rhost_url.port, global: global) + set_option('SSL', %w{ssl https}.include?(rhost_url.scheme), global: global) + set_option('TARGETURI', rhost_url.path, global: global) + if %{http https}.include?(rhost_url.scheme) + set_option('VHOST', rhost_url.hostname, global: global) unless Rex::Socket.is_ip_addr?(rhost_url.hostname) + set_option('HttpUsername', rhost_url.user.to_s, global: global) + set_option('HttpPassword', rhost_url.password.to_s, global: global) + end + end + + + def cmd_set(*args) # Figure out if these are global variables global = false - if (args[0] == '-g') + if args[0] == '-g' args.shift global = true end @@ -1736,37 +1762,29 @@ def cmd_set(*args) # Decide if this is an append operation append = false - if (args[0] == '-a') + if args[0] == '-a' args.shift append = true end - # Determine which data store we're operating on - if (active_module and global == false) - datastore = active_module.datastore - else - global = true - datastore = self.framework.datastore - end - # Dump the contents of the active datastore if no args were supplied - if (args.length == 0) + if args.length == 0 # If we aren't dumping the global data store, then go ahead and # dump it first - if (!global) + unless global print("\n" + - Msf::Serializer::ReadableText.dump_datastore( - "Global", framework.datastore)) + Msf::Serializer::ReadableText.dump_datastore( + "Global", framework.datastore)) end # Dump the active datastore print("\n" + - Msf::Serializer::ReadableText.dump_datastore( - (global) ? "Global" : "Module: #{active_module.refname}", - datastore) + "\n") + Msf::Serializer::ReadableText.dump_datastore( + (global) ? "Global" : "Module: #{active_module.refname}", + datastore) + "\n") return true - elsif (args.length == 1) - if (not datastore[args[0]].nil?) + elsif args.length == 1 + if not datastore[args[0]].nil? print_line("#{args[0]} => #{datastore[args[0]]}") return true else @@ -1780,6 +1798,22 @@ def cmd_set(*args) name = args[0] value = args[1, args.length-1].join(' ') + set_option(name, value, global: global, append: append) + end + + # + # Sets a name to a value in a context aware environment. + # + def set_option(name, value, global: false, append: false) + + # Determine which data store we're operating on + if active_module and !global + datastore = active_module.datastore + else + global = true + datastore = self.framework.datastore + end + # Set PAYLOAD if name.upcase == 'PAYLOAD' && active_module && (active_module.exploit? || active_module.evasion?) value = trim_path(value, 'payload') @@ -1794,7 +1828,7 @@ def cmd_set(*args) end # If the driver indicates that the value is not valid, bust out. - if (driver.on_variable_set(global, name, value) == false) + if driver.on_variable_set(global, name, value) == false print_error("The value specified for #{name} is not valid.") return false end From 729e83a0ecb958f74d51eeccdf40e48a75a7e570 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Wed, 24 Jun 2020 23:03:38 +0100 Subject: [PATCH 04/18] Add new rhost url option --- lib/msf/core/data_store.rb | 13 +++- lib/msf/core/exploit/http/client.rb | 1 + lib/msf/core/opt.rb | 5 ++ lib/msf/core/opt_rhost_url.rb | 59 +++++++++++++++++++ lib/msf/core/option_container.rb | 1 + lib/msf/ui/console/command_dispatcher/core.rb | 32 +--------- 6 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 lib/msf/core/opt_rhost_url.rb diff --git a/lib/msf/core/data_store.rb b/lib/msf/core/data_store.rb index aae7a994dd4e..d4e5545073e4 100644 --- a/lib/msf/core/data_store.rb +++ b/lib/msf/core/data_store.rb @@ -43,14 +43,23 @@ def []=(k, v) end end - super(k,v) + if v.is_a? Hash + v.each { |key, value| self[key] = value } + else + super(k,v) + end end # # Case-insensitive wrapper around hash lookup # def [](k) - super(find_key_case(k)) + k = find_key_case(k) + if options[k].respond_to? :calculate_value + options[k].calculate_value(self) + else + super(k) + end end # diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 0d9465471092..8940f25e2bf1 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -22,6 +22,7 @@ def initialize(info = {}) register_options( [ + Opt::RHOST_URL, Opt::RHOST, Opt::RPORT(80), OptString.new('VHOST', [ false, "HTTP server virtual host" ]), diff --git a/lib/msf/core/opt.rb b/lib/msf/core/opt.rb index 20f8cc3a2f8c..154c06be3f24 100644 --- a/lib/msf/core/opt.rb +++ b/lib/msf/core/opt.rb @@ -62,6 +62,10 @@ def self.SSLVersion ) end + def self.RHOST_URL(default=nil, required=false, desc="The target URL, only applicable if there is a single URL") + Msf::OptRhostUrl.new(__method__.to_s, [ required, desc, default ]) + end + def self.stager_retry_options [ OptInt.new('StagerRetryCount', @@ -114,6 +118,7 @@ def self.http_header_options Proxies = Proxies() RHOST = RHOST() RHOSTS = RHOSTS() + RHOST_URL = RHOST_URL() RPORT = RPORT() SSLVersion = SSLVersion() end diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb new file mode 100644 index 000000000000..4736d306f40b --- /dev/null +++ b/lib/msf/core/opt_rhost_url.rb @@ -0,0 +1,59 @@ +# -*- coding: binary -*- + +module Msf + ### + # + # RHOST URL option. + # + ### + class OptRhostUrl < OptBase + def type + 'rhost url' + end + + + def normalize(value) + uri = URI(value) + option_hash = {} + # Blank this out since we don't know if this new value will have a `VHOST` to ensure we remove the old value + option_hash['VHOST'] = nil + + option_hash['RHOSTS'] = uri.hostname + option_hash['RPORT'] = uri.port + option_hash['SSL'] = %w{ssl https}.include?(uri.scheme) + + # Both `TARGETURI` and `URI` are used as datastore options to denote the path on a uri + option_hash['TARGETURI'] = uri.path + option_hash['URI'] = uri.path + + if uri.scheme and %{http https}.include?(uri.scheme) + option_hash['VHOST'] = uri.hostname unless Rex::Socket.is_ip_addr?(uri.hostname) + option_hash['HttpUsername'] = uri.user.to_s + option_hash['HttpPassword'] = uri.password.to_s + end + + option_hash + end + + def valid?(value, check_empty: true) + uri = URI(value) + return false unless uri.host != nil and uri.port != nil + super + end + + def calculate_value(datastore) + begin + uri = URI::Generic.build(host: datastore['RHOSTS']) + uri.port=datastore['RPORT'] + # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` + uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') + uri.user=datastore['HttpUsername'] + uri.password=datastore['HttpPassword'] if uri.user + uri + rescue URI::InvalidComponentError + return nil + end + end + + end +end diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index c9b213e9c03d..6a8f1e5a3e00 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -18,6 +18,7 @@ module Msf autoload :OptRaw, 'msf/core/opt_raw' autoload :OptRegexp, 'msf/core/opt_regexp' autoload :OptString, 'msf/core/opt_string' + autoload :OptRhostUrl, 'msf/core/opt_rhost_url' # # The options purpose in life is to associate named options with arbitrary diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 9e51e93e15cd..06658721f59e 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -134,10 +134,7 @@ def commands "unset" => "Unsets one or more context-specific variables", "unsetg" => "Unsets one or more global variables", "version" => "Show the framework and console library version numbers", - "spool" => "Write console output into a file as well the screen", - "set_macro" => "sets multiple options yay", - "setg_macro" => "sets multiple options yay" - + "spool" => "Write console output into a file as well the screen" } end @@ -1723,33 +1720,6 @@ def cmd_set_help print_line end - def cmd_setg_macro(key, value) - cmd_set_macro(key, value, global: true) - end - - def cmd_set_macro(key, value, global: false) - case key.upcase - when "RHOST_URL" - set_rhost_url(value, global: global) - else - # type code here - end - end - - def set_rhost_url(value, global: false) - rhost_url = URI(value) - set_option('RHOSTS', rhost_url.hostname, global: global) - set_option('RPORT', rhost_url.port, global: global) - set_option('SSL', %w{ssl https}.include?(rhost_url.scheme), global: global) - set_option('TARGETURI', rhost_url.path, global: global) - if %{http https}.include?(rhost_url.scheme) - set_option('VHOST', rhost_url.hostname, global: global) unless Rex::Socket.is_ip_addr?(rhost_url.hostname) - set_option('HttpUsername', rhost_url.user.to_s, global: global) - set_option('HttpPassword', rhost_url.password.to_s, global: global) - end - end - - def cmd_set(*args) # Figure out if these are global variables global = false From cc52f3abad207a59f298fdd3b73b7dd9fd79f813 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Wed, 24 Jun 2020 23:17:57 +0100 Subject: [PATCH 05/18] Check for an RHOSTS entry before attempting to create a URI --- lib/msf/core/opt_rhost_url.rb | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb index 4736d306f40b..d44bf1f24a81 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_rhost_url.rb @@ -42,16 +42,18 @@ def valid?(value, check_empty: true) end def calculate_value(datastore) - begin - uri = URI::Generic.build(host: datastore['RHOSTS']) - uri.port=datastore['RPORT'] - # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` - uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') - uri.user=datastore['HttpUsername'] - uri.password=datastore['HttpPassword'] if uri.user - uri - rescue URI::InvalidComponentError - return nil + if datastore['RHOSTS'] + begin + uri = URI::Generic.build(host: datastore['RHOSTS']) + uri.port=datastore['RPORT'] + # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` + uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') + uri.user=datastore['HttpUsername'] + uri.password=datastore['HttpPassword'] if uri.user + uri + rescue URI::InvalidComponentError + nil + end end end From 79c0c430403933546a3466ed058bfe1e711c7311 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Fri, 26 Jun 2020 11:59:25 +0100 Subject: [PATCH 06/18] remove leading `//` from rhost url option --- lib/msf/core/opt_rhost_url.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb index d44bf1f24a81..eabd6aa4da7a 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_rhost_url.rb @@ -50,7 +50,7 @@ def calculate_value(datastore) uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') uri.user=datastore['HttpUsername'] uri.password=datastore['HttpPassword'] if uri.user - uri + uri.to_s.delete_prefix('//') rescue URI::InvalidComponentError nil end From 53e89f7715d8efe07b96a88be8833dfd627da0f3 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Fri, 26 Jun 2020 17:40:59 +0100 Subject: [PATCH 07/18] If no path is specified default to `/` --- lib/msf/core/opt_rhost_url.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb index eabd6aa4da7a..4836453e471e 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_rhost_url.rb @@ -23,8 +23,8 @@ def normalize(value) option_hash['SSL'] = %w{ssl https}.include?(uri.scheme) # Both `TARGETURI` and `URI` are used as datastore options to denote the path on a uri - option_hash['TARGETURI'] = uri.path - option_hash['URI'] = uri.path + option_hash['TARGETURI'] = uri.path || '/' + option_hash['URI'] = uri.path || '/' if uri.scheme and %{http https}.include?(uri.scheme) option_hash['VHOST'] = uri.hostname unless Rex::Socket.is_ip_addr?(uri.hostname) @@ -35,7 +35,8 @@ def normalize(value) option_hash end - def valid?(value, check_empty: true) + def valid?(value, check_empty: false) + return true unless value uri = URI(value) return false unless uri.host != nil and uri.port != nil super @@ -50,7 +51,7 @@ def calculate_value(datastore) uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') uri.user=datastore['HttpUsername'] uri.password=datastore['HttpPassword'] if uri.user - uri.to_s.delete_prefix('//') + uri rescue URI::InvalidComponentError nil end From d72b84652030cb8eeac568e07695c074b2ffbd5f Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Mon, 29 Jun 2020 08:57:44 +0100 Subject: [PATCH 08/18] Add some extra validation and checks --- lib/msf/core/opt_rhost_url.rb | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb index 4836453e471e..72230db441a1 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_rhost_url.rb @@ -13,7 +13,9 @@ def type def normalize(value) - uri = URI(value) + return unless value + + uri = get_uri(value) option_hash = {} # Blank this out since we don't know if this new value will have a `VHOST` to ensure we remove the old value option_hash['VHOST'] = nil @@ -37,8 +39,8 @@ def normalize(value) def valid?(value, check_empty: false) return true unless value - uri = URI(value) - return false unless uri.host != nil and uri.port != nil + uri = get_uri(value) + false unless uri.host != nil and uri.port != nil super end @@ -51,12 +53,23 @@ def calculate_value(datastore) uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') uri.user=datastore['HttpUsername'] uri.password=datastore['HttpPassword'] if uri.user - uri + uri.to_s.delete_prefix('//') rescue URI::InvalidComponentError nil end end end + protected + + def get_uri(value) + begin + uri = URI(value) + rescue URI::InvalidURIError + uri = URI('//' + value) + end + uri + end + end end From 13fcabedf8009c0a68beac3c014e52d6f6e0a8f6 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Wed, 15 Jul 2020 16:18:31 +0100 Subject: [PATCH 09/18] Ran rubocop --- lib/msf/core/opt.rb | 2 +- lib/msf/core/opt_rhost_url.rb | 18 +++++++++--------- lib/msf/core/option_container.rb | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/msf/core/opt.rb b/lib/msf/core/opt.rb index 154c06be3f24..a1a675d1c4e2 100644 --- a/lib/msf/core/opt.rb +++ b/lib/msf/core/opt.rb @@ -63,7 +63,7 @@ def self.SSLVersion end def self.RHOST_URL(default=nil, required=false, desc="The target URL, only applicable if there is a single URL") - Msf::OptRhostUrl.new(__method__.to_s, [ required, desc, default ]) + Msf::OptRhostURL.new(__method__.to_s, [required, desc, default ]) end def self.stager_retry_options diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb index 72230db441a1..d19566469e19 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_rhost_url.rb @@ -6,12 +6,11 @@ module Msf # RHOST URL option. # ### - class OptRhostUrl < OptBase + class OptRhostURL < OptBase def type 'rhost url' end - def normalize(value) return unless value @@ -22,13 +21,13 @@ def normalize(value) option_hash['RHOSTS'] = uri.hostname option_hash['RPORT'] = uri.port - option_hash['SSL'] = %w{ssl https}.include?(uri.scheme) + option_hash['SSL'] = %w[ssl https].include?(uri.scheme) # Both `TARGETURI` and `URI` are used as datastore options to denote the path on a uri option_hash['TARGETURI'] = uri.path || '/' option_hash['URI'] = uri.path || '/' - if uri.scheme and %{http https}.include?(uri.scheme) + if uri.scheme && %(http https).include?(uri.scheme) option_hash['VHOST'] = uri.hostname unless Rex::Socket.is_ip_addr?(uri.hostname) option_hash['HttpUsername'] = uri.user.to_s option_hash['HttpPassword'] = uri.password.to_s @@ -39,8 +38,9 @@ def normalize(value) def valid?(value, check_empty: false) return true unless value + uri = get_uri(value) - false unless uri.host != nil and uri.port != nil + false unless !uri.host.nil? && !uri.port.nil? super end @@ -48,11 +48,11 @@ def calculate_value(datastore) if datastore['RHOSTS'] begin uri = URI::Generic.build(host: datastore['RHOSTS']) - uri.port=datastore['RPORT'] + uri.port = datastore['RPORT'] # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` - uri.path=(datastore['TARGETURI'] || datastore['URI'] || '/') - uri.user=datastore['HttpUsername'] - uri.password=datastore['HttpPassword'] if uri.user + uri.path = (datastore['TARGETURI'] || datastore['URI'] || '/') + uri.user = datastore['HttpUsername'] + uri.password = datastore['HttpPassword'] if uri.user uri.to_s.delete_prefix('//') rescue URI::InvalidComponentError nil diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 6a8f1e5a3e00..8fde70104909 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -18,7 +18,7 @@ module Msf autoload :OptRaw, 'msf/core/opt_raw' autoload :OptRegexp, 'msf/core/opt_regexp' autoload :OptString, 'msf/core/opt_string' - autoload :OptRhostUrl, 'msf/core/opt_rhost_url' + autoload :OptRhostURL, 'msf/core/opt_rhost_url' # # The options purpose in life is to associate named options with arbitrary From 4125f7500f810ec3b17765c2654934c00e184c78 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Tue, 21 Jul 2020 22:46:49 +0100 Subject: [PATCH 10/18] Code review changes --- lib/msf/core/opt_rhost_url.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_rhost_url.rb index d19566469e19..5551c4ae55ee 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_rhost_url.rb @@ -63,12 +63,9 @@ def calculate_value(datastore) protected def get_uri(value) - begin - uri = URI(value) + URI(value) rescue URI::InvalidURIError - uri = URI('//' + value) - end - uri + URI('//' + value) end end From fbd0d0ed20de8f5c689e6f81b3466b54b35f9b1d Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Fri, 31 Jul 2020 12:05:02 +0100 Subject: [PATCH 11/18] Refactor rhost_url to be http/https only --- lib/msf/core/opt.rb | 2 +- .../core/{opt_rhost_url.rb => opt_http_rhost_url.rb} | 11 +++++++---- lib/msf/core/option_container.rb | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) rename lib/msf/core/{opt_rhost_url.rb => opt_http_rhost_url.rb} (89%) diff --git a/lib/msf/core/opt.rb b/lib/msf/core/opt.rb index a1a675d1c4e2..9c7544fe43ae 100644 --- a/lib/msf/core/opt.rb +++ b/lib/msf/core/opt.rb @@ -63,7 +63,7 @@ def self.SSLVersion end def self.RHOST_URL(default=nil, required=false, desc="The target URL, only applicable if there is a single URL") - Msf::OptRhostURL.new(__method__.to_s, [required, desc, default ]) + Msf::OptHTTPRhostURL.new(__method__.to_s, [required, desc, default ]) end def self.stager_retry_options diff --git a/lib/msf/core/opt_rhost_url.rb b/lib/msf/core/opt_http_rhost_url.rb similarity index 89% rename from lib/msf/core/opt_rhost_url.rb rename to lib/msf/core/opt_http_rhost_url.rb index 5551c4ae55ee..3d56f7f1d551 100644 --- a/lib/msf/core/opt_rhost_url.rb +++ b/lib/msf/core/opt_http_rhost_url.rb @@ -6,7 +6,7 @@ module Msf # RHOST URL option. # ### - class OptRhostURL < OptBase + class OptHTTPRhostURL < OptBase def type 'rhost url' end @@ -15,6 +15,8 @@ def normalize(value) return unless value uri = get_uri(value) + return unless uri + option_hash = {} # Blank this out since we don't know if this new value will have a `VHOST` to ensure we remove the old value option_hash['VHOST'] = nil @@ -53,7 +55,8 @@ def calculate_value(datastore) uri.path = (datastore['TARGETURI'] || datastore['URI'] || '/') uri.user = datastore['HttpUsername'] uri.password = datastore['HttpPassword'] if uri.user - uri.to_s.delete_prefix('//') + uri.scheme = datastore['SSL'] ? "https" : "http" + uri rescue URI::InvalidComponentError nil end @@ -63,10 +66,10 @@ def calculate_value(datastore) protected def get_uri(value) + value = 'http://' + value unless value.start_with?(/https?:\/\//) URI(value) rescue URI::InvalidURIError - URI('//' + value) + nil end - end end diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 8fde70104909..fd07a7ddc295 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -18,7 +18,7 @@ module Msf autoload :OptRaw, 'msf/core/opt_raw' autoload :OptRegexp, 'msf/core/opt_regexp' autoload :OptString, 'msf/core/opt_string' - autoload :OptRhostURL, 'msf/core/opt_rhost_url' + autoload :OptHTTPRhostURL, 'msf/core/opt_http_rhost_url' # # The options purpose in life is to associate named options with arbitrary From dd0a88362042632220e869ebb2d46523412ae280 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Fri, 31 Jul 2020 12:22:08 +0100 Subject: [PATCH 12/18] use HTTP/HTTPS uris when re-calculating the URI --- lib/msf/core/opt_http_rhost_url.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/msf/core/opt_http_rhost_url.rb b/lib/msf/core/opt_http_rhost_url.rb index 3d56f7f1d551..250823692b49 100644 --- a/lib/msf/core/opt_http_rhost_url.rb +++ b/lib/msf/core/opt_http_rhost_url.rb @@ -8,7 +8,7 @@ module Msf ### class OptHTTPRhostURL < OptBase def type - 'rhost url' + 'rhost http url' end def normalize(value) @@ -49,13 +49,13 @@ def valid?(value, check_empty: false) def calculate_value(datastore) if datastore['RHOSTS'] begin - uri = URI::Generic.build(host: datastore['RHOSTS']) + uri_type = datastore['SSL'] ? URI::HTTPS : URI::HTTP + uri = uri_type.build(host: datastore['RHOSTS']) uri.port = datastore['RPORT'] # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` uri.path = (datastore['TARGETURI'] || datastore['URI'] || '/') uri.user = datastore['HttpUsername'] uri.password = datastore['HttpPassword'] if uri.user - uri.scheme = datastore['SSL'] ? "https" : "http" uri rescue URI::InvalidComponentError nil @@ -66,10 +66,10 @@ def calculate_value(datastore) protected def get_uri(value) - value = 'http://' + value unless value.start_with?(/https?:\/\//) - URI(value) - rescue URI::InvalidURIError - nil + value = 'http://' + value unless value.start_with?(%r{https?://}) + URI(value) + rescue URI::InvalidURIError + nil end end end From 6c0d3bf56188630f69b7dead3c12ef12d3b688ab Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Mon, 3 Aug 2020 05:15:27 +0100 Subject: [PATCH 13/18] Add initial test suite for rhost url --- lib/msf/core/exploit/http/client.rb | 5 +- lib/msf/core/opt.rb | 59 +++++++++----------- lib/msf/core/opt_http_rhost_url.rb | 21 ++++++- lib/msf/core/opt_raw.rb | 2 +- spec/lib/msf/core/opt_http_rhost_url_spec.rb | 23 ++++++++ 5 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 spec/lib/msf/core/opt_http_rhost_url_spec.rb diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 8940f25e2bf1..7564ee5d7e1e 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -22,7 +22,6 @@ def initialize(info = {}) register_options( [ - Opt::RHOST_URL, Opt::RHOST, Opt::RPORT(80), OptString.new('VHOST', [ false, "HTTP server virtual host" ]), @@ -31,6 +30,10 @@ def initialize(info = {}) ], self.class ) + # if framework.features.enabled?("RHOST_HTTP_URL") + # register_options([OPT::RHOST_HTTP_URL]) + # end + register_advanced_options( [ OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests', diff --git a/lib/msf/core/opt.rb b/lib/msf/core/opt.rb index 9c7544fe43ae..5d557da4ed9c 100644 --- a/lib/msf/core/opt.rb +++ b/lib/msf/core/opt.rb @@ -14,92 +14,83 @@ module Msf # register_advanced_options([Opt::Proxies]) # module Opt - # @return [OptAddress] - def self.CHOST(default=nil, required=false, desc="The local client address") + def self.CHOST(default = nil, required = false, desc = 'The local client address') Msf::OptAddress.new(__method__.to_s, [ required, desc, default ]) end # @return [OptPort] - def self.CPORT(default=nil, required=false, desc="The local client port") + def self.CPORT(default = nil, required = false, desc = 'The local client port') Msf::OptPort.new(__method__.to_s, [ required, desc, default ]) end # @return [OptAddressLocal] - def self.LHOST(default=nil, required=true, desc="The listen address (an interface may be specified)") + def self.LHOST(default = nil, required = true, desc = 'The listen address (an interface may be specified)') Msf::OptAddressLocal.new(__method__.to_s, [ required, desc, default ]) end # @return [OptPort] - def self.LPORT(default=nil, required=true, desc="The listen port") + def self.LPORT(default = nil, required = true, desc = 'The listen port') Msf::OptPort.new(__method__.to_s, [ required, desc, default ]) end # @return [OptString] - def self.Proxies(default=nil, required=false, desc="A proxy chain of format type:host:port[,type:host:port][...]") + def self.Proxies(default = nil, required = false, desc = 'A proxy chain of format type:host:port[,type:host:port][...]') Msf::OptString.new(__method__.to_s, [ required, desc, default ]) end # @return [OptAddressRange] - def self.RHOSTS(default=nil, required=true, desc="The target host(s), range CIDR identifier, or hosts file with syntax 'file:'") + def self.RHOSTS(default = nil, required = true, desc = "The target host(s), range CIDR identifier, or hosts file with syntax 'file:'") Msf::OptAddressRange.new('RHOSTS', [ required, desc, default ]) end - def self.RHOST(default=nil, required=true, desc="The target host(s), range CIDR identifier, or hosts file with syntax 'file:'") + def self.RHOST(default = nil, required = true, desc = "The target host(s), range CIDR identifier, or hosts file with syntax 'file:'") Msf::OptAddressRange.new('RHOSTS', [ required, desc, default ], aliases: [ 'RHOST' ]) end # @return [OptPort] - def self.RPORT(default=nil, required=true, desc="The target port") + def self.RPORT(default = nil, required = true, desc = 'The target port') Msf::OptPort.new(__method__.to_s, [ required, desc, default ]) end # @return [OptEnum] def self.SSLVersion Msf::OptEnum.new('SSLVersion', - 'Specify the version of SSL/TLS to be used (Auto, TLS and SSL23 are auto-negotiate)', - enums: Rex::Socket::SslTcp.supported_ssl_methods - ) + 'Specify the version of SSL/TLS to be used (Auto, TLS and SSL23 are auto-negotiate)', + enums: Rex::Socket::SslTcp.supported_ssl_methods) end - def self.RHOST_URL(default=nil, required=false, desc="The target URL, only applicable if there is a single URL") + def self.RHOST_HTTP_URL(default = nil, required = false, desc = 'The target URL, only applicable if there is a single URL') Msf::OptHTTPRhostURL.new(__method__.to_s, [required, desc, default ]) end def self.stager_retry_options [ OptInt.new('StagerRetryCount', - 'The number of times the stager should retry if the first connect fails', - default: 10, - aliases: ['ReverseConnectRetries'] - ), + 'The number of times the stager should retry if the first connect fails', + default: 10, + aliases: ['ReverseConnectRetries']), OptInt.new('StagerRetryWait', - 'Number of seconds to wait for the stager between reconnect attempts', - default: 5 - ) + 'Number of seconds to wait for the stager between reconnect attempts', + default: 5) ] end def self.http_proxy_options [ OptString.new('HttpProxyHost', 'An optional proxy server IP address or hostname', - aliases: ['PayloadProxyHost'] - ), + aliases: ['PayloadProxyHost']), OptPort.new('HttpProxyPort', 'An optional proxy server port', - aliases: ['PayloadProxyPort'] - ), + aliases: ['PayloadProxyPort']), OptString.new('HttpProxyUser', 'An optional proxy server username', - aliases: ['PayloadProxyUser'], - max_length: Rex::Payloads::Meterpreter::Config::PROXY_USER_SIZE - 1 - ), + aliases: ['PayloadProxyUser'], + max_length: Rex::Payloads::Meterpreter::Config::PROXY_USER_SIZE - 1), OptString.new('HttpProxyPass', 'An optional proxy server password', - aliases: ['PayloadProxyPass'], - max_length: Rex::Payloads::Meterpreter::Config::PROXY_PASS_SIZE - 1 - ), + aliases: ['PayloadProxyPass'], + max_length: Rex::Payloads::Meterpreter::Config::PROXY_PASS_SIZE - 1), OptEnum.new('HttpProxyType', 'The type of HTTP proxy', - enums: ['HTTP', 'SOCKS'], - aliases: ['PayloadProxyType'] - ) + enums: ['HTTP', 'SOCKS'], + aliases: ['PayloadProxyType']) ] end @@ -118,7 +109,7 @@ def self.http_header_options Proxies = Proxies() RHOST = RHOST() RHOSTS = RHOSTS() - RHOST_URL = RHOST_URL() + RHOST_HTTP_URL = RHOST_HTTP_URL() RPORT = RPORT() SSLVersion = SSLVersion() end diff --git a/lib/msf/core/opt_http_rhost_url.rb b/lib/msf/core/opt_http_rhost_url.rb index 250823692b49..83da5d49570c 100644 --- a/lib/msf/core/opt_http_rhost_url.rb +++ b/lib/msf/core/opt_http_rhost_url.rb @@ -39,10 +39,11 @@ def normalize(value) end def valid?(value, check_empty: false) - return true unless value + return true unless value || required uri = get_uri(value) - false unless !uri.host.nil? && !uri.port.nil? + return false unless uri && !uri.host.nil? && !uri.port.nil? + super end @@ -66,10 +67,26 @@ def calculate_value(datastore) protected def get_uri(value) + return unless value + return if check_for_range(value) + value = 'http://' + value unless value.start_with?(%r{https?://}) URI(value) rescue URI::InvalidURIError nil end + + def check_for_range(value) + return false if value =~ /[^-0-9,.*\/]/ + walker = Rex::Socket::RangeWalker.new(value) + if walker&.valid? + # if there is only a single ip then it's not a range + return walker.length != 1 + end + rescue ::Exception + # couldn't create a range therefore it isn't one + return false + end + end end diff --git a/lib/msf/core/opt_raw.rb b/lib/msf/core/opt_raw.rb index b1443341651c..014296cb11da 100644 --- a/lib/msf/core/opt_raw.rb +++ b/lib/msf/core/opt_raw.rb @@ -28,7 +28,7 @@ def normalize(value) value end - def valid?(value=self.value) + def valid?(value=self.value, check_empty: true) value = normalize(value) return false if empty_required_value?(value) return super diff --git a/spec/lib/msf/core/opt_http_rhost_url_spec.rb b/spec/lib/msf/core/opt_http_rhost_url_spec.rb new file mode 100644 index 000000000000..acbc282de8d6 --- /dev/null +++ b/spec/lib/msf/core/opt_http_rhost_url_spec.rb @@ -0,0 +1,23 @@ +require 'msf/core/opt_http_rhost_url' + +RSpec.describe Msf::OptHTTPRhostURL do + subject(:opt) { described_class } + + valid_values = [ + { value: 'http://example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'https://example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 443, 'SSL' => true, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'http://user:pass@example.com:1234/somePath', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 1234, 'SSL' => false, 'TARGETURI' => '/somePath', 'URI' => '/somePath', 'VHOST' => 'example.com', 'HttpUsername' => 'user', 'HttpPassword' => 'pass' } }, + { value: 'http://127.0.0.1', normalized: { 'RHOSTS' => '127.0.0.1', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => nil, 'HttpUsername' => '', 'HttpPassword' => '' } } + ] + + invalid_values = [ + { value: '192.0.2.0/24' }, + { value: '192.0.2.0-255' }, + { value: '192.0.2.0,1-255' }, + { value: '192.0.2.*' }, + { value: '192.0.2.0-192.0.2.255' } + ] + + it_behaves_like 'an option', valid_values, invalid_values, 'rhost http url' +end From b14a74e605850928f4f37aa5ac94bf0ccb4fe225 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Mon, 3 Aug 2020 06:10:39 +0100 Subject: [PATCH 14/18] More tests --- spec/lib/msf/core/opt_http_rhost_url_spec.rb | 40 +++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/spec/lib/msf/core/opt_http_rhost_url_spec.rb b/spec/lib/msf/core/opt_http_rhost_url_spec.rb index acbc282de8d6..158769fd7f79 100644 --- a/spec/lib/msf/core/opt_http_rhost_url_spec.rb +++ b/spec/lib/msf/core/opt_http_rhost_url_spec.rb @@ -1,7 +1,7 @@ require 'msf/core/opt_http_rhost_url' RSpec.describe Msf::OptHTTPRhostURL do - subject(:opt) { described_class } + subject(:opt) { described_class.new('RHOST_HTTP_URL') } valid_values = [ { value: 'http://example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, @@ -20,4 +20,42 @@ ] it_behaves_like 'an option', valid_values, invalid_values, 'rhost http url' + + + calculate_values = [ + { value: 'http://example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'https://example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 443, 'SSL' => true, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'http://user:pass@example.com:1234/somePath', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 1234, 'SSL' => false, 'TARGETURI' => '/somePath', 'URI' => '/somePath', 'VHOST' => 'example.com', 'HttpUsername' => 'user', 'HttpPassword' => 'pass' } }, + { value: 'http://127.0.0.1', normalized: { 'RHOSTS' => '127.0.0.1', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => nil, 'HttpUsername' => '', 'HttpPassword' => '' } } + ] + + calculate_values_no_schema = [ + { value: 'example.com', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'example.com:443', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 443, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => 'example.com', 'HttpUsername' => '', 'HttpPassword' => '' } }, + { value: 'user:pass@example.com:1234/somePath', normalized: { 'RHOSTS' => 'example.com', 'RPORT' => 1234, 'SSL' => false, 'TARGETURI' => '/somePath', 'URI' => '/somePath', 'VHOST' => 'example.com', 'HttpUsername' => 'user', 'HttpPassword' => 'pass' } }, + { value: '127.0.0.1', normalized: { 'RHOSTS' => '127.0.0.1', 'RPORT' => 80, 'SSL' => false, 'TARGETURI' => '', 'URI' => '', 'VHOST' => nil, 'HttpUsername' => '', 'HttpPassword' => '' } } + ] + + describe '#calculate_value' do + calculate_values.each do | url | + + context url[:normalized].to_s do + it "should return #{url[:value]}" do + expect(subject.calculate_value(url[:normalized])).to eq(URI(url[:value])) + end + end + end + end + + describe '#calculate_value missing scheme' do + calculate_values_no_schema.each do | url | + + context url[:normalized].to_s do + it "should return #{url[:value]}" do + expect(subject.calculate_value(url[:normalized])).to eq(URI('http://' + url[:value])) + end + end + end + end + end From f4def2585c1d713c0d2f6a05efe0705885159aaf Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Mon, 3 Aug 2020 18:02:16 +0100 Subject: [PATCH 15/18] Add guard clause --- lib/msf/core/opt_http_rhost_url.rb | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/msf/core/opt_http_rhost_url.rb b/lib/msf/core/opt_http_rhost_url.rb index 83da5d49570c..793fbc553515 100644 --- a/lib/msf/core/opt_http_rhost_url.rb +++ b/lib/msf/core/opt_http_rhost_url.rb @@ -48,19 +48,18 @@ def valid?(value, check_empty: false) end def calculate_value(datastore) - if datastore['RHOSTS'] - begin - uri_type = datastore['SSL'] ? URI::HTTPS : URI::HTTP - uri = uri_type.build(host: datastore['RHOSTS']) - uri.port = datastore['RPORT'] - # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` - uri.path = (datastore['TARGETURI'] || datastore['URI'] || '/') - uri.user = datastore['HttpUsername'] - uri.password = datastore['HttpPassword'] if uri.user - uri - rescue URI::InvalidComponentError - nil - end + return unless datastore['RHOSTS'] + begin + uri_type = datastore['SSL'] ? URI::HTTPS : URI::HTTP + uri = uri_type.build(host: datastore['RHOSTS']) + uri.port = datastore['RPORT'] + # The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/` + uri.path = (datastore['TARGETURI'] || datastore['URI'] || '/') + uri.user = datastore['HttpUsername'] + uri.password = datastore['HttpPassword'] if uri.user + uri + rescue URI::InvalidComponentError + nil end end From 1a634fca0aadc405f49a876cdd8d2cbefb4898e1 Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Tue, 4 Aug 2020 14:58:29 +0100 Subject: [PATCH 16/18] Add feature flag and start fixing tests --- lib/msf/core/exploit/http/client.rb | 6 +++--- lib/msf/core/feature_manager.rb | 5 +++++ .../http/jboss/deployment_file_repository_spec.rb | 10 ++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 7564ee5d7e1e..aed10846103c 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -30,9 +30,9 @@ def initialize(info = {}) ], self.class ) - # if framework.features.enabled?("RHOST_HTTP_URL") - # register_options([OPT::RHOST_HTTP_URL]) - # end + if framework.features.enabled?("RHOST_HTTP_URL") + register_options([Opt::RHOST_HTTP_URL]) + end register_advanced_options( [ diff --git a/lib/msf/core/feature_manager.rb b/lib/msf/core/feature_manager.rb index 06476f0e1ccf..b81f3b90824e 100644 --- a/lib/msf/core/feature_manager.rb +++ b/lib/msf/core/feature_manager.rb @@ -20,6 +20,11 @@ class FeatureManager name: 'wrapped_tables', description: 'When enabled Metasploit will wordwrap all tables to fit into the available terminal width', default_value: false + }.freeze, + { + name: 'RHOST_HTTP_URL', + description: 'When enabled in supported modules you can specify a URL as a target', + default_value: false }.freeze ].freeze diff --git a/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_spec.rb b/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_spec.rb index 5270a1a128b6..0ac5b616724b 100644 --- a/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_spec.rb +++ b/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_spec.rb @@ -6,10 +6,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::DeploymentFileRepository do subject do - mod = ::Msf::Exploit.new - mod.extend Msf::Exploit::Remote::HTTP::JBoss - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include Msf::Exploit::Remote::HTTP::JBoss + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end let (:base_name) do From 0fdac8561c4254d415a094e6aa98b267a22b119f Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Tue, 4 Aug 2020 16:26:41 +0100 Subject: [PATCH 17/18] More test fixes --- spec/lib/metasploit/framework/aws/client_spec.rb | 7 +++++-- spec/lib/msf/core/exploit/http/jboss/base_spec.rb | 10 ++++++---- .../core/exploit/http/jboss/bean_shell_scripts_spec.rb | 10 ++++++---- .../lib/msf/core/exploit/http/jboss/bean_shell_spec.rb | 10 ++++++---- .../jboss/deployment_file_repository_scripts_spec.rb | 10 ++++++---- spec/lib/msf/core/exploit/http/joomla/base_spec.rb | 10 ++++++---- spec/lib/msf/core/exploit/http/joomla/version_spec.rb | 10 ++++++---- spec/lib/msf/core/exploit/http/wordpress/base_spec.rb | 10 ++++++---- spec/lib/msf/core/exploit/http/wordpress/login_spec.rb | 10 ++++++---- .../msf/core/exploit/http/wordpress/version_spec.rb | 10 ++++++---- 10 files changed, 59 insertions(+), 38 deletions(-) diff --git a/spec/lib/metasploit/framework/aws/client_spec.rb b/spec/lib/metasploit/framework/aws/client_spec.rb index cbc9cc0d508b..351324cee8cd 100644 --- a/spec/lib/metasploit/framework/aws/client_spec.rb +++ b/spec/lib/metasploit/framework/aws/client_spec.rb @@ -4,9 +4,12 @@ RSpec.describe Metasploit::Framework::Aws::Client do subject do - s = Class.new(Msf::Auxiliary) do + mod_klass = Class.new(Msf::Auxiliary) do include Metasploit::Framework::Aws::Client - end.new + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + s = mod_klass.new s.datastore['Region'] = 'us-east-1' s.datastore['RHOST'] = '127.0.0.1' s diff --git a/spec/lib/msf/core/exploit/http/jboss/base_spec.rb b/spec/lib/msf/core/exploit/http/jboss/base_spec.rb index 660bc8b2be7f..eaad39d044dd 100644 --- a/spec/lib/msf/core/exploit/http/jboss/base_spec.rb +++ b/spec/lib/msf/core/exploit/http/jboss/base_spec.rb @@ -6,10 +6,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::Base do subject do - mod = ::Msf::Exploit.new - mod.extend Msf::Exploit::Remote::HTTP::JBoss - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include Msf::Exploit::Remote::HTTP::JBoss + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end describe "#deploy" do diff --git a/spec/lib/msf/core/exploit/http/jboss/bean_shell_scripts_spec.rb b/spec/lib/msf/core/exploit/http/jboss/bean_shell_scripts_spec.rb index ac10a2504fbc..c1b6901f816f 100644 --- a/spec/lib/msf/core/exploit/http/jboss/bean_shell_scripts_spec.rb +++ b/spec/lib/msf/core/exploit/http/jboss/bean_shell_scripts_spec.rb @@ -6,10 +6,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::BeanShellScripts do subject do - mod = ::Msf::Exploit.new - mod.extend Msf::Exploit::Remote::HTTP::JBoss - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include Msf::Exploit::Remote::HTTP::JBoss + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end describe "#generate_bsh" do diff --git a/spec/lib/msf/core/exploit/http/jboss/bean_shell_spec.rb b/spec/lib/msf/core/exploit/http/jboss/bean_shell_spec.rb index bc633e6dda96..502761f89f4c 100644 --- a/spec/lib/msf/core/exploit/http/jboss/bean_shell_spec.rb +++ b/spec/lib/msf/core/exploit/http/jboss/bean_shell_spec.rb @@ -7,10 +7,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::BeanShell do subject do - mod = ::Msf::Exploit.new - mod.extend Msf::Exploit::Remote::HTTP::JBoss - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include Msf::Exploit::Remote::HTTP::JBoss + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end before :example do diff --git a/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_scripts_spec.rb b/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_scripts_spec.rb index dca1a5778cd3..53b8107208fd 100644 --- a/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_scripts_spec.rb +++ b/spec/lib/msf/core/exploit/http/jboss/deployment_file_repository_scripts_spec.rb @@ -6,10 +6,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::DeploymentFileRepositoryScripts do subject do - mod = ::Msf::Exploit.new - mod.extend Msf::Exploit::Remote::HTTP::JBoss - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include Msf::Exploit::Remote::HTTP::JBoss + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end describe "#stager_jsp_with_payload" do diff --git a/spec/lib/msf/core/exploit/http/joomla/base_spec.rb b/spec/lib/msf/core/exploit/http/joomla/base_spec.rb index 0edb4e107c8e..4374548f9ed7 100644 --- a/spec/lib/msf/core/exploit/http/joomla/base_spec.rb +++ b/spec/lib/msf/core/exploit/http/joomla/base_spec.rb @@ -6,10 +6,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Joomla::Base do subject do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Exploit::Remote::HTTP::Joomla - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include ::Msf::Exploit::Remote::HTTP::Joomla + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end let(:joomla_body) do diff --git a/spec/lib/msf/core/exploit/http/joomla/version_spec.rb b/spec/lib/msf/core/exploit/http/joomla/version_spec.rb index 0ad5f7d2f784..3f8bd7a9aad9 100644 --- a/spec/lib/msf/core/exploit/http/joomla/version_spec.rb +++ b/spec/lib/msf/core/exploit/http/joomla/version_spec.rb @@ -5,10 +5,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Joomla::Version do subject do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Exploit::Remote::HTTP::Joomla - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include ::Msf::Exploit::Remote::HTTP::Joomla + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end # From /joomla/language/en-GB/en-GB.xml diff --git a/spec/lib/msf/core/exploit/http/wordpress/base_spec.rb b/spec/lib/msf/core/exploit/http/wordpress/base_spec.rb index d1e622e519a7..107f92a9fb48 100644 --- a/spec/lib/msf/core/exploit/http/wordpress/base_spec.rb +++ b/spec/lib/msf/core/exploit/http/wordpress/base_spec.rb @@ -8,10 +8,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Wordpress::Base do subject do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Exploit::Remote::HTTP::Wordpress - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include ::Msf::Exploit::Remote::HTTP::Wordpress + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end describe '#wordpress_and_online?' do diff --git a/spec/lib/msf/core/exploit/http/wordpress/login_spec.rb b/spec/lib/msf/core/exploit/http/wordpress/login_spec.rb index 48c2ee63bfe5..4256ae3fe203 100644 --- a/spec/lib/msf/core/exploit/http/wordpress/login_spec.rb +++ b/spec/lib/msf/core/exploit/http/wordpress/login_spec.rb @@ -8,10 +8,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Wordpress::Login do subject do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Exploit::Remote::HTTP::Wordpress - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include ::Msf::Exploit::Remote::HTTP::Wordpress + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end describe '#wordpress_login' do diff --git a/spec/lib/msf/core/exploit/http/wordpress/version_spec.rb b/spec/lib/msf/core/exploit/http/wordpress/version_spec.rb index 328883b4985f..706ed05caa4c 100644 --- a/spec/lib/msf/core/exploit/http/wordpress/version_spec.rb +++ b/spec/lib/msf/core/exploit/http/wordpress/version_spec.rb @@ -8,10 +8,12 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Wordpress::Version do subject do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Exploit::Remote::HTTP::Wordpress - mod.send(:initialize) - mod + mod_klass = Class.new(::Msf::Exploit) do + include ::Msf::Exploit::Remote::HTTP::Wordpress + end + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end describe '#wordpress_version' do From ffbc5cb01d927633c6cab786adbff87e5c508a4c Mon Sep 17 00:00:00 2001 From: dwelch-r7 Date: Tue, 4 Aug 2020 16:32:42 +0100 Subject: [PATCH 18/18] Fixed all tests --- spec/tools/md5_lookup_spec.rb | 11 +++++++++-- spec/tools/virustotal_spec.rb | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/spec/tools/md5_lookup_spec.rb b/spec/tools/md5_lookup_spec.rb index 889038b53160..79117ad7c268 100644 --- a/spec/tools/md5_lookup_spec.rb +++ b/spec/tools/md5_lookup_spec.rb @@ -55,7 +55,10 @@ end subject do - Md5LookupUtility::Md5Lookup.new + mod_klass = Md5LookupUtility::Md5Lookup + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new end def set_expected_response(body) @@ -253,7 +256,11 @@ def stub_load(with_setting=true) describe '#get_hash_results' do context 'when a hash is found' do it 'yields a result' do - search_engine = Md5LookupUtility::Md5Lookup.new + mod_klass = Md5LookupUtility::Md5Lookup + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + search_engine = mod_klass.new + allow(search_engine).to receive(:lookup).and_return(good_result) allow(Md5LookupUtility::Md5Lookup).to receive(:new).and_return(search_engine) diff --git a/spec/tools/virustotal_spec.rb b/spec/tools/virustotal_spec.rb index 09803e540611..04d936226106 100644 --- a/spec/tools/virustotal_spec.rb +++ b/spec/tools/virustotal_spec.rb @@ -87,7 +87,11 @@ let(:vt) do file = double(File, read: malware_data) allow(File).to receive(:open).with(filename, 'rb') {|&block| block.yield file} - VirusTotalUtility::VirusTotal.new({'api_key'=>api_key, 'sample'=>filename}) + + mod_klass = VirusTotalUtility::VirusTotal + features = instance_double(Msf::FeatureManager, enabled?: false) + mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {}) + mod_klass.new({'api_key'=>api_key, 'sample'=>filename}) end context ".Initializer" do