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/plugins/lab.rb
Views: 1903
1
##
2
# $Id$
3
# $Revision$
4
##
5
6
$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib', 'lab'))
7
8
require 'yaml'
9
10
module Msf
11
class Plugin::Lab < Msf::Plugin
12
class LabCommandDispatcher
13
include Msf::Ui::Console::CommandDispatcher
14
15
attr_accessor :controller
16
17
def initialize(driver)
18
super(driver)
19
@controller = nil
20
21
#
22
# Require the lab gem, but fail nicely if it's not there.
23
#
24
begin
25
require 'lab'
26
rescue LoadError
27
raise "WARNING: Lab gem not found, Please 'gem install lab'"
28
end
29
end
30
31
#
32
# Returns the hash of commands supported by this dispatcher.
33
#
34
def commands
35
{
36
'lab_help' => "lab_help <lab command> - Show that command's description.",
37
'lab_show' => 'lab_show - show all vms in the lab.',
38
'lab_search' => 'lab_search - search local vms in the lab.',
39
'lab_search_tags' => 'lab_search_tag - search local vms in the lab.',
40
# "lab_search_remote" => "lab_search_remote - search remote vms in the lab.",
41
'lab_show_running' => 'lab_show_running - show running vms.',
42
'lab_load' => 'lab_load [file] - load a lab definition from disk.',
43
'lab_save' => 'lab_save [filename] - persist a lab definition in a file.',
44
'lab_load_running' => 'lab_load_running [type] [user] [host] - use the running vms to create a lab.',
45
'lab_load_config' => 'lab_load_config [type] [user] [host] - use the vms in the config to create a lab.',
46
'lab_load_dir' => 'lab_load_dir [type] [directory] - create a lab from a specified directory.',
47
'lab_clear' => 'lab_clear - clear the running lab.',
48
'lab_start' => 'lab_start [vmid+|all] start the specified vm.',
49
'lab_reset' => 'lab_reset [vmid+|all] reset the specified vm.',
50
'lab_suspend' => 'lab_suspend [vmid+|all] suspend the specified vm.',
51
'lab_stop' => 'lab_stop [vmid+|all] stop the specified vm.',
52
'lab_revert' => 'lab_revert [vmid+|all] [snapshot] revert the specified vm.',
53
'lab_snapshot' => 'lab_snapshot [vmid+|all] [snapshot] snapshot all targets for this exploit.',
54
'lab_upload' => 'lab_upload [vmid] [local_path] [remote_path] upload a file.',
55
'lab_run_command' => 'lab_run_command [vmid+|all] [command] run a command on all targets.',
56
'lab_browse_to' => 'lab_browse_to [vmid+|all] [uri] use the default browser to browse to a uri.'
57
}
58
end
59
60
def name
61
'Lab'
62
end
63
64
##
65
## Regular Lab Commands
66
##
67
68
def cmd_lab_load(*args)
69
return lab_usage unless args.count == 1
70
71
res = args[0]
72
good_res = nil
73
if (File.file?(res) && File.readable?(res))
74
# then the provided argument is an absolute path and is gtg.
75
good_res = res
76
elsif [
77
::Msf::Config.data_directory + File::SEPARATOR + 'lab',
78
# there isn't a user_data_directory, but could use:
79
# ::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
80
].each do |dir|
81
res_path = dir + File::SEPARATOR + res
82
if (File.file?(res_path) && File.readable?(res_path))
83
good_res = res_path
84
break
85
end
86
end
87
# let's check to see if it's in the data/lab dir (like when tab completed)
88
end
89
if good_res
90
@controller.from_file(good_res)
91
else
92
print_error("#{res} is not a valid lab definition file (.yml)")
93
end
94
end
95
96
#
97
# Tab completion for the lab_load command
98
#
99
def cmd_lab_load_tabs(str, words)
100
tabs = []
101
# return tabs if words.length > 1
102
if (str && str =~ (/^#{Regexp.escape(File::SEPARATOR)}/))
103
# then you are probably specifying a full path so let's just use normal file completion
104
return tab_complete_filenames(str, words)
105
elsif (!(words[1]) || !words[1].match(%r{^/}))
106
# then let's start tab completion in the data/lab directory
107
begin
108
[
109
::Msf::Config.data_directory + File::SEPARATOR + 'lab',
110
# there isn't a user_data_directory, but could use:
111
# ::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
112
].each do |dir|
113
next if !::File.exist? dir
114
115
tabs += ::Dir.new(dir).find_all do |e|
116
path = dir + File::SEPARATOR + e
117
::File.file?(path) and File.readable?(path)
118
end
119
end
120
rescue Exception
121
end
122
else
123
tabs += tab_complete_filenames(str, words)
124
end
125
126
return tabs
127
end
128
129
def cmd_lab_load_dir(*args)
130
return lab_usage unless args.count == 2
131
132
@controller.build_from_dir(args[0], args[1], true)
133
end
134
135
def cmd_lab_clear(*_args)
136
@controller.clear!
137
end
138
139
def cmd_lab_save(*args)
140
return lab_usage if args.empty?
141
142
@controller.to_file(args[0])
143
end
144
145
def cmd_lab_load_running(*args)
146
return lab_usage if args.empty?
147
148
if args[0] =~ /^remote_/
149
return lab_usage unless args.count == 3
150
151
## Expect a username & password
152
@controller.build_from_running(args[0], args[1], args[2])
153
else
154
return lab_usage unless args.count == 1
155
156
@controller.build_from_running(args[0])
157
end
158
end
159
160
def cmd_lab_load_config(*args)
161
return lab_usage if args.empty?
162
163
if args[0] =~ /^remote_/
164
return lab_usage unless args.count == 3
165
166
## Expect a username & password
167
@controller.build_from_config(args[0], args[1], args[2])
168
else
169
return lab_usage unless args.count == 1
170
171
@controller.build_from_config(args[0])
172
end
173
end
174
175
##
176
## Commands for dealing with a currently-loaded lab
177
##
178
def cmd_lab_show(*args)
179
if args.empty?
180
hlp_print_lab
181
else
182
args.each do |name|
183
if @controller.includes_hostname? name
184
print_line @controller[name].to_yaml
185
else
186
print_error "Unknown vm '#{name}'"
187
end
188
end
189
end
190
end
191
192
def cmd_lab_show_running(*_args)
193
hlp_print_lab_running
194
end
195
196
def cmd_lab_search(*args)
197
if args.empty?
198
hlp_print_lab
199
else
200
args.each do |arg|
201
print_line "Searching for vms with hostname matching #{arg}"
202
@controller.each do |vm|
203
print_line "checking to see #{vm.hostname} matches #{arg}"
204
print_line "#{vm.hostname} matched #{arg}" if vm.hostname =~ Regexp.new(arg)
205
end
206
end
207
end
208
end
209
210
def cmd_lab_search_tags(*args)
211
if args.empty?
212
hlp_print_lab
213
else
214
args.each do |arg|
215
print_line "Searching for vms with tags matching #{arg}"
216
@controller.each do |vm|
217
print_line "checking to see #{vm.hostname} is tagged #{arg}"
218
print_line "#{vm.hostname} tagged #{arg}" if vm.tagged?(arg)
219
end
220
end
221
end
222
end
223
224
def cmd_lab_start(*args)
225
return lab_usage if args.empty?
226
227
if args[0] == 'all'
228
@controller.each do |vm|
229
print_line "Starting lab vm #{vm.hostname}."
230
if !vm.running?
231
vm.start
232
else
233
print_line "Lab vm #{vm.hostname} already running."
234
end
235
end
236
else
237
args.each do |arg|
238
next unless @controller.includes_hostname? arg
239
240
vm = @controller.find_by_hostname(arg)
241
if !vm.running?
242
print_line "Starting lab vm #{vm.hostname}."
243
vm.start
244
else
245
print_line "Lab vm #{vm.hostname} already running."
246
end
247
end
248
end
249
end
250
251
def cmd_lab_stop(*args)
252
return lab_usage if args.empty?
253
254
if args[0] == 'all'
255
@controller.each do |vm|
256
print_line "Stopping lab vm #{vm.hostname}."
257
if vm.running?
258
vm.stop
259
else
260
print_line "Lab vm #{vm.hostname} not running."
261
end
262
end
263
else
264
args.each do |arg|
265
next unless @controller.includes_hostname? arg
266
267
vm = @controller.find_by_hostname(arg)
268
if vm.running?
269
print_line "Stopping lab vm #{vm.hostname}."
270
vm.stop
271
else
272
print_line "Lab vm #{vm.hostname} not running."
273
end
274
end
275
end
276
end
277
278
def cmd_lab_suspend(*args)
279
return lab_usage if args.empty?
280
281
if args[0] == 'all'
282
@controller.each(&:suspend)
283
else
284
args.each do |arg|
285
if @controller.includes_hostname?(arg) && @controller.find_by_hostname(arg).running?
286
print_line "Suspending lab vm #{arg}."
287
@controller.find_by_hostname(arg).suspend
288
end
289
end
290
end
291
end
292
293
def cmd_lab_reset(*args)
294
return lab_usage if args.empty?
295
296
if args[0] == 'all'
297
print_line 'Resetting all lab vms.'
298
@controller.each(&:reset)
299
else
300
args.each do |arg|
301
if @controller.includes_hostname?(arg) && @controller.find_by_hostname(arg).running?
302
print_line "Resetting lab vm #{arg}."
303
@controller.find_by_hostname(arg).reset
304
end
305
end
306
end
307
end
308
309
def cmd_lab_snapshot(*args)
310
return lab_usage if args.count < 2
311
312
snapshot = args[args.count - 1]
313
314
if args[0] == 'all'
315
print_line "Snapshotting all lab vms to snapshot: #{snapshot}."
316
@controller.each { |vm| vm.create_snapshot(snapshot) }
317
else
318
args[0..-2].each do |name_arg|
319
next unless @controller.includes_hostname? name_arg
320
321
print_line "Snapshotting #{name_arg} to snapshot: #{snapshot}."
322
@controller[name_arg].create_snapshot(snapshot)
323
end
324
end
325
end
326
327
def cmd_lab_revert(*args)
328
return lab_usage if args.count < 2
329
330
snapshot = args[args.count - 1]
331
332
if args[0] == 'all'
333
print_line "Reverting all lab vms to snapshot: #{snapshot}."
334
@controller.each { |vm| vm.revert_snapshot(snapshot) }
335
else
336
args[0..-2].each do |name_arg|
337
next unless @controller.includes_hostname? name_arg
338
339
print_line "Reverting #{name_arg} to snapshot: #{snapshot}."
340
@controller[name_arg].revert_snapshot(snapshot)
341
end
342
end
343
end
344
345
def cmd_lab_run_command(*args)
346
return lab_usage if args.empty?
347
348
command = args[args.count - 1]
349
if args[0] == 'all'
350
print_line "Running command #{command} on all vms."
351
@controller.each do |vm|
352
if vm.running?
353
print_line "#{vm.hostname} running command: #{command}."
354
vm.run_command(command)
355
end
356
end
357
else
358
args[0..-2].each do |name_arg|
359
next unless @controller.includes_hostname? name_arg
360
361
if @controller[name_arg].running?
362
print_line "#{name_arg} running command: #{command}."
363
@controller[name_arg].run_command(command)
364
end
365
end
366
end
367
end
368
369
#
370
# Command: lab_upload [vmids] [from] [to]
371
#
372
# Description: Uploads a file to the guest(s)
373
#
374
# Quirks: Pass "all" as a vmid to have it operate on all vms.
375
#
376
def cmd_lab_upload(*args)
377
return lab_usage if args.empty?
378
return lab_usage if args.count < 3
379
380
local_path = args[args.count - 2]
381
vm_path = args[args.count - 1]
382
383
if args[0] == 'all'
384
@controller.each do |vm|
385
if vm.running?
386
print_line "Copying from #{local_path} to #{vm_path} on #{vm.hostname}"
387
vm.copy_to_guest(local_path, vm_path)
388
end
389
end
390
else
391
args[0..-2].each do |vmid_arg|
392
next unless @controller.includes_hostname? vmid_arg
393
394
if @controller[vmid_arg].running?
395
print_line "Copying from #{local_path} to #{vm_path} on #{vmid_arg}"
396
@controller[vmid_arg].copy_to_guest(local_path, vm_path)
397
end
398
end
399
end
400
end
401
402
def cmd_lab_browse_to(*args)
403
return lab_usage if args.empty?
404
405
uri = args[args.count - 1]
406
if args[0] == 'all'
407
print_line "Opening: #{uri} on all vms."
408
@controller.each do |vm|
409
if vm.running?
410
print_line "#{vm.hostname} opening to uri: #{uri}."
411
vm.open_uri(uri)
412
end
413
end
414
else
415
args[0..-2].each do |name_arg|
416
next unless @controller.includes_hostname? name_arg
417
418
if @controller[name_arg].running?
419
print_line "#{name_arg} opening to uri: #{uri}."
420
@controller[name_arg].open_uri(uri)
421
end
422
end
423
end
424
end
425
426
##
427
## Commands for help
428
##
429
430
def longest_cmd_size
431
commands.keys.map(&:size).max
432
end
433
434
# No extended help yet, but this is where more detailed documentation
435
# on particular commands would live. Key is command, (not cmd_command),
436
# value is the documentation.
437
def extended_help
438
{
439
'lab_fake_cmd' => "This is a fake command. It's got its own special docs." +
440
(' ' * longest_cmd_size) + 'It might be long so so deal with formatting somehow.'
441
}
442
end
443
444
# Map for usages
445
def lab_usage
446
caller[0][/`cmd_(.*)'/]
447
cmd = Regexp.last_match(1)
448
if extended_help[cmd] || commands[cmd]
449
cmd_lab_help cmd
450
else # Should never really get here...
451
print_error "Unknown command. Try 'help'"
452
end
453
end
454
455
def cmd_lab_help(*args)
456
if args.empty?
457
commands.each_pair { |k, v| print_line format("%-#{longest_cmd_size}s - %s", k, v) }
458
else
459
args.each do |c|
460
if extended_help[c] || commands[c]
461
print_line format("%-#{longest_cmd_size}s - %s", c, extended_help[c] || commands[c])
462
else
463
print_error "Unknown command '#{c}'"
464
end
465
end
466
end
467
468
print_line
469
print_line "In order to use this plugin, you'll want to configure a .yml lab file"
470
print_line 'You can find an example in data/lab/test_targets.yml'
471
print_line
472
end
473
474
private
475
476
def hlp_print_lab
477
indent = ' '
478
479
tbl = Rex::Text::Table.new(
480
'Header' => 'Available Lab VMs',
481
'Indent' => indent.length,
482
'Columns' => [ 'Hostname', 'Driver', 'Type' ]
483
)
484
485
@controller.each do |vm|
486
tbl << [
487
vm.hostname,
488
vm.driver.class,
489
vm.type
490
]
491
end
492
493
print_line tbl.to_s
494
end
495
496
def hlp_print_lab_running
497
indent = ' '
498
499
tbl = Rex::Text::Table.new(
500
'Header' => 'Running Lab VMs',
501
'Indent' => indent.length,
502
'Columns' => [ 'Hostname', 'Driver', 'Type', 'Power?' ]
503
)
504
505
@controller.each do |vm|
506
next unless vm.running?
507
508
tbl << [
509
vm.hostname,
510
vm.driver.class,
511
vm.type,
512
vm.running?
513
]
514
end
515
print_line tbl.to_s
516
end
517
518
end
519
520
#
521
# The constructor is called when an instance of the plugin is created. The
522
# framework instance that the plugin is being associated with is passed in
523
# the framework parameter. Plugins should call the parent constructor when
524
# inheriting from Msf::Plugin to ensure that the framework attribute on
525
# their instance gets set.
526
#
527
attr_accessor :controller
528
529
def initialize(framework, opts)
530
super
531
532
## Register the commands above
533
console_dispatcher = add_console_dispatcher(LabCommandDispatcher)
534
535
@controller = ::Lab::Controllers::VmController.new
536
537
## Share the vms
538
console_dispatcher.controller = @controller
539
end
540
541
#
542
# The cleanup routine for plugins gives them a chance to undo any actions
543
# they may have done to the framework. For instance, if a console
544
# dispatcher was added, then it should be removed in the cleanup routine.
545
#
546
def cleanup
547
# If we had previously registered a console dispatcher with the console,
548
# deregister it now.
549
remove_console_dispatcher('Lab')
550
end
551
552
#
553
# This method returns a short, friendly name for the plugin.
554
#
555
def name
556
'lab'
557
end
558
559
#
560
# This method returns a brief description of the plugin. It should be no
561
# more than 60 characters, but there are no hard limits.
562
#
563
def desc
564
'Adds the ability to manage VMs'
565
end
566
567
end
568
end
569
570