Browse Source

Parse msfconsole options before initializing Rails

MSP-10905
Luke Imhoff 5 years ago
parent
commit
04541ac724
No account linked to committer's email address

+ 1
- 24
config/application.rb View File

@@ -1,30 +1,7 @@
1 1
 require 'rails'
2 2
 require File.expand_path('../boot', __FILE__)
3 3
 
4
-# only the parts of 'rails/all' that metasploit-framework actually uses
5
-begin
6
-  require 'active_record/railtie'
7
-rescue LoadError
8
-  warn "activerecord not in the bundle, so database support will be disabled."
9
-  warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'"
10
-  warn "To clear the without option do `bundle install --without ''` " \
11
-       "(the --without flag with an empty string) or " \
12
-       "`rm -rf .bundle` to remove the .bundle/config manually and " \
13
-       "then `bundle install`"
14
-end
15
-
16
-all_environments = [
17
-    :development,
18
-    :production,
19
-    :test
20
-]
21
-
22
-Bundler.require(
23
-    *Rails.groups(
24
-        db: all_environments,
25
-        pcap: all_environments
26
-    )
27
-)
4
+Bundler.require(*Rails.groups)
28 5
 
29 6
 #
30 7
 # Project

+ 22
- 0
lib/metasploit/framework/command.rb View File

@@ -0,0 +1,22 @@
1
+#
2
+# Gems
3
+#
4
+
5
+# have to be exact so minimum is loaded prior to parsing arguments which could influence loading.
6
+require 'active_support/dependencies/autoload'
7
+
8
+# @note Must use the nested declaration of the {Metasploit::Framework::Command} namespace because commands need to be
9
+# able to be required directly without any other part of metasploit-framework besides config/boot so that the commands
10
+# can parse arguments, setup RAILS_ENV, and load config/application.rb correctly.
11
+module Metasploit
12
+  module Framework
13
+    module Command
14
+      # Namespace for commands for metasploit-framework.  There are corresponding classes in the
15
+      # {Metasploit::Framework::ParsedOptions} namespace, which handle for parsing the options for each command.
16
+      extend ActiveSupport::Autoload
17
+
18
+      autoload :Base
19
+      autoload :Console
20
+    end
21
+  end
22
+end

+ 117
- 0
lib/metasploit/framework/command/base.rb View File

@@ -0,0 +1,117 @@
1
+#
2
+# Gems
3
+#
4
+
5
+require 'active_support/core_ext/module/introspection'
6
+
7
+#
8
+# Project
9
+#
10
+
11
+require 'metasploit/framework/command'
12
+require 'metasploit/framework/parsed_options'
13
+
14
+# Based on pattern used for lib/rails/commands in the railties gem.
15
+class Metasploit::Framework::Command::Base
16
+  #
17
+  # Attributes
18
+  #
19
+
20
+  # @!attribute [r] application
21
+  #   The Rails application for metasploit-framework.
22
+  #
23
+  #   @return [Metasploit::Framework::Application]
24
+  attr_reader :application
25
+
26
+  # @!attribute [r] parsed_options
27
+  #   The parsed options from the command line.
28
+  #
29
+  #   @return (see parsed_options)
30
+  attr_reader :parsed_options
31
+
32
+  #
33
+  # Class Methods
34
+  #
35
+
36
+  # @note {require_environment!} should be called to load `config/application.rb` to so that the RAILS_ENV can be set
37
+  #   from the command line options in `ARGV` prior to `Rails.env` being set.
38
+  # @note After returning, `Rails.application` will be defined and configured.
39
+  #
40
+  # Parses `ARGV` for command line arguments to configure the `Rails.application`.
41
+  #
42
+  # @return (see parsed_options)
43
+  def self.require_environment!
44
+    parsed_options = self.parsed_options
45
+    # RAILS_ENV must be set before requiring 'config/application.rb'
46
+    parsed_options.environment!
47
+    ARGV.replace(parsed_options.positional)
48
+
49
+    # @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40
50
+    require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application')
51
+
52
+    # have to configure before requiring environment because config/environment.rb calls initialize! and the initializers
53
+    # will use the configuration from the parsed options.
54
+    parsed_options.configure(Rails.application)
55
+
56
+    # support disabling the database
57
+    unless parsed_options.options.database.disable
58
+      begin
59
+        require 'active_record/railtie'
60
+      rescue LoadError
61
+        warn "activerecord not in the bundle, so database support will be disabled."
62
+        warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'"
63
+        warn "To clear the without option do `bundle install --without ''` " \
64
+             "(the --without flag with an empty string) or " \
65
+             "`rm -rf .bundle` to remove the .bundle/config manually and " \
66
+             "then `bundle install`"
67
+      end
68
+
69
+      ENV['RAILS_GROUP'] = 'db,pcap'
70
+    end
71
+
72
+    Rails.application.require_environment!
73
+
74
+    parsed_options
75
+  end
76
+
77
+  def self.parsed_options
78
+    parsed_options_class.new
79
+  end
80
+
81
+  def self.parsed_options_class
82
+    @parsed_options_class ||= parsed_options_class_name.constantize
83
+  end
84
+
85
+  def self.parsed_options_class_name
86
+    @parsed_options_class_name ||= "#{parent.parent}::ParsedOptions::#{name.demodulize}"
87
+  end
88
+
89
+  def self.start
90
+    parsed_options = require_environment!
91
+    new(application: Rails.application, parsed_options: parsed_options).start
92
+  end
93
+
94
+  #
95
+  # Instance Methods
96
+  #
97
+
98
+  # @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}]
99
+  # @option attributes [Rails::Application] :application
100
+  # @option attributes [ActiveSupport::OrderedOptions] :parsed_options
101
+  # @raise [KeyError] if :application is not given
102
+  # @raise [KeyError] if :parsed_options is not given
103
+  def initialize(attributes={})
104
+    @application = attributes.fetch(:application)
105
+    @parsed_options = attributes.fetch(:parsed_options)
106
+  end
107
+
108
+  # @abstract Use {#application} to start this command.
109
+  #
110
+  # Starts this command.
111
+  #
112
+  # @return [void]
113
+  # @raise [NotImplementedError]
114
+  def start
115
+    raise NotImplementedError
116
+  end
117
+end

+ 58
- 0
lib/metasploit/framework/command/console.rb View File

@@ -0,0 +1,58 @@
1
+#
2
+# Project
3
+#
4
+
5
+require 'metasploit/framework/command'
6
+require 'metasploit/framework/command/base'
7
+
8
+# Based on pattern used for lib/rails/commands in the railties gem.
9
+class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base
10
+  def start
11
+    driver.run
12
+  end
13
+
14
+  private
15
+
16
+  # The console UI driver.
17
+  #
18
+  # @return [Msf::Ui::Console::Driver]
19
+  def driver
20
+    unless @driver
21
+      # require here so minimum loading is done before {start} is called.
22
+      require 'msf/ui'
23
+
24
+      @driver = Msf::Ui::Console::Driver.new(
25
+          Msf::Ui::Console::Driver::DefaultPrompt,
26
+          Msf::Ui::Console::Driver::DefaultPromptChar,
27
+          driver_options
28
+      )
29
+    end
30
+
31
+    @driver
32
+  end
33
+
34
+  def driver_options
35
+    unless @driver_options
36
+      options = parsed_options.options
37
+
38
+      driver_options = {}
39
+      driver_options['Config'] = options.framework.config
40
+      driver_options['DatabaseEnv'] = options.environment
41
+      driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
42
+      driver_options['DatabaseYAML'] = options.database.config
43
+      driver_options['Defanged'] = options.console.defanged
44
+      driver_options['DisableBanner'] = options.console.quiet
45
+      driver_options['DisableDatabase'] = options.database.disable
46
+      driver_options['LocalOutput'] = options.console.local_output
47
+      driver_options['ModulePath'] = options.modules.path
48
+      driver_options['Plugins'] = options.console.plugins
49
+      driver_options['RealReadline'] = options.console.real_readline
50
+      driver_options['Resource'] = options.console.resource
51
+      driver_options['XCommands'] = options.console.commands
52
+
53
+      @driver_options = driver_options
54
+    end
55
+
56
+    @driver_options
57
+  end
58
+end

+ 24
- 0
lib/metasploit/framework/parsed_options.rb View File

@@ -0,0 +1,24 @@
1
+#
2
+# Gems
3
+#
4
+
5
+require 'active_support/dependencies/autoload'
6
+
7
+# # @note Must use the nested declaration of the {Metasploit::Framework::ParsedOptions} namespace because commands,
8
+# which use parsed options, need to be able to be required directly without any other part of metasploit-framework
9
+# besides config/boot so that the commands can parse arguments, setup RAILS_ENV, and load config/application.rb
10
+# correctly.
11
+module Metasploit
12
+  module Framework
13
+    # Namespace for parsed options for {Metasploit::Framework::Command commands}.  The names of `Class`es in this
14
+    # namespace correspond to the name of the `Class` in the {Metasploit::Framework::Command} namespace for which this
15
+    # namespace's `Class` parses options.
16
+    module ParsedOptions
17
+      extend ActiveSupport::Autoload
18
+
19
+      autoload :Base
20
+      autoload :Console
21
+    end
22
+  end
23
+end
24
+

+ 182
- 0
lib/metasploit/framework/parsed_options/base.rb View File

@@ -0,0 +1,182 @@
1
+#
2
+# Standard Library
3
+#
4
+
5
+require 'optparse'
6
+
7
+#
8
+# Gems
9
+#
10
+
11
+require 'active_support/ordered_options'
12
+
13
+#
14
+# Project
15
+#
16
+
17
+require 'metasploit/framework/parsed_options'
18
+require 'msf/base/config'
19
+
20
+# Options parsed from the command line that can be used to change the `Metasploit::Framework::Application.config` and
21
+# `Rails.env`
22
+class Metasploit::Framework::ParsedOptions::Base
23
+  #
24
+  # CONSTANTS
25
+  #
26
+
27
+  # msfconsole boots in production mode instead of the normal rails default of development.
28
+  DEFAULT_ENVIRONMENT = 'production'
29
+
30
+  #
31
+  # Attributes
32
+  #
33
+
34
+  attr_reader :positional
35
+
36
+  #
37
+  # Instance Methods
38
+  #
39
+
40
+  def initialize(arguments=ARGV)
41
+    @positional = option_parser.parse(arguments)
42
+  end
43
+
44
+  # Translates {#options} to the `application`'s config
45
+  #
46
+  # @param application [Rails::Application]
47
+  # @return [void]
48
+  def configure(application)
49
+    application.config['config/database'] = options.database.config
50
+  end
51
+
52
+  # Sets the `RAILS_ENV` environment variable.
53
+  #
54
+  # 1. If the -E/--environment option is given, then its value is used.
55
+  # 2. The default value, 'production', is used.
56
+  #
57
+  # @return [void]
58
+  def environment!
59
+    if defined?(Rails) && Rails.instance_variable_defined?(:@_env)
60
+      raise "#{self.class}##{__method__} called too late to set RAILS_ENV: Rails.env already memoized"
61
+    end
62
+
63
+    ENV['RAILS_ENV'] = options.environment
64
+  end
65
+
66
+  # Options parsed from
67
+  #
68
+  # @return [ActiveSupport::OrderedOptions]
69
+  def options
70
+    unless @options
71
+      options = ActiveSupport::OrderedOptions.new
72
+
73
+      options.database = ActiveSupport::OrderedOptions.new
74
+
75
+      user_config_root = Pathname.new(Msf::Config.get_config_root)
76
+      user_database_yaml = user_config_root.join('database.yml')
77
+
78
+      if user_database_yaml.exist?
79
+        options.database.config = [user_database_yaml.to_path]
80
+      else
81
+        options.database.config = ['config/database.yml']
82
+      end
83
+
84
+      options.database.disable = false
85
+      options.database.migrations_paths = []
86
+
87
+      options.framework = ActiveSupport::OrderedOptions.new
88
+      options.framework.config = nil
89
+
90
+      options.modules = ActiveSupport::OrderedOptions.new
91
+      options.modules.path = nil
92
+
93
+      options.environment = DEFAULT_ENVIRONMENT
94
+
95
+      @options = options
96
+    end
97
+
98
+    @options
99
+  end
100
+
101
+  private
102
+
103
+  # Parses arguments into {#options}.
104
+  #
105
+  # @return [OptionParser]
106
+  def option_parser
107
+    @option_parser ||= OptionParser.new { |option_parser|
108
+      option_parser.separator ''
109
+      option_parser.separator 'Common options'
110
+
111
+      option_parser.on(
112
+          '-E',
113
+          '--environment ENVIRONMENT',
114
+          %w{development production test},
115
+          "The Rails environment. Will use RAIL_ENV environment variable if that is set.  " \
116
+          "Defaults to production if neither option not RAILS_ENV environment variable is set."
117
+      ) do |environment|
118
+        options.environment = environment
119
+      end
120
+
121
+      option_parser.separator ''
122
+      option_parser.separator 'Database options'
123
+
124
+      option_parser.on(
125
+          '-M',
126
+          '--migration-path DIRECTORY',
127
+          'Specify a directory containing additional DB migrations'
128
+      ) do |directory|
129
+        options.database.migrations_paths << directory
130
+      end
131
+
132
+      option_parser.on('-n', '--no-database', 'Disable database support') do
133
+        options.database.disable = true
134
+      end
135
+
136
+      option_parser.on(
137
+          '-y',
138
+          '--yaml PATH',
139
+          'Specify a YAML file containing database settings'
140
+      ) do |path|
141
+        options.database.config = path
142
+      end
143
+
144
+      option_parser.separator ''
145
+      option_parser.separator 'Framework options'
146
+
147
+
148
+      option_parser.on('-c', '-c FILE', 'Load the specified configuration file') do |file|
149
+        options.framework.config = file
150
+      end
151
+
152
+      option_parser.on(
153
+          '-v',
154
+          '--version',
155
+          'Show version'
156
+      ) do
157
+        options.subcommand = :version
158
+      end
159
+
160
+      option_parser.separator ''
161
+      option_parser.separator 'Module options'
162
+
163
+      option_parser.on(
164
+          '-m',
165
+          '--module-path DIRECTORY',
166
+          'An additional module path'
167
+      ) do |directory|
168
+        options.modules.path = directory
169
+      end
170
+
171
+      #
172
+      # Tail
173
+      #
174
+
175
+      option_parser.separator ''
176
+      option_parser.on_tail('-h', '--help', 'Show this message') do
177
+        puts option_parser
178
+        exit
179
+      end
180
+    }
181
+  end
182
+end

+ 73
- 0
lib/metasploit/framework/parsed_options/console.rb View File

@@ -0,0 +1,73 @@
1
+# Parsed options for {Metasploit::Framework::Command::Console}
2
+class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::ParsedOptions::Base
3
+  # Options parsed from msfconsole command-line.
4
+  #
5
+  # @return [ActiveSupport::OrderedOptions]
6
+  def options
7
+    unless @options
8
+      super.tap { |options|
9
+        options.console = ActiveSupport::OrderedOptions.new
10
+
11
+        options.console.commands = []
12
+        options.console.defanged = false
13
+        options.console.local_output = nil
14
+        options.console.plugins = []
15
+        options.console.quiet = false
16
+        options.console.real_readline = false
17
+        options.console.resources = []
18
+      }
19
+    end
20
+
21
+    @options
22
+  end
23
+
24
+  private
25
+
26
+  # Parses msfconsole arguments into {#options}.
27
+  #
28
+  # @return [OptionParser]
29
+  def option_parser
30
+    unless @option_parser
31
+      super.tap { |option_parser|
32
+        option_parser.banner = "Usage: #{option_parser.program_name} [options]"
33
+
34
+        option_parser.separator ''
35
+        option_parser.separator 'Console options:'
36
+
37
+        option_parser.on('-d', '--defanged', 'Execute the console as defanged') do
38
+          options.console.defanged = true
39
+        end
40
+
41
+        option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do
42
+          options.console.real_readline = true
43
+        end
44
+
45
+        option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file|
46
+          options.console.local_output = file
47
+        end
48
+
49
+        option_parser.on('-p', '--plugin PLUGIN', 'Load a plugin on startup') do |plugin|
50
+          options.console.plugins << plugin
51
+        end
52
+
53
+        option_parser.on('-q', '--quiet', 'Do not print the banner on start up') do
54
+          options.console.quiet = true
55
+        end
56
+
57
+        option_parser.on('-r', '--resource FILE', 'Execute the specified resource file') do |file|
58
+          options.console.resources << file
59
+        end
60
+
61
+        option_parser.on(
62
+            '-x',
63
+            '--execute-command COMMAND',
64
+            'Execute the specified string as console commands (use ; for multiples)'
65
+        ) do |commands|
66
+          options.console.commands += commands.split(/\s*;\s*/)
67
+        end
68
+      }
69
+    end
70
+
71
+    @option_parser
72
+  end
73
+end

+ 8
- 136
msfconsole View File

@@ -9,146 +9,18 @@
9 9
 # $Revision$
10 10
 #
11 11
 
12
-msfbase = __FILE__
13
-while File.symlink?(msfbase)
14
-  msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
15
-end
16
-
17
-@msfbase_dir = File.expand_path(File.dirname(msfbase))
18
-
19
-$:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib')))
20
-require 'fastlib'
21
-require 'msfenv'
22
-
23
-$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
24
-
25
-require 'optparse'
26
-
27
-if(RUBY_PLATFORM =~ /mswin32/)
28
-  $stderr.puts "[*] The msfconsole interface is not supported on the native Windows Ruby\n"
29
-  $stderr.puts "    interpreter. Things will break, exploits will fail, payloads will not\n"
30
-  $stderr.puts "    be handled correctly. Please install Cygwin or use Linux in VMWare.\n\n"
31
-end
32
-
33
-class OptsConsole
34
-  #
35
-  # Return a hash describing the options.
36
-  #
37
-  def self.parse(args)
38
-    options = {
39
-      'DeferModuleLoads' => true
40
-    }
41
-
42
-    opts = OptionParser.new do |opts|
43
-      opts.banner = "Usage: msfconsole [options]"
44
-
45
-      opts.separator ""
46
-      opts.separator "Specific options:"
47
-
48
-      opts.on("-d", "-d", "Execute the console as defanged") do
49
-        options['Defanged'] = true
50
-      end
51
-
52
-      opts.on("-r", "-r <filename>", "Execute the specified resource file") do |r|
53
-        options['Resource'] ||= []
54
-        options['Resource'] << r
55
-      end
56
-
57
-      opts.on("-o", "-o <filename>", "Output to the specified file") do |o|
58
-        options['LocalOutput'] = o
59
-      end
60
-
61
-      opts.on("-c", "-c <filename>", "Load the specified configuration file") do |c|
62
-        options['Config'] = c
63
-      end
64
-
65
-      opts.on("-m", "-m <directory>", "Specifies an additional module search path") do |m|
66
-        options['ModulePath'] = m
67
-      end
68
-
69
-      opts.on("-p", "-p <plugin>", "Load a plugin on startup") do |p|
70
-        options['Plugins'] ||= []
71
-        options['Plugins'] << p
72
-      end
73
-
74
-      opts.on("-y", "--yaml <database.yml>", "Specify a YAML file containing database settings") do |m|
75
-        options['DatabaseYAML'] = m
76
-      end
77
-
78
-      opts.on("-M", "--migration-path <dir>", "Specify a directory containing additional DB migrations") do |m|
79
-        options['DatabaseMigrationPaths'] ||= []
80
-        options['DatabaseMigrationPaths'] << m
81
-      end
82
-
83
-      opts.on("-e", "--environment <production|development>", "Specify the database environment to load from the YAML") do |m|
84
-        options['DatabaseEnv'] = m
85
-      end
86
-
87
-      # Boolean switches
88
-      opts.on("-v", "--version", "Show version") do |v|
89
-        options['Version'] = true
90
-      end
91
-
92
-      opts.on("-L", "--real-readline", "Use the system Readline library instead of RbReadline") do |v|
93
-        options['RealReadline'] = true
94
-      end
95
-
96
-      opts.on("-n", "--no-database", "Disable database support") do |v|
97
-        options['DisableDatabase'] = true
98
-      end
99
-
100
-      opts.on("-q", "--quiet", "Do not print the banner on start up") do |v|
101
-        options['DisableBanner'] = true
102
-      end
103
-
104
-      opts.on("-x", "-x <command>", "Execute the specified string as console commands (use ; for multiples)") do |s|
105
-        options['XCommands'] ||= []
106
-        options['XCommands'] += s.split(/\s*;\s*/)
107
-      end
108
-
109
-      opts.separator ""
110
-      opts.separator "Common options:"
111
-
112
-      opts.on_tail("-h", "--help", "Show this message") do
113
-        puts opts
114
-        exit
115
-      end
116
-    end
117
-
118
-    begin
119
-      opts.parse!(args)
120
-    rescue OptionParser::InvalidOption
121
-      puts "Invalid option, try -h for usage"
122
-      exit
123
-    end
124
-
125
-    options
126
-  end
127
-end
128
-
129
-options = OptsConsole.parse(ARGV)
130
-
131 12
 #
132
-# NOTE: we don't require this until down here since we may not need it
133
-# when processing certain options (currently only -h)
13
+# Standard Library
134 14
 #
135
-require 'rex'
136
-require 'msf/ui'
15
+
16
+require 'pathname'
137 17
 
138 18
 #
139
-# Everything below this line requires the framework.
19
+# Project
140 20
 #
141 21
 
142
-if (options['Version'])
143
-  $stderr.puts 'Framework Version: ' + Msf::Framework::Version
144
-  exit
145
-end
22
+# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/generators/rails/app/templates/script/rails#L3-L5
23
+require Pathname.new(__FILE__).expand_path.parent.join('config', 'boot')
24
+require 'metasploit/framework/command/console'
146 25
 
147
-begin
148
-  Msf::Ui::Console::Driver.new(
149
-    Msf::Ui::Console::Driver::DefaultPrompt,
150
-    Msf::Ui::Console::Driver::DefaultPromptChar,
151
-    options
152
-  ).run
153
-rescue Interrupt
154
-end
26
+Metasploit::Framework::Command::Console.start

Loading…
Cancel
Save