CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/lib/msf/ui/console/driver.rb
Views: 1904
# -*- coding: binary -*-1require 'find'2require 'erb'3require 'rexml/document'4require 'fileutils'5require 'digest/md5'67module Msf8module Ui9module Console1011#12# A user interface driver on a console interface.13#14class Driver < Msf::Ui::Driver1516ConfigCore = "framework/core"17ConfigGroup = "framework/ui/console"1819DefaultPrompt = "%undmsf#{Metasploit::Framework::Version::MAJOR}%clr"20DefaultPromptChar = "%clr>"2122#23# Console Command Dispatchers to be loaded after the Core dispatcher.24#25CommandDispatchers = [26CommandDispatcher::Modules,27CommandDispatcher::Jobs,28CommandDispatcher::Resource,29CommandDispatcher::Db,30CommandDispatcher::Creds,31CommandDispatcher::Developer,32CommandDispatcher::DNS33]3435#36# The console driver processes various framework notified events.37#38include FrameworkEventManager3940#41# The console driver is a command shell.42#43include Rex::Ui::Text::DispatcherShell4445include Rex::Ui::Text::Resource4647#48# Initializes a console driver instance with the supplied prompt string and49# prompt character. The optional hash can take extra values that will50# serve to initialize the console driver.51#52# @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow53# unrecognized commands to be executed by the system shell54# @option opts [Boolean] 'Readline' (true) Whether to use the readline or not55# @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file56# where we can store command history57# @option opts [Array<String>] 'Resources' ([]) A list of resource files to58# load. If no resources are given, will load the default resource script,59# 'msfconsole.rc' in the user's {Msf::Config.config_directory config60# directory}61# @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip62# connecting to the database and running migrations63def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})64histfile = opts['HistFile'] || Msf::Config.history_file6566begin67FeatureManager.instance.load_config68rescue StandardError => e69elog(e)70end7172if opts['DeferModuleLoads'].nil?73opts['DeferModuleLoads'] = Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DEFER_MODULE_LOADS)74end7576# Initialize attributes7778framework_create_options = opts.merge({ 'DeferModuleLoads' => true })7980if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DNS)81dns_resolver = Rex::Proto::DNS::CachedResolver.new82dns_resolver.extend(Rex::Proto::DNS::CustomNameserverProvider)83dns_resolver.load_config if dns_resolver.has_config?8485# Defer loading of modules until paths from opts can be added below86framework_create_options = framework_create_options.merge({ 'CustomDnsResolver' => dns_resolver })87end88self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options)8990if self.framework.datastore['Prompt']91prompt = self.framework.datastore['Prompt']92prompt_char = self.framework.datastore['PromptChar'] || DefaultPromptChar93end9495# Call the parent96super(prompt, prompt_char, histfile, framework, :msfconsole)9798# Temporarily disable output99self.disable_output = true100101# Load pre-configuration102load_preconfig103104# Initialize the user interface to use a different input and output105# handle if one is supplied106input = opts['LocalInput']107input ||= Rex::Ui::Text::Input::Stdio.new108109if !opts['Readline']110input.disable_readline111end112113if (opts['LocalOutput'])114if (opts['LocalOutput'].kind_of?(String))115output = Rex::Ui::Text::Output::File.new(opts['LocalOutput'])116else117output = opts['LocalOutput']118end119else120output = Rex::Ui::Text::Output::Stdio.new121end122123init_ui(input, output)124init_tab_complete125126# Add the core command dispatcher as the root of the dispatcher127# stack128enstack_dispatcher(CommandDispatcher::Core)129130# Load the other "core" command dispatchers131CommandDispatchers.each do |dispatcher_class|132dispatcher = enstack_dispatcher(dispatcher_class)133dispatcher.load_config(opts['Config'])134end135136if !framework.db || !framework.db.active137if framework.db.error == "disabled"138print_warning("Database support has been disabled")139else140error_msg = "#{framework.db.error.class.is_a?(String) ? "#{framework.db.error.class} " : nil}#{framework.db.error}"141print_warning("No database support: #{error_msg}")142end143end144145# Register event handlers146register_event_handlers147148# Re-enable output149self.disable_output = false150151# Whether or not command passthru should be allowed152self.command_passthru = opts.fetch('AllowCommandPassthru', true)153154# Whether or not to confirm before exiting155self.confirm_exit = opts['ConfirmExit']156157# Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false158unless opts['Framework']159# Configure the framework module paths160self.framework.init_module_paths(module_paths: opts['ModulePath'], defer_module_loads: opts['DeferModuleLoads'])161end162163unless opts['DeferModuleLoads']164framework.threads.spawn("ModuleCacheRebuild", true) do165framework.modules.refresh_cache_from_module_files166end167end168169# Load console-specific configuration (after module paths are added)170load_config(opts['Config'])171172# Process things before we actually display the prompt and get rocking173on_startup(opts)174175# Process any resource scripts176if opts['Resource'].blank?177# None given, load the default178default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc')179load_resource(default_resource) if ::File.exist?(default_resource)180else181opts['Resource'].each { |r|182load_resource(r)183}184end185186# Process persistent job handler187begin188restore_handlers = JSON.parse(File.read(Msf::Config.persist_file))189rescue Errno::ENOENT, JSON::ParserError190restore_handlers = nil191end192193if restore_handlers194print_status("Starting persistent handler(s)...")195196restore_handlers.each.with_index do |handler_opts, index|197handler = framework.modules.create(handler_opts['mod_name'])198handler.init_ui(self.input, self.output)199replicant_handler = nil200handler.exploit_simple(handler_opts['mod_options']) do |yielded_replicant_handler|201replicant_handler = yielded_replicant_handler202end203204if replicant_handler.nil? || replicant_handler.error205print_status("Failed to start persistent payload handler ##{index} (#{handler_opts['mod_name']})")206next207end208209if replicant_handler.error.nil?210job_id = handler.job_id211print_status "Persistent payload handler started as Job #{job_id}"212end213end214end215216# Process any additional startup commands217if opts['XCommands'] and opts['XCommands'].kind_of? Array218opts['XCommands'].each { |c|219run_single(c)220}221end222end223224#225# Loads configuration that needs to be analyzed before the framework226# instance is created.227#228def load_preconfig229begin230conf = Msf::Config.load231rescue232wlog("Failed to load configuration: #{$!}")233return234end235236if (conf.group?(ConfigCore))237conf[ConfigCore].each_pair { |k, v|238on_variable_set(true, k, v)239}240end241end242243#244# Loads configuration for the console.245#246def load_config(path=nil)247begin248conf = Msf::Config.load(path)249rescue250wlog("Failed to load configuration: #{$!}")251return252end253254# If we have configuration, process it255if (conf.group?(ConfigGroup))256conf[ConfigGroup].each_pair { |k, v|257case k.downcase258when 'activemodule'259run_single("use #{v}")260when 'activeworkspace'261if framework.db.active262workspace = framework.db.find_workspace(v)263framework.db.workspace = workspace if workspace264end265end266}267end268end269270#271# Generate configuration for the console.272#273def get_config274# Build out the console config group275group = {}276277if (active_module)278group['ActiveModule'] = active_module.fullname279end280281if framework.db.active282unless framework.db.workspace.default?283group['ActiveWorkspace'] = framework.db.workspace.name284end285end286287group288end289290def get_config_core291ConfigCore292end293294def get_config_group295ConfigGroup296end297298#299# Saves configuration for the console.300#301def save_config302begin303Msf::Config.save(ConfigGroup => get_config)304rescue ::Exception305print_error("Failed to save console config: #{$!}")306end307end308309#310# Saves the recent history to the specified file311#312def save_recent_history(path)313num = ::Reline::HISTORY.length - hist_last_saved - 1314315tmprc = ""316num.times { |x|317tmprc << ::Reline::HISTORY[hist_last_saved + x] + "\n"318}319320if tmprc.length > 0321print_status("Saving last #{num} commands to #{path} ...")322save_resource(tmprc, path)323else324print_error("No commands to save!")325end326327# Always update this, even if we didn't save anything. We do this328# so that we don't end up saving the "makerc" command itself.329self.hist_last_saved = ::Reline::HISTORY.length330end331332#333# Creates the resource script file for the console.334#335def save_resource(data, path=nil)336path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc')337338begin339rcfd = File.open(path, 'w')340rcfd.write(data)341rcfd.close342rescue ::Exception343end344end345346#347# Called before things actually get rolling such that banners can be348# displayed, scripts can be processed, and other fun can be had.349#350def on_startup(opts = {})351# Check for modules that failed to load352if framework.modules.module_load_error_by_path.length > 0353wlog("The following modules could not be loaded!")354355framework.modules.module_load_error_by_path.each do |path, _error|356wlog("\t#{path}")357end358end359360if framework.modules.module_load_warnings.length > 0361print_warning("The following modules were loaded with warnings:")362363framework.modules.module_load_warnings.each do |path, _error|364wlog("\t#{path}")365end366end367368if framework.db&.active369framework.db.workspace = framework.db.default_workspace unless framework.db.workspace370end371372framework.events.on_ui_start(Msf::Framework::Revision)373374if $msf_spinner_thread375$msf_spinner_thread.kill376$stderr.print "\r" + (" " * 50) + "\n"377end378379run_single("banner") unless opts['DisableBanner']380381payloads_manifest_errors = []382begin383payloads_manifest_errors = ::MetasploitPayloads.manifest_errors if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)384rescue ::StandardError => e385$stderr.print('Could not verify the integrity of the Metasploit Payloads manifest')386elog(e)387end388389av_warning_message if (framework.eicar_corrupted? || payloads_manifest_errors.any?)390391if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)392if payloads_manifest_errors.any?393warn_msg = "Metasploit Payloads manifest errors:\n"394payloads_manifest_errors.each do |file|395warn_msg << "\t#{file[:path]} : #{file[:error]}\n"396end397$stderr.print(warn_msg)398end399end400401opts["Plugins"].each do |plug|402run_single("load '#{plug}'")403end if opts["Plugins"]404405self.on_command_proc = Proc.new { |command| framework.events.on_ui_command(command) }406end407408def av_warning_message409avdwarn = "\e[31m"\410"Warning: This copy of the Metasploit Framework has been corrupted by an installed anti-virus program."\411" We recommend that you disable your anti-virus or exclude your Metasploit installation path, "\412"then restore the removed files from quarantine or reinstall the framework.\e[0m"\413"\n\n"414415$stderr.puts(Rex::Text.wordwrap(avdwarn, 0, 80))416end417418#419# Called when a variable is set to a specific value. This allows the420# console to do extra processing, such as enabling logging or doing421# some other kind of task. If this routine returns false it will indicate422# that the variable is not being set to a valid value.423#424def on_variable_set(glob, var, val)425case var.downcase426when 'sessionlogging'427handle_session_logging(val) if glob428when 'sessiontlvlogging'429handle_session_tlv_logging(val) if glob430when 'consolelogging'431handle_console_logging(val) if glob432when 'loglevel'433handle_loglevel(val) if glob434when 'payload'435handle_payload(val)436when 'ssh_ident'437handle_ssh_ident(val)438end439end440441#442# Called when a variable is unset. If this routine returns false it is an443# indication that the variable should not be allowed to be unset.444#445def on_variable_unset(glob, var)446case var.downcase447when 'sessionlogging'448handle_session_logging('0') if glob449when 'sessiontlvlogging'450handle_session_tlv_logging('false') if glob451when 'consolelogging'452handle_console_logging('0') if glob453when 'loglevel'454handle_loglevel(nil) if glob455end456end457458#459# Proxies to shell.rb's update prompt with our own extras460#461def update_prompt(*args)462if args.empty?463pchar = framework.datastore['PromptChar'] || DefaultPromptChar464p = framework.datastore['Prompt'] || DefaultPrompt465p = "#{p} #{active_module.type}(%bld%red#{active_module.promptname}%clr)" if active_module466super(p, pchar)467else468# Don't squash calls from within lib/rex/ui/text/shell.rb469super(*args)470end471end472473#474# The framework instance associated with this driver.475#476attr_reader :framework477#478# Whether or not to confirm before exiting479#480attr_reader :confirm_exit481#482# Whether or not commands can be passed through.483#484attr_reader :command_passthru485#486# The active module associated with the driver.487#488attr_accessor :active_module489#490# The active session associated with the driver.491#492attr_accessor :active_session493494def stop495framework.events.on_ui_stop()496super497end498499protected500501attr_writer :framework # :nodoc:502attr_writer :confirm_exit # :nodoc:503attr_writer :command_passthru # :nodoc:504505#506# If an unknown command was passed, try to see if it's a valid local507# executable. This is only allowed if command passthru has been permitted508#509def unknown_command(method, line)510if File.basename(method) == 'msfconsole'511print_error('msfconsole cannot be run inside msfconsole')512return513end514515[method, method+".exe"].each do |cmd|516if command_passthru && Rex::FileUtils.find_full_path(cmd)517518self.busy = true519begin520run_unknown_command(line)521rescue ::Errno::EACCES, ::Errno::ENOENT522print_error("Permission denied exec: #{line}")523end524self.busy = false525return526end527end528529if framework.modules.create(method)530super531if prompt_yesno "This is a module we can load. Do you want to use #{method}?"532run_single "use #{method}"533end534535return536end537538super539end540541def run_unknown_command(command)542print_status("exec: #{command}")543print_line('')544system(command)545end546547##548#549# Handlers for various global configuration values550#551##552553#554# SessionLogging.555#556def handle_session_logging(val)557if (val =~ /^(y|t|1)/i)558Msf::Logging.enable_session_logging(true)559framework.sessions.values.each do |session|560Msf::Logging.start_session_log(session)561end562print_line("Session logging enabled.")563else564Msf::Logging.enable_session_logging(false)565framework.sessions.values.each do |session|566Msf::Logging.stop_session_log(session)567end568print_line("Session logging disabled.")569end570end571572#573# ConsoleLogging.574#575def handle_console_logging(val)576if (val =~ /^(y|t|1)/i)577Msf::Logging.enable_log_source('console')578print_line("Console logging is now enabled.")579580set_log_source('console')581582rlog("\n[*] Console logging started: #{Time.now}\n\n", 'console')583else584rlog("\n[*] Console logging stopped: #{Time.now}\n\n", 'console')585586unset_log_source587588Msf::Logging.disable_log_source('console')589print_line("Console logging is now disabled.")590end591end592593#594# This method handles adjusting the global log level threshold.595#596def handle_loglevel(val)597set_log_level(Rex::LogSource, val)598set_log_level(Msf::LogSource, val)599end600601#602# This method handles setting a desired payload603#604# TODO: Move this out of the console driver!605#606def handle_payload(val)607if framework && !framework.payloads.valid?(val)608return false609elsif active_module && (active_module.exploit? || active_module.evasion?)610return false unless active_module.is_payload_compatible?(val)611elsif active_module && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)612active_module.datastore.clear_non_user_defined613elsif framework && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)614framework.datastore.clear_non_user_defined615end616end617618#619# This method monkeypatches Net::SSH's client identification string620#621# TODO: Move this out of the console driver!622#623def handle_ssh_ident(val)624# HACK: Suppress already initialized constant warning625verbose, $VERBOSE = $VERBOSE, nil626627return false unless val.is_a?(String) && !val.empty?628629require 'net/ssh'630631# HACK: Bypass dynamic constant assignment error632::Net::SSH::Transport::ServerVersion.const_set(:PROTO_VERSION, val)633634true635rescue LoadError636print_error('Net::SSH could not be loaded')637false638rescue NameError639print_error('Invalid constant Net::SSH::Transport::ServerVersion::PROTO_VERSION')640false641ensure642# Restore warning643$VERBOSE = verbose644end645646def handle_session_tlv_logging(val)647return false if val.nil?648649if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false')650return true651elsif val.start_with?('file:') && !val.split('file:').empty?652pathname = ::Pathname.new(val.split('file:').last)653654# Check if we want to write the log to file655if ::File.file?(pathname)656if ::File.writable?(pathname)657return true658else659print_status "No write permissions for log output file: #{pathname}"660return false661end662# Check if we want to write the log file to a directory663elsif ::File.directory?(pathname)664if ::File.writable?(pathname)665return true666else667print_status "No write permissions for log output directory: #{pathname}"668return false669end670# Check if the subdirectory exists671elsif ::File.directory?(pathname.dirname)672if ::File.writable?(pathname.dirname)673return true674else675print_status "No write permissions for log output directory: #{pathname.dirname}"676return false677end678else679# Else the directory doesn't exist. Check if we can create it.680begin681::FileUtils.mkdir_p(pathname.dirname)682return true683rescue ::StandardError => e684print_status "Error when trying to create directory #{pathname.dirname}: #{e.message}"685return false686end687end688end689690false691end692end693694end695end696end697698699