diff --git a/config/application.rb b/config/application.rb index c48edfafac..55271a8cdc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,30 +1,7 @@ require 'rails' require File.expand_path('../boot', __FILE__) -# only the parts of 'rails/all' that metasploit-framework actually uses -begin - require 'active_record/railtie' -rescue LoadError - warn "activerecord not in the bundle, so database support will be disabled." - warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'" - warn "To clear the without option do `bundle install --without ''` " \ - "(the --without flag with an empty string) or " \ - "`rm -rf .bundle` to remove the .bundle/config manually and " \ - "then `bundle install`" -end - -all_environments = [ - :development, - :production, - :test -] - -Bundler.require( - *Rails.groups( - db: all_environments, - pcap: all_environments - ) -) +Bundler.require(*Rails.groups) # # Project diff --git a/lib/metasploit/framework/command.rb b/lib/metasploit/framework/command.rb new file mode 100644 index 0000000000..4b6449b21b --- /dev/null +++ b/lib/metasploit/framework/command.rb @@ -0,0 +1,22 @@ +# +# Gems +# + +# have to be exact so minimum is loaded prior to parsing arguments which could influence loading. +require 'active_support/dependencies/autoload' + +# @note Must use the nested declaration of the {Metasploit::Framework::Command} namespace because commands need to be +# able to be required directly without any other part of metasploit-framework besides config/boot so that the commands +# can parse arguments, setup RAILS_ENV, and load config/application.rb correctly. +module Metasploit + module Framework + module Command + # Namespace for commands for metasploit-framework. There are corresponding classes in the + # {Metasploit::Framework::ParsedOptions} namespace, which handle for parsing the options for each command. + extend ActiveSupport::Autoload + + autoload :Base + autoload :Console + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/command/base.rb b/lib/metasploit/framework/command/base.rb new file mode 100644 index 0000000000..70df3f1507 --- /dev/null +++ b/lib/metasploit/framework/command/base.rb @@ -0,0 +1,117 @@ +# +# Gems +# + +require 'active_support/core_ext/module/introspection' + +# +# Project +# + +require 'metasploit/framework/command' +require 'metasploit/framework/parsed_options' + +# Based on pattern used for lib/rails/commands in the railties gem. +class Metasploit::Framework::Command::Base + # + # Attributes + # + + # @!attribute [r] application + # The Rails application for metasploit-framework. + # + # @return [Metasploit::Framework::Application] + attr_reader :application + + # @!attribute [r] parsed_options + # The parsed options from the command line. + # + # @return (see parsed_options) + attr_reader :parsed_options + + # + # Class Methods + # + + # @note {require_environment!} should be called to load `config/application.rb` to so that the RAILS_ENV can be set + # from the command line options in `ARGV` prior to `Rails.env` being set. + # @note After returning, `Rails.application` will be defined and configured. + # + # Parses `ARGV` for command line arguments to configure the `Rails.application`. + # + # @return (see parsed_options) + def self.require_environment! + parsed_options = self.parsed_options + # RAILS_ENV must be set before requiring 'config/application.rb' + parsed_options.environment! + ARGV.replace(parsed_options.positional) + + # @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40 + require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application') + + # have to configure before requiring environment because config/environment.rb calls initialize! and the initializers + # will use the configuration from the parsed options. + parsed_options.configure(Rails.application) + + # support disabling the database + unless parsed_options.options.database.disable + begin + require 'active_record/railtie' + rescue LoadError + warn "activerecord not in the bundle, so database support will be disabled." + warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'" + warn "To clear the without option do `bundle install --without ''` " \ + "(the --without flag with an empty string) or " \ + "`rm -rf .bundle` to remove the .bundle/config manually and " \ + "then `bundle install`" + end + + ENV['RAILS_GROUP'] = 'db,pcap' + end + + Rails.application.require_environment! + + parsed_options + end + + def self.parsed_options + parsed_options_class.new + end + + def self.parsed_options_class + @parsed_options_class ||= parsed_options_class_name.constantize + end + + def self.parsed_options_class_name + @parsed_options_class_name ||= "#{parent.parent}::ParsedOptions::#{name.demodulize}" + end + + def self.start + parsed_options = require_environment! + new(application: Rails.application, parsed_options: parsed_options).start + end + + # + # Instance Methods + # + + # @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}] + # @option attributes [Rails::Application] :application + # @option attributes [ActiveSupport::OrderedOptions] :parsed_options + # @raise [KeyError] if :application is not given + # @raise [KeyError] if :parsed_options is not given + def initialize(attributes={}) + @application = attributes.fetch(:application) + @parsed_options = attributes.fetch(:parsed_options) + end + + # @abstract Use {#application} to start this command. + # + # Starts this command. + # + # @return [void] + # @raise [NotImplementedError] + def start + raise NotImplementedError + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb new file mode 100644 index 0000000000..6395352822 --- /dev/null +++ b/lib/metasploit/framework/command/console.rb @@ -0,0 +1,58 @@ +# +# Project +# + +require 'metasploit/framework/command' +require 'metasploit/framework/command/base' + +# Based on pattern used for lib/rails/commands in the railties gem. +class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base + def start + driver.run + end + + private + + # The console UI driver. + # + # @return [Msf::Ui::Console::Driver] + def driver + unless @driver + # require here so minimum loading is done before {start} is called. + require 'msf/ui' + + @driver = Msf::Ui::Console::Driver.new( + Msf::Ui::Console::Driver::DefaultPrompt, + Msf::Ui::Console::Driver::DefaultPromptChar, + driver_options + ) + end + + @driver + end + + def driver_options + unless @driver_options + options = parsed_options.options + + driver_options = {} + driver_options['Config'] = options.framework.config + driver_options['DatabaseEnv'] = options.environment + driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths + driver_options['DatabaseYAML'] = options.database.config + driver_options['Defanged'] = options.console.defanged + driver_options['DisableBanner'] = options.console.quiet + driver_options['DisableDatabase'] = options.database.disable + driver_options['LocalOutput'] = options.console.local_output + driver_options['ModulePath'] = options.modules.path + driver_options['Plugins'] = options.console.plugins + driver_options['RealReadline'] = options.console.real_readline + driver_options['Resource'] = options.console.resource + driver_options['XCommands'] = options.console.commands + + @driver_options = driver_options + end + + @driver_options + end +end diff --git a/lib/metasploit/framework/parsed_options.rb b/lib/metasploit/framework/parsed_options.rb new file mode 100644 index 0000000000..c6e8de5e02 --- /dev/null +++ b/lib/metasploit/framework/parsed_options.rb @@ -0,0 +1,24 @@ +# +# Gems +# + +require 'active_support/dependencies/autoload' + +# # @note Must use the nested declaration of the {Metasploit::Framework::ParsedOptions} namespace because commands, +# which use parsed options, need to be able to be required directly without any other part of metasploit-framework +# besides config/boot so that the commands can parse arguments, setup RAILS_ENV, and load config/application.rb +# correctly. +module Metasploit + module Framework + # Namespace for parsed options for {Metasploit::Framework::Command commands}. The names of `Class`es in this + # namespace correspond to the name of the `Class` in the {Metasploit::Framework::Command} namespace for which this + # namespace's `Class` parses options. + module ParsedOptions + extend ActiveSupport::Autoload + + autoload :Base + autoload :Console + end + end +end + diff --git a/lib/metasploit/framework/parsed_options/base.rb b/lib/metasploit/framework/parsed_options/base.rb new file mode 100644 index 0000000000..a2ed28c005 --- /dev/null +++ b/lib/metasploit/framework/parsed_options/base.rb @@ -0,0 +1,182 @@ +# +# Standard Library +# + +require 'optparse' + +# +# Gems +# + +require 'active_support/ordered_options' + +# +# Project +# + +require 'metasploit/framework/parsed_options' +require 'msf/base/config' + +# Options parsed from the command line that can be used to change the `Metasploit::Framework::Application.config` and +# `Rails.env` +class Metasploit::Framework::ParsedOptions::Base + # + # CONSTANTS + # + + # msfconsole boots in production mode instead of the normal rails default of development. + DEFAULT_ENVIRONMENT = 'production' + + # + # Attributes + # + + attr_reader :positional + + # + # Instance Methods + # + + def initialize(arguments=ARGV) + @positional = option_parser.parse(arguments) + end + + # Translates {#options} to the `application`'s config + # + # @param application [Rails::Application] + # @return [void] + def configure(application) + application.config['config/database'] = options.database.config + end + + # Sets the `RAILS_ENV` environment variable. + # + # 1. If the -E/--environment option is given, then its value is used. + # 2. The default value, 'production', is used. + # + # @return [void] + def environment! + if defined?(Rails) && Rails.instance_variable_defined?(:@_env) + raise "#{self.class}##{__method__} called too late to set RAILS_ENV: Rails.env already memoized" + end + + ENV['RAILS_ENV'] = options.environment + end + + # Options parsed from + # + # @return [ActiveSupport::OrderedOptions] + def options + unless @options + options = ActiveSupport::OrderedOptions.new + + options.database = ActiveSupport::OrderedOptions.new + + user_config_root = Pathname.new(Msf::Config.get_config_root) + user_database_yaml = user_config_root.join('database.yml') + + if user_database_yaml.exist? + options.database.config = [user_database_yaml.to_path] + else + options.database.config = ['config/database.yml'] + end + + options.database.disable = false + options.database.migrations_paths = [] + + options.framework = ActiveSupport::OrderedOptions.new + options.framework.config = nil + + options.modules = ActiveSupport::OrderedOptions.new + options.modules.path = nil + + options.environment = DEFAULT_ENVIRONMENT + + @options = options + end + + @options + end + + private + + # Parses arguments into {#options}. + # + # @return [OptionParser] + def option_parser + @option_parser ||= OptionParser.new { |option_parser| + option_parser.separator '' + option_parser.separator 'Common options' + + option_parser.on( + '-E', + '--environment ENVIRONMENT', + %w{development production test}, + "The Rails environment. Will use RAIL_ENV environment variable if that is set. " \ + "Defaults to production if neither option not RAILS_ENV environment variable is set." + ) do |environment| + options.environment = environment + end + + option_parser.separator '' + option_parser.separator 'Database options' + + option_parser.on( + '-M', + '--migration-path DIRECTORY', + 'Specify a directory containing additional DB migrations' + ) do |directory| + options.database.migrations_paths << directory + end + + option_parser.on('-n', '--no-database', 'Disable database support') do + options.database.disable = true + end + + option_parser.on( + '-y', + '--yaml PATH', + 'Specify a YAML file containing database settings' + ) do |path| + options.database.config = path + end + + option_parser.separator '' + option_parser.separator 'Framework options' + + + option_parser.on('-c', '-c FILE', 'Load the specified configuration file') do |file| + options.framework.config = file + end + + option_parser.on( + '-v', + '--version', + 'Show version' + ) do + options.subcommand = :version + end + + option_parser.separator '' + option_parser.separator 'Module options' + + option_parser.on( + '-m', + '--module-path DIRECTORY', + 'An additional module path' + ) do |directory| + options.modules.path = directory + end + + # + # Tail + # + + option_parser.separator '' + option_parser.on_tail('-h', '--help', 'Show this message') do + puts option_parser + exit + end + } + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb new file mode 100644 index 0000000000..383c5482a7 --- /dev/null +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -0,0 +1,73 @@ +# Parsed options for {Metasploit::Framework::Command::Console} +class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::ParsedOptions::Base + # Options parsed from msfconsole command-line. + # + # @return [ActiveSupport::OrderedOptions] + def options + unless @options + super.tap { |options| + options.console = ActiveSupport::OrderedOptions.new + + options.console.commands = [] + options.console.defanged = false + options.console.local_output = nil + options.console.plugins = [] + options.console.quiet = false + options.console.real_readline = false + options.console.resources = [] + } + end + + @options + end + + private + + # Parses msfconsole arguments into {#options}. + # + # @return [OptionParser] + def option_parser + unless @option_parser + super.tap { |option_parser| + option_parser.banner = "Usage: #{option_parser.program_name} [options]" + + option_parser.separator '' + option_parser.separator 'Console options:' + + option_parser.on('-d', '--defanged', 'Execute the console as defanged') do + options.console.defanged = true + end + + option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do + options.console.real_readline = true + end + + option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file| + options.console.local_output = file + end + + option_parser.on('-p', '--plugin PLUGIN', 'Load a plugin on startup') do |plugin| + options.console.plugins << plugin + end + + option_parser.on('-q', '--quiet', 'Do not print the banner on start up') do + options.console.quiet = true + end + + option_parser.on('-r', '--resource FILE', 'Execute the specified resource file') do |file| + options.console.resources << file + end + + option_parser.on( + '-x', + '--execute-command COMMAND', + 'Execute the specified string as console commands (use ; for multiples)' + ) do |commands| + options.console.commands += commands.split(/\s*;\s*/) + end + } + end + + @option_parser + end +end \ No newline at end of file diff --git a/msfconsole b/msfconsole index 4934fd86e5..9dee47926f 100755 --- a/msfconsole +++ b/msfconsole @@ -9,146 +9,18 @@ # $Revision$ # -msfbase = __FILE__ -while File.symlink?(msfbase) - msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) -end - -@msfbase_dir = File.expand_path(File.dirname(msfbase)) - -$:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' -require 'msfenv' - -$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] - -require 'optparse' - -if(RUBY_PLATFORM =~ /mswin32/) - $stderr.puts "[*] The msfconsole interface is not supported on the native Windows Ruby\n" - $stderr.puts " interpreter. Things will break, exploits will fail, payloads will not\n" - $stderr.puts " be handled correctly. Please install Cygwin or use Linux in VMWare.\n\n" -end - -class OptsConsole - # - # Return a hash describing the options. - # - def self.parse(args) - options = { - 'DeferModuleLoads' => true - } - - opts = OptionParser.new do |opts| - opts.banner = "Usage: msfconsole [options]" - - opts.separator "" - opts.separator "Specific options:" - - opts.on("-d", "-d", "Execute the console as defanged") do - options['Defanged'] = true - end - - opts.on("-r", "-r ", "Execute the specified resource file") do |r| - options['Resource'] ||= [] - options['Resource'] << r - end - - opts.on("-o", "-o ", "Output to the specified file") do |o| - options['LocalOutput'] = o - end - - opts.on("-c", "-c ", "Load the specified configuration file") do |c| - options['Config'] = c - end - - opts.on("-m", "-m ", "Specifies an additional module search path") do |m| - options['ModulePath'] = m - end - - opts.on("-p", "-p ", "Load a plugin on startup") do |p| - options['Plugins'] ||= [] - options['Plugins'] << p - end - - opts.on("-y", "--yaml ", "Specify a YAML file containing database settings") do |m| - options['DatabaseYAML'] = m - end - - opts.on("-M", "--migration-path ", "Specify a directory containing additional DB migrations") do |m| - options['DatabaseMigrationPaths'] ||= [] - options['DatabaseMigrationPaths'] << m - end - - opts.on("-e", "--environment ", "Specify the database environment to load from the YAML") do |m| - options['DatabaseEnv'] = m - end - - # Boolean switches - opts.on("-v", "--version", "Show version") do |v| - options['Version'] = true - end - - opts.on("-L", "--real-readline", "Use the system Readline library instead of RbReadline") do |v| - options['RealReadline'] = true - end - - opts.on("-n", "--no-database", "Disable database support") do |v| - options['DisableDatabase'] = true - end - - opts.on("-q", "--quiet", "Do not print the banner on start up") do |v| - options['DisableBanner'] = true - end - - opts.on("-x", "-x ", "Execute the specified string as console commands (use ; for multiples)") do |s| - options['XCommands'] ||= [] - options['XCommands'] += s.split(/\s*;\s*/) - end - - opts.separator "" - opts.separator "Common options:" - - opts.on_tail("-h", "--help", "Show this message") do - puts opts - exit - end - end - - begin - opts.parse!(args) - rescue OptionParser::InvalidOption - puts "Invalid option, try -h for usage" - exit - end - - options - end -end - -options = OptsConsole.parse(ARGV) - # -# NOTE: we don't require this until down here since we may not need it -# when processing certain options (currently only -h) -# -require 'rex' -require 'msf/ui' - -# -# Everything below this line requires the framework. +# Standard Library # -if (options['Version']) - $stderr.puts 'Framework Version: ' + Msf::Framework::Version - exit -end +require 'pathname' -begin - Msf::Ui::Console::Driver.new( - Msf::Ui::Console::Driver::DefaultPrompt, - Msf::Ui::Console::Driver::DefaultPromptChar, - options - ).run -rescue Interrupt -end +# +# Project +# + +# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/generators/rails/app/templates/script/rails#L3-L5 +require Pathname.new(__FILE__).expand_path.parent.join('config', 'boot') +require 'metasploit/framework/command/console' + +Metasploit::Framework::Command::Console.start