CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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