Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/plugins/lab.rb
Views: 11705
##1# $Id$2# $Revision$3##45$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib', 'lab'))67require 'yaml'89module Msf10class Plugin::Lab < Msf::Plugin11class LabCommandDispatcher12include Msf::Ui::Console::CommandDispatcher1314attr_accessor :controller1516def initialize(driver)17super(driver)18@controller = nil1920#21# Require the lab gem, but fail nicely if it's not there.22#23begin24require 'lab'25rescue LoadError26raise "WARNING: Lab gem not found, Please 'gem install lab'"27end28end2930#31# Returns the hash of commands supported by this dispatcher.32#33def commands34{35'lab_help' => "lab_help <lab command> - Show that command's description.",36'lab_show' => 'lab_show - show all vms in the lab.',37'lab_search' => 'lab_search - search local vms in the lab.',38'lab_search_tags' => 'lab_search_tag - search local vms in the lab.',39# "lab_search_remote" => "lab_search_remote - search remote vms in the lab.",40'lab_show_running' => 'lab_show_running - show running vms.',41'lab_load' => 'lab_load [file] - load a lab definition from disk.',42'lab_save' => 'lab_save [filename] - persist a lab definition in a file.',43'lab_load_running' => 'lab_load_running [type] [user] [host] - use the running vms to create a lab.',44'lab_load_config' => 'lab_load_config [type] [user] [host] - use the vms in the config to create a lab.',45'lab_load_dir' => 'lab_load_dir [type] [directory] - create a lab from a specified directory.',46'lab_clear' => 'lab_clear - clear the running lab.',47'lab_start' => 'lab_start [vmid+|all] start the specified vm.',48'lab_reset' => 'lab_reset [vmid+|all] reset the specified vm.',49'lab_suspend' => 'lab_suspend [vmid+|all] suspend the specified vm.',50'lab_stop' => 'lab_stop [vmid+|all] stop the specified vm.',51'lab_revert' => 'lab_revert [vmid+|all] [snapshot] revert the specified vm.',52'lab_snapshot' => 'lab_snapshot [vmid+|all] [snapshot] snapshot all targets for this exploit.',53'lab_upload' => 'lab_upload [vmid] [local_path] [remote_path] upload a file.',54'lab_run_command' => 'lab_run_command [vmid+|all] [command] run a command on all targets.',55'lab_browse_to' => 'lab_browse_to [vmid+|all] [uri] use the default browser to browse to a uri.'56}57end5859def name60'Lab'61end6263##64## Regular Lab Commands65##6667def cmd_lab_load(*args)68return lab_usage unless args.count == 16970res = args[0]71good_res = nil72if (File.file?(res) && File.readable?(res))73# then the provided argument is an absolute path and is gtg.74good_res = res75elsif [76::Msf::Config.data_directory + File::SEPARATOR + 'lab',77# there isn't a user_data_directory, but could use:78# ::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"79].each do |dir|80res_path = dir + File::SEPARATOR + res81if (File.file?(res_path) && File.readable?(res_path))82good_res = res_path83break84end85end86# let's check to see if it's in the data/lab dir (like when tab completed)87end88if good_res89@controller.from_file(good_res)90else91print_error("#{res} is not a valid lab definition file (.yml)")92end93end9495#96# Tab completion for the lab_load command97#98def cmd_lab_load_tabs(str, words)99tabs = []100# return tabs if words.length > 1101if (str && str =~ (/^#{Regexp.escape(File::SEPARATOR)}/))102# then you are probably specifying a full path so let's just use normal file completion103return tab_complete_filenames(str, words)104elsif (!(words[1]) || !words[1].match(%r{^/}))105# then let's start tab completion in the data/lab directory106begin107[108::Msf::Config.data_directory + File::SEPARATOR + 'lab',109# there isn't a user_data_directory, but could use:110# ::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"111].each do |dir|112next if !::File.exist? dir113114tabs += ::Dir.new(dir).find_all do |e|115path = dir + File::SEPARATOR + e116::File.file?(path) and File.readable?(path)117end118end119rescue Exception120end121else122tabs += tab_complete_filenames(str, words)123end124125return tabs126end127128def cmd_lab_load_dir(*args)129return lab_usage unless args.count == 2130131@controller.build_from_dir(args[0], args[1], true)132end133134def cmd_lab_clear(*_args)135@controller.clear!136end137138def cmd_lab_save(*args)139return lab_usage if args.empty?140141@controller.to_file(args[0])142end143144def cmd_lab_load_running(*args)145return lab_usage if args.empty?146147if args[0] =~ /^remote_/148return lab_usage unless args.count == 3149150## Expect a username & password151@controller.build_from_running(args[0], args[1], args[2])152else153return lab_usage unless args.count == 1154155@controller.build_from_running(args[0])156end157end158159def cmd_lab_load_config(*args)160return lab_usage if args.empty?161162if args[0] =~ /^remote_/163return lab_usage unless args.count == 3164165## Expect a username & password166@controller.build_from_config(args[0], args[1], args[2])167else168return lab_usage unless args.count == 1169170@controller.build_from_config(args[0])171end172end173174##175## Commands for dealing with a currently-loaded lab176##177def cmd_lab_show(*args)178if args.empty?179hlp_print_lab180else181args.each do |name|182if @controller.includes_hostname? name183print_line @controller[name].to_yaml184else185print_error "Unknown vm '#{name}'"186end187end188end189end190191def cmd_lab_show_running(*_args)192hlp_print_lab_running193end194195def cmd_lab_search(*args)196if args.empty?197hlp_print_lab198else199args.each do |arg|200print_line "Searching for vms with hostname matching #{arg}"201@controller.each do |vm|202print_line "checking to see #{vm.hostname} matches #{arg}"203print_line "#{vm.hostname} matched #{arg}" if vm.hostname =~ Regexp.new(arg)204end205end206end207end208209def cmd_lab_search_tags(*args)210if args.empty?211hlp_print_lab212else213args.each do |arg|214print_line "Searching for vms with tags matching #{arg}"215@controller.each do |vm|216print_line "checking to see #{vm.hostname} is tagged #{arg}"217print_line "#{vm.hostname} tagged #{arg}" if vm.tagged?(arg)218end219end220end221end222223def cmd_lab_start(*args)224return lab_usage if args.empty?225226if args[0] == 'all'227@controller.each do |vm|228print_line "Starting lab vm #{vm.hostname}."229if !vm.running?230vm.start231else232print_line "Lab vm #{vm.hostname} already running."233end234end235else236args.each do |arg|237next unless @controller.includes_hostname? arg238239vm = @controller.find_by_hostname(arg)240if !vm.running?241print_line "Starting lab vm #{vm.hostname}."242vm.start243else244print_line "Lab vm #{vm.hostname} already running."245end246end247end248end249250def cmd_lab_stop(*args)251return lab_usage if args.empty?252253if args[0] == 'all'254@controller.each do |vm|255print_line "Stopping lab vm #{vm.hostname}."256if vm.running?257vm.stop258else259print_line "Lab vm #{vm.hostname} not running."260end261end262else263args.each do |arg|264next unless @controller.includes_hostname? arg265266vm = @controller.find_by_hostname(arg)267if vm.running?268print_line "Stopping lab vm #{vm.hostname}."269vm.stop270else271print_line "Lab vm #{vm.hostname} not running."272end273end274end275end276277def cmd_lab_suspend(*args)278return lab_usage if args.empty?279280if args[0] == 'all'281@controller.each(&:suspend)282else283args.each do |arg|284if @controller.includes_hostname?(arg) && @controller.find_by_hostname(arg).running?285print_line "Suspending lab vm #{arg}."286@controller.find_by_hostname(arg).suspend287end288end289end290end291292def cmd_lab_reset(*args)293return lab_usage if args.empty?294295if args[0] == 'all'296print_line 'Resetting all lab vms.'297@controller.each(&:reset)298else299args.each do |arg|300if @controller.includes_hostname?(arg) && @controller.find_by_hostname(arg).running?301print_line "Resetting lab vm #{arg}."302@controller.find_by_hostname(arg).reset303end304end305end306end307308def cmd_lab_snapshot(*args)309return lab_usage if args.count < 2310311snapshot = args[args.count - 1]312313if args[0] == 'all'314print_line "Snapshotting all lab vms to snapshot: #{snapshot}."315@controller.each { |vm| vm.create_snapshot(snapshot) }316else317args[0..-2].each do |name_arg|318next unless @controller.includes_hostname? name_arg319320print_line "Snapshotting #{name_arg} to snapshot: #{snapshot}."321@controller[name_arg].create_snapshot(snapshot)322end323end324end325326def cmd_lab_revert(*args)327return lab_usage if args.count < 2328329snapshot = args[args.count - 1]330331if args[0] == 'all'332print_line "Reverting all lab vms to snapshot: #{snapshot}."333@controller.each { |vm| vm.revert_snapshot(snapshot) }334else335args[0..-2].each do |name_arg|336next unless @controller.includes_hostname? name_arg337338print_line "Reverting #{name_arg} to snapshot: #{snapshot}."339@controller[name_arg].revert_snapshot(snapshot)340end341end342end343344def cmd_lab_run_command(*args)345return lab_usage if args.empty?346347command = args[args.count - 1]348if args[0] == 'all'349print_line "Running command #{command} on all vms."350@controller.each do |vm|351if vm.running?352print_line "#{vm.hostname} running command: #{command}."353vm.run_command(command)354end355end356else357args[0..-2].each do |name_arg|358next unless @controller.includes_hostname? name_arg359360if @controller[name_arg].running?361print_line "#{name_arg} running command: #{command}."362@controller[name_arg].run_command(command)363end364end365end366end367368#369# Command: lab_upload [vmids] [from] [to]370#371# Description: Uploads a file to the guest(s)372#373# Quirks: Pass "all" as a vmid to have it operate on all vms.374#375def cmd_lab_upload(*args)376return lab_usage if args.empty?377return lab_usage if args.count < 3378379local_path = args[args.count - 2]380vm_path = args[args.count - 1]381382if args[0] == 'all'383@controller.each do |vm|384if vm.running?385print_line "Copying from #{local_path} to #{vm_path} on #{vm.hostname}"386vm.copy_to_guest(local_path, vm_path)387end388end389else390args[0..-2].each do |vmid_arg|391next unless @controller.includes_hostname? vmid_arg392393if @controller[vmid_arg].running?394print_line "Copying from #{local_path} to #{vm_path} on #{vmid_arg}"395@controller[vmid_arg].copy_to_guest(local_path, vm_path)396end397end398end399end400401def cmd_lab_browse_to(*args)402return lab_usage if args.empty?403404uri = args[args.count - 1]405if args[0] == 'all'406print_line "Opening: #{uri} on all vms."407@controller.each do |vm|408if vm.running?409print_line "#{vm.hostname} opening to uri: #{uri}."410vm.open_uri(uri)411end412end413else414args[0..-2].each do |name_arg|415next unless @controller.includes_hostname? name_arg416417if @controller[name_arg].running?418print_line "#{name_arg} opening to uri: #{uri}."419@controller[name_arg].open_uri(uri)420end421end422end423end424425##426## Commands for help427##428429def longest_cmd_size430commands.keys.map(&:size).max431end432433# No extended help yet, but this is where more detailed documentation434# on particular commands would live. Key is command, (not cmd_command),435# value is the documentation.436def extended_help437{438'lab_fake_cmd' => "This is a fake command. It's got its own special docs." +439(' ' * longest_cmd_size) + 'It might be long so so deal with formatting somehow.'440}441end442443# Map for usages444def lab_usage445caller[0][/`cmd_(.*)'/]446cmd = Regexp.last_match(1)447if extended_help[cmd] || commands[cmd]448cmd_lab_help cmd449else # Should never really get here...450print_error "Unknown command. Try 'help'"451end452end453454def cmd_lab_help(*args)455if args.empty?456commands.each_pair { |k, v| print_line format("%-#{longest_cmd_size}s - %s", k, v) }457else458args.each do |c|459if extended_help[c] || commands[c]460print_line format("%-#{longest_cmd_size}s - %s", c, extended_help[c] || commands[c])461else462print_error "Unknown command '#{c}'"463end464end465end466467print_line468print_line "In order to use this plugin, you'll want to configure a .yml lab file"469print_line 'You can find an example in data/lab/test_targets.yml'470print_line471end472473private474475def hlp_print_lab476indent = ' '477478tbl = Rex::Text::Table.new(479'Header' => 'Available Lab VMs',480'Indent' => indent.length,481'Columns' => [ 'Hostname', 'Driver', 'Type' ]482)483484@controller.each do |vm|485tbl << [486vm.hostname,487vm.driver.class,488vm.type489]490end491492print_line tbl.to_s493end494495def hlp_print_lab_running496indent = ' '497498tbl = Rex::Text::Table.new(499'Header' => 'Running Lab VMs',500'Indent' => indent.length,501'Columns' => [ 'Hostname', 'Driver', 'Type', 'Power?' ]502)503504@controller.each do |vm|505next unless vm.running?506507tbl << [508vm.hostname,509vm.driver.class,510vm.type,511vm.running?512]513end514print_line tbl.to_s515end516517end518519#520# The constructor is called when an instance of the plugin is created. The521# framework instance that the plugin is being associated with is passed in522# the framework parameter. Plugins should call the parent constructor when523# inheriting from Msf::Plugin to ensure that the framework attribute on524# their instance gets set.525#526attr_accessor :controller527528def initialize(framework, opts)529super530531## Register the commands above532console_dispatcher = add_console_dispatcher(LabCommandDispatcher)533534@controller = ::Lab::Controllers::VmController.new535536## Share the vms537console_dispatcher.controller = @controller538end539540#541# The cleanup routine for plugins gives them a chance to undo any actions542# they may have done to the framework. For instance, if a console543# dispatcher was added, then it should be removed in the cleanup routine.544#545def cleanup546# If we had previously registered a console dispatcher with the console,547# deregister it now.548remove_console_dispatcher('Lab')549end550551#552# This method returns a short, friendly name for the plugin.553#554def name555'lab'556end557558#559# This method returns a brief description of the plugin. It should be no560# more than 60 characters, but there are no hard limits.561#562def desc563'Adds the ability to manage VMs'564end565566end567end568569570