Parse msfconsole options before initializing Rails

MSP-10905
bug/bundler_fix
Luke Imhoff 2014-07-29 14:07:14 -05:00
parent 8e7dd1b658
commit 04541ac724
No known key found for this signature in database
GPG Key ID: 5B1FB01FB33356F8
8 changed files with 488 additions and 163 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <filename>", "Execute the specified resource file") do |r|
options['Resource'] ||= []
options['Resource'] << r
end
opts.on("-o", "-o <filename>", "Output to the specified file") do |o|
options['LocalOutput'] = o
end
opts.on("-c", "-c <filename>", "Load the specified configuration file") do |c|
options['Config'] = c
end
opts.on("-m", "-m <directory>", "Specifies an additional module search path") do |m|
options['ModulePath'] = m
end
opts.on("-p", "-p <plugin>", "Load a plugin on startup") do |p|
options['Plugins'] ||= []
options['Plugins'] << p
end
opts.on("-y", "--yaml <database.yml>", "Specify a YAML file containing database settings") do |m|
options['DatabaseYAML'] = m
end
opts.on("-M", "--migration-path <dir>", "Specify a directory containing additional DB migrations") do |m|
options['DatabaseMigrationPaths'] ||= []
options['DatabaseMigrationPaths'] << m
end
opts.on("-e", "--environment <production|development>", "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 <command>", "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