From babaee8c8ed893910f0407b5fba6042ee53038d3 Mon Sep 17 00:00:00 2001 From: Adam Galway Date: Mon, 11 May 2020 10:22:20 +0100 Subject: [PATCH] adds debug command with spec tests --- lib/msf/ui.rb | 1 + lib/msf/ui/console/command_dispatcher/core.rb | 57 ++ lib/msf/ui/console/driver.rb | 22 +- lib/msf/ui/debug.rb | 287 ++++++ lib/rex/ui/text/shell.rb | 4 +- spec/file_fixtures/debug/config_files/db.ini | 7 + .../debug/config_files/empty.ini | 0 .../debug/error_logs/basic/framework.log | 16 + .../debug/error_logs/empty/framework.log | 0 .../debug/error_logs/long/framework.log | 39 + .../debug/framework_logs/empty/framework.log | 0 .../debug/framework_logs/equal/framework.log | 50 ++ .../debug/framework_logs/long/framework.log | 100 +++ .../debug/framework_logs/short/framework.log | 30 + .../debug/installs/omnibus/version.yml | 0 spec/lib/msf/debug_spec.rb | 830 ++++++++++++++++++ 16 files changed, 1437 insertions(+), 6 deletions(-) create mode 100644 lib/msf/ui/debug.rb create mode 100644 spec/file_fixtures/debug/config_files/db.ini create mode 100644 spec/file_fixtures/debug/config_files/empty.ini create mode 100644 spec/file_fixtures/debug/error_logs/basic/framework.log create mode 100644 spec/file_fixtures/debug/error_logs/empty/framework.log create mode 100644 spec/file_fixtures/debug/error_logs/long/framework.log create mode 100644 spec/file_fixtures/debug/framework_logs/empty/framework.log create mode 100644 spec/file_fixtures/debug/framework_logs/equal/framework.log create mode 100644 spec/file_fixtures/debug/framework_logs/long/framework.log create mode 100644 spec/file_fixtures/debug/framework_logs/short/framework.log create mode 100644 spec/file_fixtures/debug/installs/omnibus/version.yml create mode 100644 spec/lib/msf/debug_spec.rb diff --git a/lib/msf/ui.rb b/lib/msf/ui.rb index bf9c145f7066d..05b25d3bd9af1 100644 --- a/lib/msf/ui.rb +++ b/lib/msf/ui.rb @@ -7,6 +7,7 @@ module Ui require 'rex/ui' require 'msf/ui/banner' require 'msf/ui/tip' +require 'msf/ui/debug' require 'msf/ui/driver' require 'msf/ui/common' require 'msf/ui/console' diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 85530b33af10d..e23400f09f22c 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -72,6 +72,14 @@ class Core @@tip_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ]) + @@debug_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner." ], + "-d" => [ false, "Display the Datastore Information." ], + "-c" => [ false, "Display command history." ], + "-e" => [ false, "Display the most recent Error and Stack Trace." ], + "-l" => [ false, "Display the most recent logs." ], + "-v" => [ false, "Display versions and install info." ]) + @@connect_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ], "-p" => [ true, "List of proxies to use." ], @@ -103,6 +111,7 @@ def commands "cd" => "Change the current working directory", "connect" => "Communicate with a host", "color" => "Toggle color", + "debug" => "Display information useful for debugging", "exit" => "Exit the console", "get" => "Gets the value of a context-specific variable", "getg" => "Gets the value of a global variable", @@ -291,6 +300,54 @@ def cmd_tips(*args) alias cmd_tip cmd_tips + + def cmd_debug_help + print_line "Usage: debug [options]" + print_line + print_line("Print a set of information in a Markdown format to be included when opening an Issue on Github. " + + "This information helps us fix problems you encounter and should be included when you open a new issue: " + + Debug.issue_link) + print @@debug_opts.usage + end + + # + # Display information useful for debugging errors. + # + def cmd_debug(*args) + if args.empty? + print_line Debug.all(framework, driver) + return + end + + if args.include?("-h") + cmd_debug_help + else + output = "" + @@debug_opts.parse(args) do |opt| + case opt + when '-d' + output << Debug.datastore(framework, driver) + when '-c' + output << Debug.history(driver) + when '-e' + output << Debug.errors + when '-l' + output << Debug.logs + when '-v' + output << Debug.versions(framework) + end + end + + if output.empty? + print_line("Valid argument was not given.") + cmd_debug_help + else + output = Debug.preamble + output + print_line output + end + end + end + def cmd_connect_help print_line "Usage: connect [options] " print_line diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 4ca682826cfef..13efc255ae190 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -285,9 +285,9 @@ def load_config(path=nil) end # - # Saves configuration for the console. + # Generate configuration for the console. # - def save_config + def get_config # Build out the console config group group = {} @@ -301,9 +301,23 @@ def save_config end end - # Save it + group + end + + def get_config_core + ConfigCore + end + + def get_config_group + ConfigGroup + end + + # + # Saves configuration for the console. + # + def save_config begin - Msf::Config.save(ConfigGroup => group) + Msf::Config.save(ConfigGroup => get_config) rescue ::Exception print_error("Failed to save console config: #{$!}") end diff --git a/lib/msf/ui/debug.rb b/lib/msf/ui/debug.rb new file mode 100644 index 0000000000000..2555aff66a94c --- /dev/null +++ b/lib/msf/ui/debug.rb @@ -0,0 +1,287 @@ +# -*- coding: binary -*- +# frozen_string_literal: true + +module Msf + module Ui + ### + # + # Displays Metasploit information useful for Debugging. + # + ### + module Debug + COMMAND_HISTORY_TOTAL = 50 + ERROR_TOTAL = 10 + LOG_LINE_TOTAL = 50 + ISSUE_LINK = 'https://github.com/rapid7/metasploit-framework/issues/new/choose' + PREAMBLE = <<~PREMABLE + Please provide the below information in any Github issues you open. New issues can be opened here #{ISSUE_LINK.dup} + %red%undENSURE YOU HAVE REMOVED ANY SENSITIVE INFORMATION BEFORE SUBMITTING!%clr + + ===8<=== CUT AND PASTE EVERYTHING BELOW THIS LINE ===8<=== + + + PREMABLE + + def self.issue_link + return ISSUE_LINK.dup + end + + def self.preamble + return PREAMBLE.dup + end + + def self.all(framework, driver) + all_information = preamble + all_information << datastore(framework, driver) + all_information << history(driver) + all_information << errors + all_information << logs + all_information << versions(framework) + + all_information + end + + def self.datastore(framework, driver) + + # Generate an ini with the existing config file + ini = Rex::Parser::Ini.new(Msf::Config.config_file) + + # Delete all groups from the config ini that potentially have more up to date information + ini.keys.each do |key| + unless key =~ %r{^framework/database} + ini.delete(k) + end + end + + # Retrieve and add more up to date information + add_hash_to_ini_group(ini, framework.datastore, driver.get_config_core) + add_hash_to_ini_group(ini, driver.get_config, driver.get_config_group) + + if driver.active_module + add_hash_to_ini_group(ini, driver.active_module.datastore.dup, driver.active_module.refname) + end + + # Filter credentials + ini.each do |key, value| + if key =~ %r{^framework/database/} + value.transform_values! { '[Filtered]' } + end + end + + if ini.to_s.empty? + content = 'The local config file is empty, no global variables are set, and there is no active module.' + else + content = ini.to_s + end + + build_section( + 'Module/Datastore', + 'The following global/module datastore, and database setup was configured before the issue occurred:', + content + ) + rescue StandardError => e + section_build_error('Failed to extract Datastore', e) + end + + def self.history(driver) + end_pos = Readline::HISTORY.length - 1 + start_pos = end_pos - COMMAND_HISTORY_TOTAL > driver.hist_last_saved ? end_pos - (COMMAND_HISTORY_TOTAL - 1) : driver.hist_last_saved + + commands = '' + while start_pos <= end_pos + # Formats command position in history to 6 characters in length + commands += "#{'%-6.6s' % start_pos.to_s} #{Readline::HISTORY[start_pos]}\n" + start_pos += 1 + end + + build_section( + 'History', + 'The following commands were ran during the session and before this issue occurred:', + commands + ) + rescue StandardError => e + section_build_error('Failed to extract History', e) + end + + def self.errors + + errors = File.read(File.join(Msf::Config.log_directory, 'framework.log')) + + # Returns any error logs in framework.log file as an array + # "[mm/dd/yyyy hh:mm:ss] [e([ANY_NUMBER])]" Indicates the start of an error message + # The end of an error message is indicated by the start of the next log message [mm/dd/yyyy hh:mm:ss] [[ANY_LETTER]([ANY_NUMBER])] + # + # + # The below example framework.log will only return three separate errors, and their accompanying traces: + # + # [05/15/2020 14:13:38] [e(0)] core: [-] Error during IRB: undefined method `[]' for nil:NilClass + # + # [06/19/2020 12:05:02] [i(0)] core: Trying to continue despite failed database creation: could not connect to server: Connection refused + # Is the server running on host "127.0.0.1" and accepting + # TCP/IP connections on port 5433? + # + # [05/15/2020 14:19:20] [e(0)] core: [-] Error while running command debug: can't modify frozen String + # Call stack: + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/ui/debug.rb:33:in `get_all' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/ui/console/command_dispatcher/core.rb:318:in `cmd_debug' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:523:in `run_command' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:474:in `block in run_single' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:468:in `each' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:468:in `run_single' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/shell.rb:158:in `run' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/metasploit/framework/command/console.rb:48:in `start' + # /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/metasploit/framework/command/base.rb:82:in `start' + # + # [06/19/2020 11:51:44] [d(2)] core: Stager osx/armle/reverse_tcp and stage osx/x64/meterpreter have incompatible architectures: armle - x64 + # + # [05/15/2020 14:23:55] [e(0)] core: [-] Error during IRB: undefined method `[]' for nil:NilClass + res = errors.scan(%r|\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}\] \[e\(\d+\)\] (?:(?!\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}\] \[[A-Za-z]\(\d+\)\]).)+|m) + + if res.empty? + return build_section( + 'Errors', + 'The following errors occurred before the issue occurred:', + 'The error log file was empty' + ) + end + + # Scan returns each error as a single item array + res.flatten! + + errors_str = concat_str_array_from_last_idx(res, ERROR_TOTAL) + build_section( + 'Errors', + 'The following errors occurred before the issue occurred:', + errors_str + ) + rescue StandardError => e + section_build_error('Failed to extract Errors', e) + + end + + def self.logs + + log_lines = File.readlines(File.join(Msf::Config.log_directory, 'framework.log')) + + logs_str = concat_str_array_from_last_idx(log_lines, LOG_LINE_TOTAL) + + build_section( + 'Logs', + 'The following logs were recorded before the issue occurred:', + logs_str + ) + rescue StandardError => e + section_build_error('Failed to extract Logs', e) + + end + + def self.versions(framework) + + str = <<~VERSIONS + Framework: #{framework.version} + Ruby: #{RUBY_DESCRIPTION} + Install Root: #{Msf::Config.install_root} + Session Type: #{db_connection_info(framework)} + Install Method: #{installation_method} + VERSIONS + + build_section('Version/Install', 'The versions and install method of your Metasploit setup:', str) + rescue StandardError => e + section_build_error('Failed to extract Versions', e) + end + + class << self + + private + + def add_hash_to_ini_group(ini, hash, group_name) + if hash.empty? + return + end + + unless ini.group?(group_name) + ini.add_group(group_name) + end + + hash.each_pair do |k, v| + ini[group_name][k] = v + end + end + + def concat_str_array_from_last_idx(array, concat_total) + start_pos = array.length > concat_total ? array.length - concat_total : 0 + end_pos = array.length - 1 + + str = array[start_pos..end_pos].join('') + + str.strip + end + + # Copy pasta of the print_connection_info method in console/command_dispatcher/db.rb + def db_connection_info(framework) + unless framework.db.connection_established? + return "#{framework.db.driver} selected, no connection" + end + + cdb = '' + if framework.db.driver == 'http' + cdb = framework.db.name + else + ::ActiveRecord::Base.connection_pool.with_connection do |conn| + if conn.respond_to?(:current_database) + cdb = conn.current_database + end + end + end + + if cdb.empty? + output = "Connected Database Name could not be extracted. DB Connection type: #{framework.db.driver}." + else + output = "Connected to #{cdb}. Connection type: #{framework.db.driver}." + end + + output += " Connection name: #{framework.db.get_data_service}." if framework.db.get_data_service + + output + end + + def build_section(header_name, blurb, content) + <<~SECTION + ## %grn#{header_name.strip}%clr + + #{blurb.strip} + #{with_collapsible_wrapper(content.strip)} + + SECTION + end + + def with_collapsible_wrapper(content) + <<~WRAPPER +
+ Collapse + + ``` + #{content} + ``` + +
+ WRAPPER + end + + def installation_method + if File.exist?(File.join(Msf::Config.install_root, 'version.yml')) + 'Omnibus Installer' + elsif File.directory?(File.join(Msf::Config.install_root, '.git')) + 'Git Clone' + else + 'Other - Please specify' + end + end + + def section_build_error(msg, error) + "#{msg}: #{error.class} - #{error.message} \n Call stack:\n#{error.backtrace.join("\n")}" + end + end + end + end +end diff --git a/lib/rex/ui/text/shell.rb b/lib/rex/ui/text/shell.rb index e9fb01d5c5fc9..0e6054502fcf4 100644 --- a/lib/rex/ui/text/shell.rb +++ b/lib/rex/ui/text/shell.rb @@ -290,8 +290,9 @@ def print(msg='') attr_accessor :on_command_proc attr_accessor :on_print_proc attr_accessor :framework + attr_accessor :hist_last_saved # the number of history lines when last saved/loaded -protected + protected def supports_color? true @@ -481,7 +482,6 @@ def format_prompt(str) attr_accessor :stop_flag, :cont_prompt # :nodoc: attr_accessor :tab_complete_proc # :nodoc: attr_accessor :histfile # :nodoc: - attr_accessor :hist_last_saved # the number of history lines when last saved/loaded attr_accessor :log_source, :stop_count # :nodoc: attr_accessor :local_hostname, :local_username # :nodoc: attr_reader :cont_flag # :nodoc: diff --git a/spec/file_fixtures/debug/config_files/db.ini b/spec/file_fixtures/debug/config_files/db.ini new file mode 100644 index 0000000000000..cb14b8a285fcd --- /dev/null +++ b/spec/file_fixtures/debug/config_files/db.ini @@ -0,0 +1,7 @@ +[framework/database/1] +key10=val10 +key11=val11 + +[framework/database/2] +key12=val12 +key13=val13 diff --git a/spec/file_fixtures/debug/config_files/empty.ini b/spec/file_fixtures/debug/config_files/empty.ini new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/spec/file_fixtures/debug/error_logs/basic/framework.log b/spec/file_fixtures/debug/error_logs/basic/framework.log new file mode 100644 index 0000000000000..24508a6b0102e --- /dev/null +++ b/spec/file_fixtures/debug/error_logs/basic/framework.log @@ -0,0 +1,16 @@ +[00/00/0000 00:00:00] [e(0)] core: [-] Error 1 + +[11/11/1111 11:11:11] [e(0)] core: [-] Error 2 +Call stack: +Stack_Trace +stack trace +STACK-TRACE + +[99/99/9999 99:99:99] [d(0)] NOT_AN_ERROR_TO_BE_IGNORED + +[99/99/9999 99:99:99] [d(0)] NOT_AN_ERROR_TO_BE_IGNORED +[99/99/9999 99:99:99] [d(0)] NOT_AN_ERROR_TO_BE_IGNORED +[99/99/9999 99:99:99] [d(0)] NOT_AN_ERROR_TO_BE_IGNORED + + +[22/22/2222 22:22:22] [e(0)] core: [-] Error 3 diff --git a/spec/file_fixtures/debug/error_logs/empty/framework.log b/spec/file_fixtures/debug/error_logs/empty/framework.log new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/spec/file_fixtures/debug/error_logs/long/framework.log b/spec/file_fixtures/debug/error_logs/long/framework.log new file mode 100644 index 0000000000000..182f69f5548ab --- /dev/null +++ b/spec/file_fixtures/debug/error_logs/long/framework.log @@ -0,0 +1,39 @@ +[00/00/0000 00:00:00] [e(0)] core: [-] Error 1 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 2 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 3 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 4 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 5 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 6 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 7 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 8 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 9 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 10 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 11 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 12 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 13 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 14 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 15 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 16 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 17 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 18 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 19 + +[00/00/0000 00:00:00] [e(0)] core: [-] Error 20 diff --git a/spec/file_fixtures/debug/framework_logs/empty/framework.log b/spec/file_fixtures/debug/framework_logs/empty/framework.log new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/spec/file_fixtures/debug/framework_logs/equal/framework.log b/spec/file_fixtures/debug/framework_logs/equal/framework.log new file mode 100644 index 0000000000000..f4f54a271b874 --- /dev/null +++ b/spec/file_fixtures/debug/framework_logs/equal/framework.log @@ -0,0 +1,50 @@ +[00/00/0000 00:00:00] [e(0)] core: Log Line 1 +[00/00/0000 00:00:00] [e(0)] core: Log Line 2 +[00/00/0000 00:00:00] [e(0)] core: Log Line 3 +[00/00/0000 00:00:00] [e(0)] core: Log Line 4 +[00/00/0000 00:00:00] [e(0)] core: Log Line 5 +[00/00/0000 00:00:00] [e(0)] core: Log Line 6 +[00/00/0000 00:00:00] [e(0)] core: Log Line 7 +[00/00/0000 00:00:00] [e(0)] core: Log Line 8 +[00/00/0000 00:00:00] [e(0)] core: Log Line 9 +[00/00/0000 00:00:00] [e(0)] core: Log Line 10 +[00/00/0000 00:00:00] [e(0)] core: Log Line 11 +[00/00/0000 00:00:00] [e(0)] core: Log Line 12 +[00/00/0000 00:00:00] [e(0)] core: Log Line 13 +[00/00/0000 00:00:00] [e(0)] core: Log Line 14 +[00/00/0000 00:00:00] [e(0)] core: Log Line 15 +[00/00/0000 00:00:00] [e(0)] core: Log Line 16 +[00/00/0000 00:00:00] [e(0)] core: Log Line 17 +[00/00/0000 00:00:00] [e(0)] core: Log Line 18 +[00/00/0000 00:00:00] [e(0)] core: Log Line 19 +[00/00/0000 00:00:00] [e(0)] core: Log Line 20 +[00/00/0000 00:00:00] [e(0)] core: Log Line 21 +[00/00/0000 00:00:00] [e(0)] core: Log Line 22 +[00/00/0000 00:00:00] [e(0)] core: Log Line 23 +[00/00/0000 00:00:00] [e(0)] core: Log Line 24 +[00/00/0000 00:00:00] [e(0)] core: Log Line 25 +[00/00/0000 00:00:00] [e(0)] core: Log Line 26 +[00/00/0000 00:00:00] [e(0)] core: Log Line 27 +[00/00/0000 00:00:00] [e(0)] core: Log Line 28 +[00/00/0000 00:00:00] [e(0)] core: Log Line 29 +[00/00/0000 00:00:00] [e(0)] core: Log Line 30 +[00/00/0000 00:00:00] [e(0)] core: Log Line 31 +[00/00/0000 00:00:00] [e(0)] core: Log Line 32 +[00/00/0000 00:00:00] [e(0)] core: Log Line 33 +[00/00/0000 00:00:00] [e(0)] core: Log Line 34 +[00/00/0000 00:00:00] [e(0)] core: Log Line 35 +[00/00/0000 00:00:00] [e(0)] core: Log Line 36 +[00/00/0000 00:00:00] [e(0)] core: Log Line 37 +[00/00/0000 00:00:00] [e(0)] core: Log Line 38 +[00/00/0000 00:00:00] [e(0)] core: Log Line 39 +[00/00/0000 00:00:00] [e(0)] core: Log Line 40 +[00/00/0000 00:00:00] [e(0)] core: Log Line 41 +[00/00/0000 00:00:00] [e(0)] core: Log Line 42 +[00/00/0000 00:00:00] [e(0)] core: Log Line 43 +[00/00/0000 00:00:00] [e(0)] core: Log Line 44 +[00/00/0000 00:00:00] [e(0)] core: Log Line 45 +[00/00/0000 00:00:00] [e(0)] core: Log Line 46 +[00/00/0000 00:00:00] [e(0)] core: Log Line 47 +[00/00/0000 00:00:00] [e(0)] core: Log Line 48 +[00/00/0000 00:00:00] [e(0)] core: Log Line 49 +[00/00/0000 00:00:00] [e(0)] core: Log Line 50 diff --git a/spec/file_fixtures/debug/framework_logs/long/framework.log b/spec/file_fixtures/debug/framework_logs/long/framework.log new file mode 100644 index 0000000000000..61ced9cda9e90 --- /dev/null +++ b/spec/file_fixtures/debug/framework_logs/long/framework.log @@ -0,0 +1,100 @@ +[00/00/0000 00:00:00] [e(0)] core: Log Line 1 +[00/00/0000 00:00:00] [e(0)] core: Log Line 2 +[00/00/0000 00:00:00] [e(0)] core: Log Line 3 +[00/00/0000 00:00:00] [e(0)] core: Log Line 4 +[00/00/0000 00:00:00] [e(0)] core: Log Line 5 +[00/00/0000 00:00:00] [e(0)] core: Log Line 6 +[00/00/0000 00:00:00] [e(0)] core: Log Line 7 +[00/00/0000 00:00:00] [e(0)] core: Log Line 8 +[00/00/0000 00:00:00] [e(0)] core: Log Line 9 +[00/00/0000 00:00:00] [e(0)] core: Log Line 10 +[00/00/0000 00:00:00] [e(0)] core: Log Line 11 +[00/00/0000 00:00:00] [e(0)] core: Log Line 12 +[00/00/0000 00:00:00] [e(0)] core: Log Line 13 +[00/00/0000 00:00:00] [e(0)] core: Log Line 14 +[00/00/0000 00:00:00] [e(0)] core: Log Line 15 +[00/00/0000 00:00:00] [e(0)] core: Log Line 16 +[00/00/0000 00:00:00] [e(0)] core: Log Line 17 +[00/00/0000 00:00:00] [e(0)] core: Log Line 18 +[00/00/0000 00:00:00] [e(0)] core: Log Line 19 +[00/00/0000 00:00:00] [e(0)] core: Log Line 20 +[00/00/0000 00:00:00] [e(0)] core: Log Line 21 +[00/00/0000 00:00:00] [e(0)] core: Log Line 22 +[00/00/0000 00:00:00] [e(0)] core: Log Line 23 +[00/00/0000 00:00:00] [e(0)] core: Log Line 24 +[00/00/0000 00:00:00] [e(0)] core: Log Line 25 +[00/00/0000 00:00:00] [e(0)] core: Log Line 26 +[00/00/0000 00:00:00] [e(0)] core: Log Line 27 +[00/00/0000 00:00:00] [e(0)] core: Log Line 28 +[00/00/0000 00:00:00] [e(0)] core: Log Line 29 +[00/00/0000 00:00:00] [e(0)] core: Log Line 30 +[00/00/0000 00:00:00] [e(0)] core: Log Line 31 +[00/00/0000 00:00:00] [e(0)] core: Log Line 32 +[00/00/0000 00:00:00] [e(0)] core: Log Line 33 +[00/00/0000 00:00:00] [e(0)] core: Log Line 34 +[00/00/0000 00:00:00] [e(0)] core: Log Line 35 +[00/00/0000 00:00:00] [e(0)] core: Log Line 36 +[00/00/0000 00:00:00] [e(0)] core: Log Line 37 +[00/00/0000 00:00:00] [e(0)] core: Log Line 38 +[00/00/0000 00:00:00] [e(0)] core: Log Line 39 +[00/00/0000 00:00:00] [e(0)] core: Log Line 40 +[00/00/0000 00:00:00] [e(0)] core: Log Line 41 +[00/00/0000 00:00:00] [e(0)] core: Log Line 42 +[00/00/0000 00:00:00] [e(0)] core: Log Line 43 +[00/00/0000 00:00:00] [e(0)] core: Log Line 44 +[00/00/0000 00:00:00] [e(0)] core: Log Line 45 +[00/00/0000 00:00:00] [e(0)] core: Log Line 46 +[00/00/0000 00:00:00] [e(0)] core: Log Line 47 +[00/00/0000 00:00:00] [e(0)] core: Log Line 48 +[00/00/0000 00:00:00] [e(0)] core: Log Line 49 +[00/00/0000 00:00:00] [e(0)] core: Log Line 50 +[00/00/0000 00:00:00] [e(0)] core: Log Line 51 +[00/00/0000 00:00:00] [e(0)] core: Log Line 52 +[00/00/0000 00:00:00] [e(0)] core: Log Line 53 +[00/00/0000 00:00:00] [e(0)] core: Log Line 54 +[00/00/0000 00:00:00] [e(0)] core: Log Line 55 +[00/00/0000 00:00:00] [e(0)] core: Log Line 56 +[00/00/0000 00:00:00] [e(0)] core: Log Line 57 +[00/00/0000 00:00:00] [e(0)] core: Log Line 58 +[00/00/0000 00:00:00] [e(0)] core: Log Line 59 +[00/00/0000 00:00:00] [e(0)] core: Log Line 60 +[00/00/0000 00:00:00] [e(0)] core: Log Line 61 +[00/00/0000 00:00:00] [e(0)] core: Log Line 62 +[00/00/0000 00:00:00] [e(0)] core: Log Line 63 +[00/00/0000 00:00:00] [e(0)] core: Log Line 64 +[00/00/0000 00:00:00] [e(0)] core: Log Line 65 +[00/00/0000 00:00:00] [e(0)] core: Log Line 66 +[00/00/0000 00:00:00] [e(0)] core: Log Line 67 +[00/00/0000 00:00:00] [e(0)] core: Log Line 68 +[00/00/0000 00:00:00] [e(0)] core: Log Line 69 +[00/00/0000 00:00:00] [e(0)] core: Log Line 70 +[00/00/0000 00:00:00] [e(0)] core: Log Line 71 +[00/00/0000 00:00:00] [e(0)] core: Log Line 72 +[00/00/0000 00:00:00] [e(0)] core: Log Line 73 +[00/00/0000 00:00:00] [e(0)] core: Log Line 74 +[00/00/0000 00:00:00] [e(0)] core: Log Line 75 +[00/00/0000 00:00:00] [e(0)] core: Log Line 76 +[00/00/0000 00:00:00] [e(0)] core: Log Line 77 +[00/00/0000 00:00:00] [e(0)] core: Log Line 78 +[00/00/0000 00:00:00] [e(0)] core: Log Line 79 +[00/00/0000 00:00:00] [e(0)] core: Log Line 80 +[00/00/0000 00:00:00] [e(0)] core: Log Line 81 +[00/00/0000 00:00:00] [e(0)] core: Log Line 82 +[00/00/0000 00:00:00] [e(0)] core: Log Line 83 +[00/00/0000 00:00:00] [e(0)] core: Log Line 84 +[00/00/0000 00:00:00] [e(0)] core: Log Line 85 +[00/00/0000 00:00:00] [e(0)] core: Log Line 86 +[00/00/0000 00:00:00] [e(0)] core: Log Line 87 +[00/00/0000 00:00:00] [e(0)] core: Log Line 88 +[00/00/0000 00:00:00] [e(0)] core: Log Line 89 +[00/00/0000 00:00:00] [e(0)] core: Log Line 90 +[00/00/0000 00:00:00] [e(0)] core: Log Line 91 +[00/00/0000 00:00:00] [e(0)] core: Log Line 92 +[00/00/0000 00:00:00] [e(0)] core: Log Line 93 +[00/00/0000 00:00:00] [e(0)] core: Log Line 94 +[00/00/0000 00:00:00] [e(0)] core: Log Line 95 +[00/00/0000 00:00:00] [e(0)] core: Log Line 96 +[00/00/0000 00:00:00] [e(0)] core: Log Line 97 +[00/00/0000 00:00:00] [e(0)] core: Log Line 98 +[00/00/0000 00:00:00] [e(0)] core: Log Line 99 +[00/00/0000 00:00:00] [e(0)] core: Log Line 100 diff --git a/spec/file_fixtures/debug/framework_logs/short/framework.log b/spec/file_fixtures/debug/framework_logs/short/framework.log new file mode 100644 index 0000000000000..453955a08b8a6 --- /dev/null +++ b/spec/file_fixtures/debug/framework_logs/short/framework.log @@ -0,0 +1,30 @@ +[00/00/0000 00:00:00] [e(0)] core: Log Line 1 +[00/00/0000 00:00:00] [e(0)] core: Log Line 2 +[00/00/0000 00:00:00] [e(0)] core: Log Line 3 +[00/00/0000 00:00:00] [e(0)] core: Log Line 4 +[00/00/0000 00:00:00] [e(0)] core: Log Line 5 +[00/00/0000 00:00:00] [e(0)] core: Log Line 6 +[00/00/0000 00:00:00] [e(0)] core: Log Line 7 +[00/00/0000 00:00:00] [e(0)] core: Log Line 8 +[00/00/0000 00:00:00] [e(0)] core: Log Line 9 +[00/00/0000 00:00:00] [e(0)] core: Log Line 10 +[00/00/0000 00:00:00] [e(0)] core: Log Line 11 +[00/00/0000 00:00:00] [e(0)] core: Log Line 12 +[00/00/0000 00:00:00] [e(0)] core: Log Line 13 +[00/00/0000 00:00:00] [e(0)] core: Log Line 14 +[00/00/0000 00:00:00] [e(0)] core: Log Line 15 +[00/00/0000 00:00:00] [e(0)] core: Log Line 16 +[00/00/0000 00:00:00] [e(0)] core: Log Line 17 +[00/00/0000 00:00:00] [e(0)] core: Log Line 18 +[00/00/0000 00:00:00] [e(0)] core: Log Line 19 +[00/00/0000 00:00:00] [e(0)] core: Log Line 20 +[00/00/0000 00:00:00] [e(0)] core: Log Line 21 +[00/00/0000 00:00:00] [e(0)] core: Log Line 22 +[00/00/0000 00:00:00] [e(0)] core: Log Line 23 +[00/00/0000 00:00:00] [e(0)] core: Log Line 24 +[00/00/0000 00:00:00] [e(0)] core: Log Line 25 +[00/00/0000 00:00:00] [e(0)] core: Log Line 26 +[00/00/0000 00:00:00] [e(0)] core: Log Line 27 +[00/00/0000 00:00:00] [e(0)] core: Log Line 28 +[00/00/0000 00:00:00] [e(0)] core: Log Line 29 +[00/00/0000 00:00:00] [e(0)] core: Log Line 30 diff --git a/spec/file_fixtures/debug/installs/omnibus/version.yml b/spec/file_fixtures/debug/installs/omnibus/version.yml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/spec/lib/msf/debug_spec.rb b/spec/lib/msf/debug_spec.rb new file mode 100644 index 0000000000000..2b861047bc232 --- /dev/null +++ b/spec/lib/msf/debug_spec.rb @@ -0,0 +1,830 @@ +require 'spec_helper' +require 'msf/ui/debug' +require 'msf/base/config' +require 'msf/ui/console/driver' + +RSpec.describe Msf::Ui::Debug do + let(:file_fixtures_path) { File.join(Msf::Config.install_root, 'spec', 'file_fixtures') } + + it 'correctly parses an error log' do + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'error_logs', 'basic')) + + error_log_output = <<~LOG + ## %grnErrors%clr + + The following errors occurred before the issue occurred: +
+ Collapse + + ``` + [00/00/0000 00:00:00] [e(0)] core: [-] Error 1 + + [11/11/1111 11:11:11] [e(0)] core: [-] Error 2 + Call stack: + Stack_Trace + stack trace + STACK-TRACE + + [22/22/2222 22:22:22] [e(0)] core: [-] Error 3 + ``` + +
+ + + LOG + + expect(subject.errors).to eql(error_log_output) + end + + it 'correctly parses an error log file larger than the log line total' do + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'error_logs', 'long')) + + logs = '' + digits = 11..20 + + digits.each do |d| + logs += "[00/00/0000 00:00:00] [e(0)] core: [-] Error #{d}\n\n" + end + + error_log_output = <<~LOG + ## %grnErrors%clr + + The following errors occurred before the issue occurred: +
+ Collapse + + ``` + #{logs.strip} + ``` + +
+ + + LOG + + expect(subject.errors).to eql(error_log_output) + end + + it 'correctly parses an empty error log file' do + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'error_logs', 'empty')) + + error_log_output = <<~EMPTY + ## %grnErrors%clr + + The following errors occurred before the issue occurred: +
+ Collapse + + ``` + The error log file was empty + ``` + +
+ + + EMPTY + + expect(subject.errors).to eql(error_log_output) + end + + it 'correctly retrieves and parses a command history shorter than the command total' do + stub_const('Readline::HISTORY', Array.new(4) { |i| "Command #{i + 1}" }) + + driver = instance_double( + Msf::Ui::Console::Driver, + hist_last_saved: 0 + ) + + stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10) + + history_output = <<~E_LOG + ## %grnHistory%clr + + The following commands were ran during the session and before this issue occurred: +
+ Collapse + + ``` + 0 Command 1 + 1 Command 2 + 2 Command 3 + 3 Command 4 + ``` + +
+ + + E_LOG + + expect(subject.history(driver)).to eql(history_output) + end + + it 'correctly retrieves and parses a command history equal in length to the command total' do + driver = instance_double( + ::Msf::Ui::Console::Driver, + hist_last_saved: 0 + ) + + stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10) + + stub_const('Readline::HISTORY', Array.new(10) { |i| "Command #{i + 1}" }) + + history_output = <<~E_LOG + ## %grnHistory%clr + + The following commands were ran during the session and before this issue occurred: +
+ Collapse + + ``` + 0 Command 1 + 1 Command 2 + 2 Command 3 + 3 Command 4 + 4 Command 5 + 5 Command 6 + 6 Command 7 + 7 Command 8 + 8 Command 9 + 9 Command 10 + ``` + +
+ + + E_LOG + + expect(subject.history(driver)).to eql(history_output) + end + + it 'correctly retrieves and parses a command history larger than the command total' do + driver = instance_double( + ::Msf::Ui::Console::Driver, + hist_last_saved: 0 + ) + + stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10) + stub_const('Readline::HISTORY', Array.new(15) { |i| "Command #{i + 1}" }) + + history_output = <<~E_LOG + ## %grnHistory%clr + + The following commands were ran during the session and before this issue occurred: +
+ Collapse + + ``` + 5 Command 6 + 6 Command 7 + 7 Command 8 + 8 Command 9 + 9 Command 10 + 10 Command 11 + 11 Command 12 + 12 Command 13 + 13 Command 14 + 14 Command 15 + ``` + +
+ + + E_LOG + + expect(subject.history(driver)).to eql(history_output) + end + + it 'correctly retrieves and parses a command history larger than the command total and a session command count smaller than the command total' do + driver = instance_double( + ::Msf::Ui::Console::Driver, + hist_last_saved: 10 + ) + + stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10) + stub_const('Readline::HISTORY', Array.new(15) { |i| "Command #{i + 1}" }) + + history_output = <<~E_LOG + ## %grnHistory%clr + + The following commands were ran during the session and before this issue occurred: +
+ Collapse + + ``` + 10 Command 11 + 11 Command 12 + 12 Command 13 + 13 Command 14 + 14 Command 15 + ``` + +
+ + + E_LOG + + expect(subject.history(driver)).to eql(history_output) + end + + it 'correctly retrieves and parses an empty config file and datastore' do + allow(::Msf::Config).to receive(:config_file).and_return(File.join(file_fixtures_path, 'debug', 'config_files', 'empty.ini')) + + framework = instance_double( + ::Msf::Framework, + datastore: {} + ) + + driver = instance_double( + ::Msf::Ui::Console::Driver, + get_config_core: 'config_core', + get_config: {}, + get_config_group: 'config_group', + active_module: nil + ) + + expected_output = <<~OUTPUT + ## %grnModule/Datastore%clr + + The following global/module datastore, and database setup was configured before the issue occurred: +
+ Collapse + + ``` + The local config file is empty, no global variables are set, and there is no active module. + ``` + +
+ + + OUTPUT + + expect(subject.datastore(framework, driver)).to eql(expected_output) + end + + it 'correctly retrieves and parses a populated global datastore' do + allow(::Msf::Config).to receive(:config_file).and_return(File.join(file_fixtures_path, 'debug', 'config_files', 'empty.ini')) + + framework = instance_double( + ::Msf::Framework, + datastore: { + 'key1' => 'val1', + 'key2' => 'val2', + 'key3' => 'val3' + } + ) + + driver = instance_double( + ::Msf::Ui::Console::Driver, + get_config_core: 'group/name/1', + get_config: {}, + get_config_group: 'config_group', + active_module: nil + ) + + expected_output = <<~OUTPUT + ## %grnModule/Datastore%clr + + The following global/module datastore, and database setup was configured before the issue occurred: +
+ Collapse + + ``` + [group/name/1] + key1=val1 + key2=val2 + key3=val3 + ``` + +
+ + + OUTPUT + + expect(subject.datastore(framework, driver)).to eql(expected_output) + end + + it 'correctly retrieves and parses a populated global datastore and current module' do + allow(::Msf::Config).to receive(:config_file).and_return(File.join(file_fixtures_path, 'debug', 'config_files', 'empty.ini')) + + framework = instance_double( + ::Msf::Framework, + datastore: { + 'key1' => 'val1', + 'key2' => 'val2', + 'key3' => 'val3' + } + ) + + driver = instance_double( + ::Msf::Ui::Console::Driver, + get_config_core: 'group/name/1', + get_config: { + 'key4' => 'val4', + 'key5' => 'val5', + 'key6' => 'val6' + }, + get_config_group: 'group/name/2', + active_module: nil + ) + + expected_output = <<~OUTPUT + ## %grnModule/Datastore%clr + + The following global/module datastore, and database setup was configured before the issue occurred: +
+ Collapse + + ``` + [group/name/1] + key1=val1 + key2=val2 + key3=val3 + + [group/name/2] + key4=val4 + key5=val5 + key6=val6 + ``` + +
+ + + OUTPUT + + expect(subject.datastore(framework, driver)).to eql(expected_output) + end + + it 'correctly retrieves and parses active module variables ' do + allow(::Msf::Config).to receive(:config_file).and_return(File.join(file_fixtures_path, 'debug', 'config_files', 'empty.ini')) + + framework = instance_double( + ::Msf::Framework, + datastore: {} + ) + + active_module = instance_double( + Msf::Module, + datastore: { + 'key7' => 'val7', + 'key8' => 'default_val8', + 'key9' => 'val9' + }, + refname: 'active/module/variables' + ) + + driver = instance_double( + ::Msf::Ui::Console::Driver, + get_config_core: 'group/name/1', + get_config: {}, + get_config_group: 'config_group', + active_module: active_module + ) + + expected_output = <<~OUTPUT + ## %grnModule/Datastore%clr + + The following global/module datastore, and database setup was configured before the issue occurred: +
+ Collapse + + ``` + [active/module/variables] + key7=val7 + key8=default_val8 + key9=val9 + ``` + +
+ + + OUTPUT + + expect(subject.datastore(framework, driver)).to eql(expected_output) + end + + it 'correctly retrieves and parses Database information' do + allow(::Msf::Config).to receive(:config_file).and_return(File.join(file_fixtures_path, 'debug', 'config_files', 'db.ini')) + + framework = instance_double( + ::Msf::Framework, + datastore: {} + ) + + driver = instance_double( + ::Msf::Ui::Console::Driver, + get_config_core: 'group/name/1', + get_config: {}, + get_config_group: 'group/name/2', + active_module: nil + ) + + expected_output = <<~OUTPUT + ## %grnModule/Datastore%clr + + The following global/module datastore, and database setup was configured before the issue occurred: +
+ Collapse + + ``` + [framework/database/1] + key10=[Filtered] + key11=[Filtered] + + [framework/database/2] + key12=[Filtered] + key13=[Filtered] + ``` + +
+ + + OUTPUT + + expect(subject.datastore(framework, driver)).to eql(expected_output) + end + + it 'correctly retrieves and parses logs shorter than the log line total' do + range = 1..30 + logs = '' + range.each do |i| + logs += "[00/00/0000 00:00:00] [e(0)] core: Log Line #{i}\n" + end + + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'framework_logs', 'short')) + + error_log_output = <<~E_LOG + ## %grnLogs%clr + + The following logs were recorded before the issue occurred: +
+ Collapse + + ``` + #{logs.strip} + ``` + +
+ + + E_LOG + + expect(subject.logs).to eql(error_log_output) + end + + it 'correctly retrieves and parses logs equal to the log line total' do + range = 1..50 + logs = '' + range.each do |i| + logs += "[00/00/0000 00:00:00] [e(0)] core: Log Line #{i}\n" + end + + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'framework_logs', 'equal')) + + error_log_output = <<~E_LOG + ## %grnLogs%clr + + The following logs were recorded before the issue occurred: +
+ Collapse + + ``` + #{logs.strip} + ``` + +
+ + + E_LOG + + expect(subject.logs).to eql(error_log_output) + end + + it 'correctly retrieves and parses logs larger than the log line total' do + range = 51..100 + logs = '' + range.each do |i| + logs += "[00/00/0000 00:00:00] [e(0)] core: Log Line #{i}\n" + end + + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'framework_logs', 'long')) + + error_log_output = <<~E_LOG + ## %grnLogs%clr + + The following logs were recorded before the issue occurred: +
+ Collapse + + ``` + #{logs.strip} + ``` + +
+ + + E_LOG + + expect(subject.logs).to eql(error_log_output) + end + + it 'correctly retrieves and parses an empty log file' do + allow(::Msf::Config).to receive(:log_directory).and_return(File.join(file_fixtures_path, 'debug', 'framework_logs', 'empty')) + + error_log_output = <<~E_LOG + ## %grnLogs%clr + + The following logs were recorded before the issue occurred: +
+ Collapse + + ``` + + ``` + +
+ + + E_LOG + + expect(subject.logs).to eql(error_log_output) + end + + it 'correctly retrieves version information with no connected DB' do + db = instance_double( + Msf::DBManager, + connection_established?: false, + driver: 'driver' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + allow(::Msf::Config).to receive(:install_root).and_return('bad/path') + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: bad/path + Session Type: driver selected, no connection + Install Method: Other - Please specify + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end + + it 'correctly retrieves version information with DB connected via http' do + db = double( + 'Metasploit::Framework::DataService::DataProxy', + name: 'db_name', + driver: 'http', + connection_established?: true, + get_data_service: 'db_data_service' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + allow(::Msf::Config).to receive(:install_root).and_return('bad/path') + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: bad/path + Session Type: Connected to db_name. Connection type: http. Connection name: db_data_service. + Install Method: Other - Please specify + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end + + it 'correctly retrieves version information with DB connected via local connection' do + db = double( + 'Metasploit::Framework::DataService::DataProxy', + connection_established?: true, + driver: 'local', + get_data_service: 'db_data_service' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + connection_pool = instance_double(ActiveRecord::ConnectionAdapters::ConnectionPool) + connection = double( + 'connection', + current_database: 'current_db_connection', + respond_to?: true + ) + allow(connection_pool).to receive(:with_connection).and_yield(connection) + + allow(::ActiveRecord::Base).to receive(:connection_pool).and_return(connection_pool) + allow(::Msf::Config).to receive(:install_root).and_return('bad/path') + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: bad/path + Session Type: Connected to current_db_connection. Connection type: local. Connection name: db_data_service. + Install Method: Other - Please specify + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end + + it 'correctly retrieves version information with no connected DB and a Kali Install' do + db = instance_double( + Msf::DBManager, + connection_established?: false, + driver: 'driver' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + allow(::Msf::Config).to receive(:install_root).and_return(File.join(File::SEPARATOR, 'usr', 'share', 'metasploit-framework')) + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: #{File.join(File::SEPARATOR, 'usr', 'share', 'metasploit-framework')} + Session Type: driver selected, no connection + Install Method: Other - Please specify + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end + + it 'correctly retrieves version information with no connected DB and an Omnibus Install' do + db = instance_double( + Msf::DBManager, + connection_established?: false, + driver: 'driver' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + allow(::Msf::Config).to receive(:install_root).and_return(File.join(file_fixtures_path, 'debug', 'installs', 'omnibus')) + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: #{File.join(file_fixtures_path, 'debug', 'installs', 'omnibus')} + Session Type: driver selected, no connection + Install Method: Omnibus Installer + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end + + it 'correctly retrieves version information with no connected DB and a Git Clone' do + db = instance_double( + Msf::DBManager, + connection_established?: false, + driver: 'driver' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + allow(::Msf::Config).to receive(:install_root).and_return(File.join(file_fixtures_path, 'debug', 'installs')) + allow(File).to receive(:directory?).with(File.join(Msf::Config.install_root, '.git')).and_return(true) + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: #{File.join(file_fixtures_path, 'debug', 'installs')} + Session Type: driver selected, no connection + Install Method: Git Clone + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end + + it 'correctly retrieves version information with no connected DB and a Arch Pacman install' do + db = instance_double( + Msf::DBManager, + connection_established?: false, + driver: 'driver' + ) + + framework = instance_double( + ::Msf::Framework, + version: 'VERSION', + db: db + ) + + allow(::Msf::Config).to receive(:install_root).times.and_return(File.join(File::SEPARATOR, 'opt', 'metasploit')) + + expected_output = <<~OUTPUT + ## %grnVersion/Install%clr + + The versions and install method of your Metasploit setup: +
+ Collapse + + ``` + Framework: VERSION + Ruby: #{RUBY_DESCRIPTION} + Install Root: #{File.join(File::SEPARATOR, 'opt', 'metasploit')} + Session Type: driver selected, no connection + Install Method: Other - Please specify + ``` + +
+ + + OUTPUT + + expect(subject.versions(framework)).to eql(expected_output) + end +end