CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/ui/console/driver.rb
Views: 11783
1
# -*- coding: binary -*-
2
require 'find'
3
require 'erb'
4
require 'rexml/document'
5
require 'fileutils'
6
require 'digest/md5'
7
8
module Msf
9
module Ui
10
module Console
11
12
#
13
# A user interface driver on a console interface.
14
#
15
class Driver < Msf::Ui::Driver
16
17
ConfigCore = "framework/core"
18
ConfigGroup = "framework/ui/console"
19
20
DefaultPrompt = "%undmsf#{Metasploit::Framework::Version::MAJOR}%clr"
21
DefaultPromptChar = "%clr>"
22
23
#
24
# Console Command Dispatchers to be loaded after the Core dispatcher.
25
#
26
CommandDispatchers = [
27
CommandDispatcher::Modules,
28
CommandDispatcher::Jobs,
29
CommandDispatcher::Resource,
30
CommandDispatcher::Db,
31
CommandDispatcher::Creds,
32
CommandDispatcher::Developer,
33
CommandDispatcher::DNS
34
]
35
36
#
37
# The console driver processes various framework notified events.
38
#
39
include FrameworkEventManager
40
41
#
42
# The console driver is a command shell.
43
#
44
include Rex::Ui::Text::DispatcherShell
45
46
include Rex::Ui::Text::Resource
47
48
#
49
# Initializes a console driver instance with the supplied prompt string and
50
# prompt character. The optional hash can take extra values that will
51
# serve to initialize the console driver.
52
#
53
# @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow
54
# unrecognized commands to be executed by the system shell
55
# @option opts [Boolean] 'Readline' (true) Whether to use the readline or not
56
# @option opts [Boolean] 'RealReadline' (false) Whether to use the system's
57
# readline library instead of RBReadline
58
# @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file
59
# where we can store command history
60
# @option opts [Array<String>] 'Resources' ([]) A list of resource files to
61
# load. If no resources are given, will load the default resource script,
62
# 'msfconsole.rc' in the user's {Msf::Config.config_directory config
63
# directory}
64
# @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip
65
# connecting to the database and running migrations
66
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
67
choose_readline(opts)
68
69
histfile = opts['HistFile'] || Msf::Config.history_file
70
71
begin
72
FeatureManager.instance.load_config
73
rescue StandardError => e
74
elog(e)
75
end
76
77
if opts['DeferModuleLoads'].nil?
78
opts['DeferModuleLoads'] = Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DEFER_MODULE_LOADS)
79
end
80
81
# Initialize attributes
82
83
framework_create_options = opts.merge({ 'DeferModuleLoads' => true })
84
85
if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DNS)
86
dns_resolver = Rex::Proto::DNS::CachedResolver.new
87
dns_resolver.extend(Rex::Proto::DNS::CustomNameserverProvider)
88
dns_resolver.load_config if dns_resolver.has_config?
89
90
# Defer loading of modules until paths from opts can be added below
91
framework_create_options = framework_create_options.merge({ 'CustomDnsResolver' => dns_resolver })
92
end
93
self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options)
94
95
if self.framework.datastore['Prompt']
96
prompt = self.framework.datastore['Prompt']
97
prompt_char = self.framework.datastore['PromptChar'] || DefaultPromptChar
98
end
99
100
# Call the parent
101
super(prompt, prompt_char, histfile, framework, :msfconsole)
102
103
# Temporarily disable output
104
self.disable_output = true
105
106
# Load pre-configuration
107
load_preconfig
108
109
# Initialize the user interface to use a different input and output
110
# handle if one is supplied
111
input = opts['LocalInput']
112
input ||= Rex::Ui::Text::Input::Stdio.new
113
114
if !opts['Readline']
115
input.disable_readline
116
end
117
118
if (opts['LocalOutput'])
119
if (opts['LocalOutput'].kind_of?(String))
120
output = Rex::Ui::Text::Output::File.new(opts['LocalOutput'])
121
else
122
output = opts['LocalOutput']
123
end
124
else
125
output = Rex::Ui::Text::Output::Stdio.new
126
end
127
128
init_ui(input, output)
129
init_tab_complete
130
131
# Add the core command dispatcher as the root of the dispatcher
132
# stack
133
enstack_dispatcher(CommandDispatcher::Core)
134
135
# Report readline error if there was one..
136
if !@rl_err.nil?
137
print_error("***")
138
print_error("* Unable to load readline: #{@rl_err}")
139
print_error("* Falling back to RbReadLine")
140
print_error("***")
141
end
142
143
# Load the other "core" command dispatchers
144
CommandDispatchers.each do |dispatcher_class|
145
dispatcher = enstack_dispatcher(dispatcher_class)
146
dispatcher.load_config(opts['Config'])
147
end
148
149
if !framework.db || !framework.db.active
150
if framework.db.error == "disabled"
151
print_warning("Database support has been disabled")
152
else
153
error_msg = "#{framework.db.error.class.is_a?(String) ? "#{framework.db.error.class} " : nil}#{framework.db.error}"
154
print_warning("No database support: #{error_msg}")
155
end
156
end
157
158
# Register event handlers
159
register_event_handlers
160
161
# Re-enable output
162
self.disable_output = false
163
164
# Whether or not command passthru should be allowed
165
self.command_passthru = opts.fetch('AllowCommandPassthru', true)
166
167
# Whether or not to confirm before exiting
168
self.confirm_exit = opts['ConfirmExit']
169
170
# Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false
171
unless opts['Framework']
172
# Configure the framework module paths
173
self.framework.init_module_paths(module_paths: opts['ModulePath'], defer_module_loads: opts['DeferModuleLoads'])
174
end
175
176
unless opts['DeferModuleLoads']
177
framework.threads.spawn("ModuleCacheRebuild", true) do
178
framework.modules.refresh_cache_from_module_files
179
end
180
end
181
182
# Load console-specific configuration (after module paths are added)
183
load_config(opts['Config'])
184
185
# Process things before we actually display the prompt and get rocking
186
on_startup(opts)
187
188
# Process any resource scripts
189
if opts['Resource'].blank?
190
# None given, load the default
191
default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc')
192
load_resource(default_resource) if ::File.exist?(default_resource)
193
else
194
opts['Resource'].each { |r|
195
load_resource(r)
196
}
197
end
198
199
# Process persistent job handler
200
begin
201
restore_handlers = JSON.parse(File.read(Msf::Config.persist_file))
202
rescue Errno::ENOENT, JSON::ParserError
203
restore_handlers = nil
204
end
205
206
if restore_handlers
207
print_status("Starting persistent handler(s)...")
208
209
restore_handlers.each.with_index do |handler_opts, index|
210
handler = framework.modules.create(handler_opts['mod_name'])
211
handler.init_ui(self.input, self.output)
212
replicant_handler = nil
213
handler.exploit_simple(handler_opts['mod_options']) do |yielded_replicant_handler|
214
replicant_handler = yielded_replicant_handler
215
end
216
217
if replicant_handler.nil? || replicant_handler.error
218
print_status("Failed to start persistent payload handler ##{index} (#{handler_opts['mod_name']})")
219
next
220
end
221
222
if replicant_handler.error.nil?
223
job_id = handler.job_id
224
print_status "Persistent payload handler started as Job #{job_id}"
225
end
226
end
227
end
228
229
# Process any additional startup commands
230
if opts['XCommands'] and opts['XCommands'].kind_of? Array
231
opts['XCommands'].each { |c|
232
run_single(c)
233
}
234
end
235
end
236
237
#
238
# Loads configuration that needs to be analyzed before the framework
239
# instance is created.
240
#
241
def load_preconfig
242
begin
243
conf = Msf::Config.load
244
rescue
245
wlog("Failed to load configuration: #{$!}")
246
return
247
end
248
249
if (conf.group?(ConfigCore))
250
conf[ConfigCore].each_pair { |k, v|
251
on_variable_set(true, k, v)
252
}
253
end
254
end
255
256
#
257
# Loads configuration for the console.
258
#
259
def load_config(path=nil)
260
begin
261
conf = Msf::Config.load(path)
262
rescue
263
wlog("Failed to load configuration: #{$!}")
264
return
265
end
266
267
# If we have configuration, process it
268
if (conf.group?(ConfigGroup))
269
conf[ConfigGroup].each_pair { |k, v|
270
case k.downcase
271
when 'activemodule'
272
run_single("use #{v}")
273
when 'activeworkspace'
274
if framework.db.active
275
workspace = framework.db.find_workspace(v)
276
framework.db.workspace = workspace if workspace
277
end
278
end
279
}
280
end
281
end
282
283
#
284
# Generate configuration for the console.
285
#
286
def get_config
287
# Build out the console config group
288
group = {}
289
290
if (active_module)
291
group['ActiveModule'] = active_module.fullname
292
end
293
294
if framework.db.active
295
unless framework.db.workspace.default?
296
group['ActiveWorkspace'] = framework.db.workspace.name
297
end
298
end
299
300
group
301
end
302
303
def get_config_core
304
ConfigCore
305
end
306
307
def get_config_group
308
ConfigGroup
309
end
310
311
#
312
# Saves configuration for the console.
313
#
314
def save_config
315
begin
316
Msf::Config.save(ConfigGroup => get_config)
317
rescue ::Exception
318
print_error("Failed to save console config: #{$!}")
319
end
320
end
321
322
#
323
# Saves the recent history to the specified file
324
#
325
def save_recent_history(path)
326
num = Readline::HISTORY.length - hist_last_saved - 1
327
328
tmprc = ""
329
num.times { |x|
330
tmprc << Readline::HISTORY[hist_last_saved + x] + "\n"
331
}
332
333
if tmprc.length > 0
334
print_status("Saving last #{num} commands to #{path} ...")
335
save_resource(tmprc, path)
336
else
337
print_error("No commands to save!")
338
end
339
340
# Always update this, even if we didn't save anything. We do this
341
# so that we don't end up saving the "makerc" command itself.
342
self.hist_last_saved = Readline::HISTORY.length
343
end
344
345
#
346
# Creates the resource script file for the console.
347
#
348
def save_resource(data, path=nil)
349
path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc')
350
351
begin
352
rcfd = File.open(path, 'w')
353
rcfd.write(data)
354
rcfd.close
355
rescue ::Exception
356
end
357
end
358
359
#
360
# Called before things actually get rolling such that banners can be
361
# displayed, scripts can be processed, and other fun can be had.
362
#
363
def on_startup(opts = {})
364
# Check for modules that failed to load
365
if framework.modules.module_load_error_by_path.length > 0
366
wlog("The following modules could not be loaded!")
367
368
framework.modules.module_load_error_by_path.each do |path, _error|
369
wlog("\t#{path}")
370
end
371
end
372
373
if framework.modules.module_load_warnings.length > 0
374
print_warning("The following modules were loaded with warnings:")
375
376
framework.modules.module_load_warnings.each do |path, _error|
377
wlog("\t#{path}")
378
end
379
end
380
381
if framework.db&.active
382
framework.db.workspace = framework.db.default_workspace unless framework.db.workspace
383
end
384
385
framework.events.on_ui_start(Msf::Framework::Revision)
386
387
if $msf_spinner_thread
388
$msf_spinner_thread.kill
389
$stderr.print "\r" + (" " * 50) + "\n"
390
end
391
392
run_single("banner") unless opts['DisableBanner']
393
394
payloads_manifest_errors = []
395
begin
396
payloads_manifest_errors = ::MetasploitPayloads.manifest_errors if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)
397
rescue ::StandardError => e
398
$stderr.print('Could not verify the integrity of the Metasploit Payloads manifest')
399
elog(e)
400
end
401
402
av_warning_message if (framework.eicar_corrupted? || payloads_manifest_errors.any?)
403
404
if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)
405
if payloads_manifest_errors.any?
406
warn_msg = "Metasploit Payloads manifest errors:\n"
407
payloads_manifest_errors.each do |file|
408
warn_msg << "\t#{file[:path]} : #{file[:error]}\n"
409
end
410
$stderr.print(warn_msg)
411
end
412
end
413
414
opts["Plugins"].each do |plug|
415
run_single("load '#{plug}'")
416
end if opts["Plugins"]
417
418
self.on_command_proc = Proc.new { |command| framework.events.on_ui_command(command) }
419
end
420
421
def av_warning_message
422
avdwarn = "\e[31m"\
423
"Warning: This copy of the Metasploit Framework has been corrupted by an installed anti-virus program."\
424
" We recommend that you disable your anti-virus or exclude your Metasploit installation path, "\
425
"then restore the removed files from quarantine or reinstall the framework.\e[0m"\
426
"\n\n"
427
428
$stderr.puts(Rex::Text.wordwrap(avdwarn, 0, 80))
429
end
430
431
#
432
# Called when a variable is set to a specific value. This allows the
433
# console to do extra processing, such as enabling logging or doing
434
# some other kind of task. If this routine returns false it will indicate
435
# that the variable is not being set to a valid value.
436
#
437
def on_variable_set(glob, var, val)
438
case var.downcase
439
when 'sessionlogging'
440
handle_session_logging(val) if glob
441
when 'sessiontlvlogging'
442
handle_session_tlv_logging(val) if glob
443
when 'consolelogging'
444
handle_console_logging(val) if glob
445
when 'loglevel'
446
handle_loglevel(val) if glob
447
when 'payload'
448
handle_payload(val)
449
when 'ssh_ident'
450
handle_ssh_ident(val)
451
end
452
end
453
454
#
455
# Called when a variable is unset. If this routine returns false it is an
456
# indication that the variable should not be allowed to be unset.
457
#
458
def on_variable_unset(glob, var)
459
case var.downcase
460
when 'sessionlogging'
461
handle_session_logging('0') if glob
462
when 'sessiontlvlogging'
463
handle_session_tlv_logging('false') if glob
464
when 'consolelogging'
465
handle_console_logging('0') if glob
466
when 'loglevel'
467
handle_loglevel(nil) if glob
468
end
469
end
470
471
#
472
# Proxies to shell.rb's update prompt with our own extras
473
#
474
def update_prompt(*args)
475
if args.empty?
476
pchar = framework.datastore['PromptChar'] || DefaultPromptChar
477
p = framework.datastore['Prompt'] || DefaultPrompt
478
p = "#{p} #{active_module.type}(%bld%red#{active_module.promptname}%clr)" if active_module
479
super(p, pchar)
480
else
481
# Don't squash calls from within lib/rex/ui/text/shell.rb
482
super(*args)
483
end
484
end
485
486
#
487
# The framework instance associated with this driver.
488
#
489
attr_reader :framework
490
#
491
# Whether or not to confirm before exiting
492
#
493
attr_reader :confirm_exit
494
#
495
# Whether or not commands can be passed through.
496
#
497
attr_reader :command_passthru
498
#
499
# The active module associated with the driver.
500
#
501
attr_accessor :active_module
502
#
503
# The active session associated with the driver.
504
#
505
attr_accessor :active_session
506
507
def stop
508
framework.events.on_ui_stop()
509
super
510
end
511
512
protected
513
514
attr_writer :framework # :nodoc:
515
attr_writer :confirm_exit # :nodoc:
516
attr_writer :command_passthru # :nodoc:
517
518
#
519
# If an unknown command was passed, try to see if it's a valid local
520
# executable. This is only allowed if command passthru has been permitted
521
#
522
def unknown_command(method, line)
523
if File.basename(method) == 'msfconsole'
524
print_error('msfconsole cannot be run inside msfconsole')
525
return
526
end
527
528
[method, method+".exe"].each do |cmd|
529
if command_passthru && Rex::FileUtils.find_full_path(cmd)
530
531
self.busy = true
532
begin
533
run_unknown_command(line)
534
rescue ::Errno::EACCES, ::Errno::ENOENT
535
print_error("Permission denied exec: #{line}")
536
end
537
self.busy = false
538
return
539
end
540
end
541
542
if framework.modules.create(method)
543
super
544
if prompt_yesno "This is a module we can load. Do you want to use #{method}?"
545
run_single "use #{method}"
546
end
547
548
return
549
end
550
551
super
552
end
553
554
def run_unknown_command(command)
555
print_status("exec: #{command}")
556
print_line('')
557
system(command)
558
end
559
560
##
561
#
562
# Handlers for various global configuration values
563
#
564
##
565
566
#
567
# SessionLogging.
568
#
569
def handle_session_logging(val)
570
if (val =~ /^(y|t|1)/i)
571
Msf::Logging.enable_session_logging(true)
572
framework.sessions.values.each do |session|
573
Msf::Logging.start_session_log(session)
574
end
575
print_line("Session logging enabled.")
576
else
577
Msf::Logging.enable_session_logging(false)
578
framework.sessions.values.each do |session|
579
Msf::Logging.stop_session_log(session)
580
end
581
print_line("Session logging disabled.")
582
end
583
end
584
585
#
586
# ConsoleLogging.
587
#
588
def handle_console_logging(val)
589
if (val =~ /^(y|t|1)/i)
590
Msf::Logging.enable_log_source('console')
591
print_line("Console logging is now enabled.")
592
593
set_log_source('console')
594
595
rlog("\n[*] Console logging started: #{Time.now}\n\n", 'console')
596
else
597
rlog("\n[*] Console logging stopped: #{Time.now}\n\n", 'console')
598
599
unset_log_source
600
601
Msf::Logging.disable_log_source('console')
602
print_line("Console logging is now disabled.")
603
end
604
end
605
606
#
607
# This method handles adjusting the global log level threshold.
608
#
609
def handle_loglevel(val)
610
set_log_level(Rex::LogSource, val)
611
set_log_level(Msf::LogSource, val)
612
end
613
614
#
615
# This method handles setting a desired payload
616
#
617
# TODO: Move this out of the console driver!
618
#
619
def handle_payload(val)
620
if framework && !framework.payloads.valid?(val)
621
return false
622
elsif active_module && (active_module.exploit? || active_module.evasion?)
623
return false unless active_module.is_payload_compatible?(val)
624
elsif active_module && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
625
active_module.datastore.clear_non_user_defined
626
elsif framework && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
627
framework.datastore.clear_non_user_defined
628
end
629
end
630
631
#
632
# This method monkeypatches Net::SSH's client identification string
633
#
634
# TODO: Move this out of the console driver!
635
#
636
def handle_ssh_ident(val)
637
# HACK: Suppress already initialized constant warning
638
verbose, $VERBOSE = $VERBOSE, nil
639
640
return false unless val.is_a?(String) && !val.empty?
641
642
require 'net/ssh'
643
644
# HACK: Bypass dynamic constant assignment error
645
::Net::SSH::Transport::ServerVersion.const_set(:PROTO_VERSION, val)
646
647
true
648
rescue LoadError
649
print_error('Net::SSH could not be loaded')
650
false
651
rescue NameError
652
print_error('Invalid constant Net::SSH::Transport::ServerVersion::PROTO_VERSION')
653
false
654
ensure
655
# Restore warning
656
$VERBOSE = verbose
657
end
658
659
def handle_session_tlv_logging(val)
660
return false if val.nil?
661
662
if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false')
663
return true
664
elsif val.start_with?('file:') && !val.split('file:').empty?
665
pathname = ::Pathname.new(val.split('file:').last)
666
667
# Check if we want to write the log to file
668
if ::File.file?(pathname)
669
if ::File.writable?(pathname)
670
return true
671
else
672
print_status "No write permissions for log output file: #{pathname}"
673
return false
674
end
675
# Check if we want to write the log file to a directory
676
elsif ::File.directory?(pathname)
677
if ::File.writable?(pathname)
678
return true
679
else
680
print_status "No write permissions for log output directory: #{pathname}"
681
return false
682
end
683
# Check if the subdirectory exists
684
elsif ::File.directory?(pathname.dirname)
685
if ::File.writable?(pathname.dirname)
686
return true
687
else
688
print_status "No write permissions for log output directory: #{pathname.dirname}"
689
return false
690
end
691
else
692
# Else the directory doesn't exist. Check if we can create it.
693
begin
694
::FileUtils.mkdir_p(pathname.dirname)
695
return true
696
rescue ::StandardError => e
697
print_status "Error when trying to create directory #{pathname.dirname}: #{e.message}"
698
return false
699
end
700
end
701
end
702
703
false
704
end
705
706
# Require the appropriate readline library based on the user's preference.
707
#
708
# @return [void]
709
def choose_readline(opts)
710
# Choose a readline library before calling the parent
711
@rl_err = nil
712
if opts['RealReadline']
713
# Remove the gem version from load path to be sure we're getting the
714
# stdlib readline.
715
gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir
716
rb_readline_path = File.join(gem_dir, "lib")
717
index = $LOAD_PATH.index(rb_readline_path)
718
# Bundler guarantees that the gem will be there, so it should be safe to
719
# assume we found it in the load path, but check to be on the safe side.
720
if index
721
$LOAD_PATH.delete_at(index)
722
end
723
end
724
725
begin
726
require 'readline'
727
728
# Only Windows requires a monkey-patched RbReadline
729
return unless Rex::Compat.is_windows
730
731
if defined?(::RbReadline) && !defined?(RbReadline.refresh_console_handle)
732
::RbReadline.instance_eval do
733
class << self
734
alias_method :old_rl_move_cursor_relative, :_rl_move_cursor_relative
735
alias_method :old_rl_get_screen_size, :_rl_get_screen_size
736
alias_method :old_space_to_eol, :space_to_eol
737
alias_method :old_insert_some_chars, :insert_some_chars
738
end
739
740
def self.refresh_console_handle
741
# hConsoleHandle gets set only when RbReadline detects it is running on Windows.
742
# Therefore, we don't need to check Rex::Compat.is_windows, we can simply check if hConsoleHandle is nil or not.
743
@hConsoleHandle = @GetStdHandle.Call(::Readline::STD_OUTPUT_HANDLE) if @hConsoleHandle
744
end
745
746
def self._rl_move_cursor_relative(*args)
747
refresh_console_handle
748
old_rl_move_cursor_relative(*args)
749
end
750
751
def self._rl_get_screen_size(*args)
752
refresh_console_handle
753
old_rl_get_screen_size(*args)
754
end
755
756
def self.space_to_eol(*args)
757
refresh_console_handle
758
old_space_to_eol(*args)
759
end
760
761
def self.insert_some_chars(*args)
762
refresh_console_handle
763
old_insert_some_chars(*args)
764
end
765
end
766
end
767
rescue ::LoadError => e
768
if @rl_err.nil? && index
769
# Then this is the first time the require failed and we have an index
770
# for the gem version as a fallback.
771
@rl_err = e
772
# Put the gem back and see if that works
773
$LOAD_PATH.insert(index, rb_readline_path)
774
index = rb_readline_path = nil
775
retry
776
else
777
# Either we didn't have the gem to fall back on, or we failed twice.
778
# Nothing more we can do here.
779
raise e
780
end
781
end
782
end
783
end
784
785
end
786
end
787
end
788
789