From 81472b529cbf915521303a658696a1f2cd4ef7cd Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Tue, 15 Jan 2019 16:06:10 -0600 Subject: [PATCH 01/16] Simplify deletion-related questions in msfdb --- msfdb | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/msfdb b/msfdb index a711f8933f..dde51896eb 100755 --- a/msfdb +++ b/msfdb @@ -64,7 +64,8 @@ require 'msf/util/helper' ws_env: ENV['RACK_ENV'] || 'production', retry_max: 10, retry_delay: 5.0, - ws_user: nil + ws_user: nil, + delete_existing_data: nil } @@ -207,8 +208,7 @@ def init_db return end - if File.exist?(@db_conf) && - !ask_yn("Found database config at #{@db_conf}, do you want to overwrite it?") + if File.exist?(@db_conf) && !@options[:delete_existing_data] if !load_db_config puts "Failed to load existing database config. Please reinit and overwrite the file." return @@ -366,12 +366,12 @@ def delete_db if Dir.exist?(@db) stop_db - if ask_yn("Delete all data at #{@db}?") + if @options[:delete_existing_data] puts "Deleting all data at #{@db}" FileUtils.rm_rf(@db) end - if File.file?(@db_conf) && ask_yn("Delete database configuration at #{@db_conf}?") + if @options[:delete_existing_data] File.delete(@db_conf) end else @@ -452,8 +452,7 @@ def init_web_service end File.chmod(0640, @ws_conf) - if @options[:ssl] && ((!File.file?(@options[:ssl_key]) || !File.file?(@options[:ssl_cert])) || - (@options[:ssl_key] == @ws_ssl_key_default && @options[:ssl_cert] == @ws_ssl_cert_default)) + if should_generate_web_service_ssl generate_web_service_ssl(key: @options[:ssl_key], cert: @options[:ssl_cert]) end @@ -541,7 +540,7 @@ end def delete_web_service stop_web_service - if File.file?(@ws_conf) && ask_yn("Delete MSF web service configuration at #{@ws_conf}?") + if File.file?(@ws_conf) && @options[:delete_existing_data] File.delete(@ws_conf) end end @@ -553,8 +552,7 @@ end def generate_web_service_ssl(key:, cert:) @ws_generated_ssl = true - if (File.file?(key) || File.file?(cert)) && - !ask_yn("Either MSF web service SSL key #{key} or certificate #{cert} already exist, overwrite both?") + if (File.file?(key) || File.file?(cert)) && !@options[:delete_existing_data] return end @@ -941,6 +939,36 @@ def has_requirements end +def should_generate_web_service_ssl + @options[:ssl] && ((!File.file?(@options[:ssl_key]) || !File.file?(@options[:ssl_cert])) || + (@options[:ssl_key] == @ws_ssl_key_default && @options[:ssl_cert] == @ws_ssl_cert_default)) +end + +def prompt_for_deletion(command) + destructive_operations = [:init, :reinit, :delete] + + if destructive_operations.include? command + if command == :init + if (@options[:component] == :all || @options[:component] == :webservice) && + should_generate_web_service_ssl && (File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert])) + puts should_generate_web_service_ssl + @options[:delete_existing_data] = should_delete + return + elsif (@options[:component] == :all || @options[:component] == :database) && File.exist?(@db_conf) + @options[:delete_existing_data] = should_delete + return + end + else + @options[:delete_existing_data] = should_delete + end + end +end + + +def should_delete + ask_yn("Would you like to delete your existing data and configurations?") +end + if $PROGRAM_NAME == __FILE__ # Bomb out if we're root @@ -978,6 +1006,7 @@ if $PROGRAM_NAME == __FILE__ parse_args(ARGV) command = ARGV[0].to_sym + prompt_for_deletion(command) if @options[:component] == :all @components.each { |component| invoke_command(commands, component.to_sym, command) From 0b37214123bf64531ff9cdb2cf1b65de381a57c6 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Tue, 15 Jan 2019 16:06:10 -0600 Subject: [PATCH 02/16] Simplify deletion-related questions in msfdb --- msfdb | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/msfdb b/msfdb index 9164f3a4c7..3ee4c55a12 100755 --- a/msfdb +++ b/msfdb @@ -64,7 +64,8 @@ require 'msf/util/helper' ws_env: ENV['RACK_ENV'] || 'production', retry_max: 10, retry_delay: 5.0, - ws_user: nil + ws_user: nil, + delete_existing_data: nil } @@ -207,8 +208,7 @@ def init_db return end - if File.exist?(@db_conf) && - !ask_yn("Found database config at #{@db_conf}, do you want to overwrite it?") + if File.exist?(@db_conf) && !@options[:delete_existing_data] if !load_db_config puts "Failed to load existing database config. Please reinit and overwrite the file." return @@ -366,12 +366,12 @@ def delete_db if Dir.exist?(@db) stop_db - if ask_yn("Delete all data at #{@db}?") + if @options[:delete_existing_data] puts "Deleting all data at #{@db}" FileUtils.rm_rf(@db) end - if File.file?(@db_conf) && ask_yn("Delete database configuration at #{@db_conf}?") + if @options[:delete_existing_data] File.delete(@db_conf) end else @@ -404,8 +404,7 @@ def init_web_service @msf_ws_user = @options[:ws_user] end - if @options[:ssl] && ((!File.file?(@options[:ssl_key]) || !File.file?(@options[:ssl_cert])) || - (@options[:ssl_key] == @ws_ssl_key_default && @options[:ssl_cert] == @ws_ssl_cert_default)) + if should_generate_web_service_ssl generate_web_service_ssl(key: @options[:ssl_key], cert: @options[:ssl_cert]) end @@ -502,8 +501,7 @@ end def generate_web_service_ssl(key:, cert:) @ws_generated_ssl = true - if (File.file?(key) || File.file?(cert)) && - !ask_yn("Either MSF web service SSL key #{key} or certificate #{cert} already exist, overwrite both?") + if (File.file?(key) || File.file?(cert)) && !@options[:delete_existing_data] return end @@ -889,6 +887,36 @@ def has_requirements end +def should_generate_web_service_ssl + @options[:ssl] && ((!File.file?(@options[:ssl_key]) || !File.file?(@options[:ssl_cert])) || + (@options[:ssl_key] == @ws_ssl_key_default && @options[:ssl_cert] == @ws_ssl_cert_default)) +end + +def prompt_for_deletion(command) + destructive_operations = [:init, :reinit, :delete] + + if destructive_operations.include? command + if command == :init + if (@options[:component] == :all || @options[:component] == :webservice) && + should_generate_web_service_ssl && (File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert])) + puts should_generate_web_service_ssl + @options[:delete_existing_data] = should_delete + return + elsif (@options[:component] == :all || @options[:component] == :database) && File.exist?(@db_conf) + @options[:delete_existing_data] = should_delete + return + end + else + @options[:delete_existing_data] = should_delete + end + end +end + + +def should_delete + ask_yn("Would you like to delete your existing data and configurations?") +end + if $PROGRAM_NAME == __FILE__ # Bomb out if we're root @@ -926,6 +954,7 @@ if $PROGRAM_NAME == __FILE__ parse_args(ARGV) command = ARGV[0].to_sym + prompt_for_deletion(command) if @options[:component] == :all @components.each { |component| invoke_command(commands, component.to_sym, command) From 49fd26644266608aa6a4123ebb9456b26076d6c7 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Wed, 16 Jan 2019 14:05:43 -0600 Subject: [PATCH 03/16] Add colors and formatting --- msfdb | 73 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/msfdb b/msfdb index 3ee4c55a12..40e36c0a7a 100755 --- a/msfdb +++ b/msfdb @@ -69,6 +69,34 @@ require 'msf/util/helper' } +class String + def red + "\e[1;91;40m#{self}\e[0m" + end + + def green + "\e[1;92;40m#{self}\e[0m" + end + + def blue + "\e[1;94;40m#{self}\e[0m" + end + + def cyan + "\e[0;36;40m#{self}\e[0m" + end + + def cyan_bold + "\e[1;36;40m#{self}\e[0m" + end + + def cyan_bold_underline + "\e[4;1;36;40m#{self}\e[0m" + end + +end + + def run_cmd(cmd, input: nil, env: {}) exitstatus = 0 err = out = "" @@ -139,10 +167,10 @@ def started_db run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} -l #{@db}/log start") sleep(2) if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} status") != 0 - puts 'failed' + puts "#{'failed'.red}" false else - puts 'success' + puts "#{'success'.green}" true end end @@ -338,7 +366,7 @@ end def ask_yn(question) loop do - print "#{question}: " + print "#{'[?]'.blue} #{question}: " yn = STDIN.gets case yn when /^[Yy]/ @@ -352,7 +380,7 @@ def ask_yn(question) end def ask_value(question, default_value) - print "#{question}[#{default_value}]: " + print "#{'[?]'.blue} #{question}[#{default_value}]: " input = STDIN.gets.strip if input.nil? || input.empty? return default_value @@ -438,7 +466,7 @@ def start_web_service(expect_auth: true) end # daemonize MSF web service - puts 'Attempting to start MSF web service...' + print 'Attempting to start MSF web service...' if run_cmd("#{thin_cmd} start") == 0 # wait until web service is online retry_count = 0 @@ -454,18 +482,22 @@ def start_web_service(expect_auth: true) is_online = response_data[:state] != :offline end - if response_data[:state] == :online + if response_data[:state] == :online + puts "#{'success'.green}" puts 'MSF web service started and online' return true - elsif response_data[:state] == :error + elsif response_data[:state] == :error + puts "#{'failed'.red}" puts 'MSF web service appears to be started, but may not operate as expected.' puts "#{response_data[:message]}" - else + else + puts "#{'failed'.red}" puts 'MSF web service does not appear to be started.' end puts "Please see #{@ws_log} for additional details." return false else + puts "#{'failed'.red}" puts 'Failed to start MSF web service' return false end @@ -554,7 +586,7 @@ def add_web_service_workspace(name: 'default') response = response_data[:response] puts "add_web_service_workspace: add workspace response=#{response}" if @options[:debug] if response.nil? || response.dig(:data, :name) != name - puts "Error creating MSF web service workspace '#{name}'" + puts "#{'Error'.red} creating MSF web service workspace '#{name}'" return false end return true @@ -575,11 +607,21 @@ def add_web_service_user response = response_data[:response] puts "add_web_service_user: create user response=#{response}" if @options[:debug] if response.nil? || response.dig(:data, :username) != @msf_ws_user - puts "Error creating MSF web service user #{@msf_ws_user}" + puts "#{'Error'.red} creating MSF web service user #{@msf_ws_user}" return false end - puts "\nMSF web service username: #{@msf_ws_user}" - puts "MSF web service password: #{msf_ws_pass}" + + puts "\n\n#{' ############################################################'.cyan}" + print "#{' ## '.cyan}" + print"#{'MSF Web Service Credentials'.cyan_bold_underline}" + puts"#{' ##'.cyan}" + puts "#{' ## ##'.cyan}" + puts "#{' ## Please store these credentials securely. ##'.cyan}" + puts "#{' ## You will need them to connect to the webservice. ##'.cyan}" + puts "#{' ############################################################'.cyan}" + + puts "\n\n#{'MSF web service username'.cyan_bold}: #{@msf_ws_user}" + puts "#{'MSF web service password'.cyan_bold}: #{msf_ws_pass}" # Send request to create new API token for the user generate_token_uri = get_web_service_uri(path: '/api/v1/auth/generate-token') @@ -588,11 +630,10 @@ def add_web_service_user response = response_data[:response] puts "add_web_service_user: generate token response=#{response}" if @options[:debug] if response.nil? || (@ws_api_token = response.dig(:data, :token)).nil? - puts 'Error creating MSF web service user API token' + puts "#{'Error'.red} creating MSF web service user API token" return false end - puts "MSF web service user API token: #{@ws_api_token}" - puts 'Please store these credentials securely.' + puts "#{'MSF web service user API token'.cyan_bold}: #{@ws_api_token}" return true end @@ -860,7 +901,7 @@ def invoke_command(commands, component, command) if !method.nil? send(method) else - puts "Error: unrecognized command '#{command}' for #{component}" + puts "#{'Error'.red}: unrecognized command '#{command}' for #{component}" end end From 1975bbf16030f33b1f8b4434e106d57426966622 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Wed, 16 Jan 2019 17:01:09 -0600 Subject: [PATCH 04/16] Add password option to prompt --- msfdb | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/msfdb b/msfdb index 40e36c0a7a..f8994e745a 100755 --- a/msfdb +++ b/msfdb @@ -1,6 +1,7 @@ #!/usr/bin/env ruby require 'fileutils' +require 'io/console' require 'json' require 'net/http' require 'net/https' @@ -380,7 +381,7 @@ def ask_yn(question) end def ask_value(question, default_value) - print "#{'[?]'.blue} #{question}[#{default_value}]: " + print "#{'[?]'.blue} #{question} [#{default_value}]: " input = STDIN.gets.strip if input.nil? || input.empty? return default_value @@ -389,6 +390,16 @@ def ask_value(question, default_value) end end +def ask_password(question) + print "#{'[?]'.blue} #{question}: " + input = STDIN.noecho(&:gets).chomp + print "\n" + if input.nil? || input.empty? + return pw_gen + else + return input + end +end def delete_db if Dir.exist?(@db) @@ -432,6 +443,12 @@ def init_web_service @msf_ws_user = @options[:ws_user] end + if @options[:ws_pass].nil? + @msf_ws_pass = ask_password('Initial MSF web service account password? (Leave blank for random password)') + else + @msf_ws_pass = @options[:ws_pass] + end + if should_generate_web_service_ssl generate_web_service_ssl(key: @options[:ssl_key], cert: @options[:ssl_cert]) end @@ -596,8 +613,7 @@ def add_web_service_user puts "Creating MSF web service user #{@msf_ws_user}" # Generate new web service user password - msf_ws_pass = pw_gen - cred_data = { username: @msf_ws_user, password: msf_ws_pass } + cred_data = { username: @msf_ws_user, password: @msf_ws_pass } # Send request to create new admin user user_data = cred_data.merge({ admin: true }) @@ -611,7 +627,7 @@ def add_web_service_user return false end - puts "\n\n#{' ############################################################'.cyan}" + puts "\n#{' ############################################################'.cyan}" print "#{' ## '.cyan}" print"#{'MSF Web Service Credentials'.cyan_bold_underline}" puts"#{' ##'.cyan}" @@ -620,8 +636,8 @@ def add_web_service_user puts "#{' ## You will need them to connect to the webservice. ##'.cyan}" puts "#{' ############################################################'.cyan}" - puts "\n\n#{'MSF web service username'.cyan_bold}: #{@msf_ws_user}" - puts "#{'MSF web service password'.cyan_bold}: #{msf_ws_pass}" + puts "\n#{'MSF web service username'.cyan_bold}: #{@msf_ws_user}" + puts "#{'MSF web service password'.cyan_bold}: #{@msf_ws_pass}" # Send request to create new API token for the user generate_token_uri = get_web_service_uri(path: '/api/v1/auth/generate-token') @@ -638,7 +654,7 @@ def add_web_service_user end def output_web_service_information - puts '' + puts "\n\n" puts 'MSF web service configuration complete' puts 'Connect to the data service in msfconsole using the command:' puts "#{get_db_connect_command}" @@ -882,6 +898,10 @@ def parse_args(args) @options[:ws_user] = u } + opts.on('--pass PASS', 'Initial web service admin password') { |p| + @options[:ws_pass] = p + } + opts.separator('') opts.separator(subtext) end From 309437c9bc970dc6b207fc5a3d11f56b20cc66f1 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Wed, 16 Jan 2019 17:21:12 -0600 Subject: [PATCH 05/16] Remove accidental debug statement --- msfdb | 1 - 1 file changed, 1 deletion(-) diff --git a/msfdb b/msfdb index f8994e745a..a7708e96fe 100755 --- a/msfdb +++ b/msfdb @@ -960,7 +960,6 @@ def prompt_for_deletion(command) if command == :init if (@options[:component] == :all || @options[:component] == :webservice) && should_generate_web_service_ssl && (File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert])) - puts should_generate_web_service_ssl @options[:delete_existing_data] = should_delete return elsif (@options[:component] == :all || @options[:component] == :database) && File.exist?(@db_conf) From 60a69f086bbeaac0a5a710456f8b351df807c3dd Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Fri, 18 Jan 2019 12:40:49 -0600 Subject: [PATCH 06/16] Refactor web service status methods --- msfdb | 74 ++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/msfdb b/msfdb index a7708e96fe..48397d9ab0 100755 --- a/msfdb +++ b/msfdb @@ -94,7 +94,6 @@ class String def cyan_bold_underline "\e[4;1;36;40m#{self}\e[0m" end - end @@ -423,20 +422,47 @@ def reinit_db init_db end -def status_web_service +class WebServicePIDStatus + RUNNING = 0 + INACTIVE = 1 + NO_PID_FILE = 2 +end + +def web_service_pid + File.file?(@ws_pid) ? tail(@ws_pid) : nil +end + +def web_service_pid_status if File.file?(@ws_pid) ws_pid = tail(@ws_pid) if ws_pid.nil? || !process_active?(ws_pid.to_i) - puts "MSF web service is not running: PID file found at #{@ws_pid}, but no active process running as PID #{ws_pid}" + WebServicePIDStatus::INACTIVE else - puts "MSF web service is running as PID #{ws_pid}" + WebServicePIDStatus::RUNNING end else + WebServicePIDStatus::NO_PID_FILE + end +end + +def status_web_service + ws_pid = web_service_pid + status = web_service_pid_status + if status == WebServicePIDStatus::RUNNING + puts "MSF web service is running as PID #{ws_pid}" + elsif status == WebServicePIDStatus::INACTIVE + puts "MSF web service is not running: PID file found at #{@ws_pid}, but no active process running as PID #{ws_pid}" + elsif status == WebServicePIDStatus::NO_PID_FILE puts "MSF web service is not running: no PID file found at #{@ws_pid}" end end def init_web_service + if web_service_pid_status == WebServicePIDStatus::RUNNING + puts "MSF web service is already running as PID #{web_service_pid}" + return false + end + if @options[:ws_user].nil? @msf_ws_user = ask_value('Initial MSF web service account username?', @msf_ws_user) else @@ -470,16 +496,15 @@ def start_web_service(expect_auth: true) end # check if MSF web service is already started - if File.file?(@ws_pid) - ws_pid = tail(@ws_pid) - if ws_pid.nil? || !process_active?(ws_pid.to_i) - puts "MSF web service PID file found, but no active process running as PID #{ws_pid}" - puts "Deleting MSF web service PID file #{@ws_pid}" - File.delete(@ws_pid) - else - puts "MSF web service is already running as PID #{ws_pid}" - return false - end + ws_pid = web_service_pid + status = web_service_pid_status + if status == WebServicePIDStatus::RUNNING + puts "MSF web service is already running as PID #{ws_pid}" + return false + elsif status == WebServicePIDStatus::INACTIVE + puts "MSF web service PID file found, but no active process running as PID #{ws_pid}" + puts "Deleting MSF web service PID file #{@ws_pid}" + File.delete(@ws_pid) end # daemonize MSF web service @@ -521,16 +546,17 @@ def start_web_service(expect_auth: true) end def stop_web_service - ws_pid = tail(@ws_pid) - if ws_pid.nil? || !process_active?(ws_pid.to_i) + ws_pid = web_service_pid + status = web_service_pid_status + if status == WebServicePIDStatus::RUNNING + puts "Stopping MSF web service PID #{ws_pid}" + run_cmd("#{thin_cmd} stop") + else puts 'MSF web service is no longer running' - if File.file?(@ws_pid) + if status == WebServicePIDStatus::INACTIVE puts "Deleting MSF web service PID file #{@ws_pid}" File.delete(@ws_pid) end - else - puts "Stopping MSF web service PID #{ws_pid}" - run_cmd("#{thin_cmd} stop") end end @@ -958,11 +984,13 @@ def prompt_for_deletion(command) if destructive_operations.include? command if command == :init - if (@options[:component] == :all || @options[:component] == :webservice) && - should_generate_web_service_ssl && (File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert])) + return if web_service_pid_status == WebServicePIDStatus::RUNNING + if (@options[:component] == :all || @options[:component] == :webservice) && should_generate_web_service_ssl && + (File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert])) @options[:delete_existing_data] = should_delete return - elsif (@options[:component] == :all || @options[:component] == :database) && File.exist?(@db_conf) + end + if (@options[:component] == :all || @options[:component] == :database) && File.exist?(@db_conf) @options[:delete_existing_data] = should_delete return end From 3e949a49c79210281fafc17def821e1d47410ee6 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Tue, 22 Jan 2019 13:55:06 -0600 Subject: [PATCH 07/16] Skip attempts to restart database on failures --- msfdb | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/msfdb b/msfdb index 48397d9ab0..755376da83 100755 --- a/msfdb +++ b/msfdb @@ -183,23 +183,13 @@ def start_db update_db_port - while !started_db + if !started_db last_log = tail("#{@db}/log") puts last_log - fixed = false if last_log =~ /not compatible/ puts 'Please attempt to upgrade the database manually using pg_upgrade.' end - if !fixed - if ask_yn('Your database may be corrupt, would you like to reinitialize it?') - fixed = reinit_db - end - end - if !fixed - if !ask_yn('Database not started, try again?') - return - end - end + print_error "Your database may be corrupt. Try reinitializing by running #{'msfdb reinit --component database'.underline}." end end @@ -400,6 +390,10 @@ def ask_password(question) end end +def print_error(error) + puts "#{'[!]'.red} #{error}" +end + def delete_db if Dir.exist?(@db) stop_db From 9ecc4b9d1c3bb9b723cdad58c8e1d82f5170e8db Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Tue, 22 Jan 2019 13:56:02 -0600 Subject: [PATCH 08/16] Simplify bash colors --- msfdb | 65 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/msfdb b/msfdb index 755376da83..dd106c2511 100755 --- a/msfdb +++ b/msfdb @@ -71,29 +71,30 @@ require 'msf/util/helper' class String + def bold + "\e[1m#{self}\e[0m" + end + + def underline + "\e[4m#{self}\e[0m" + end + def red - "\e[1;91;40m#{self}\e[0m" + "\e[91m#{self}\e[0m" end def green - "\e[1;92;40m#{self}\e[0m" + "\e[92m#{self}\e[0m" end def blue - "\e[1;94;40m#{self}\e[0m" + "\e[34m#{self}\e[0m" end def cyan - "\e[0;36;40m#{self}\e[0m" + "\e[36m#{self}\e[0m" end - def cyan_bold - "\e[1;36;40m#{self}\e[0m" - end - - def cyan_bold_underline - "\e[4;1;36;40m#{self}\e[0m" - end end @@ -167,10 +168,10 @@ def started_db run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} -l #{@db}/log start") sleep(2) if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} status") != 0 - puts "#{'failed'.red}" + puts "#{'failed'.red.bold}" false else - puts "#{'success'.green}" + puts "#{'success'.green.bold}" true end end @@ -356,7 +357,7 @@ end def ask_yn(question) loop do - print "#{'[?]'.blue} #{question}: " + print "#{'[?]'.blue.bold} #{question}: " yn = STDIN.gets case yn when /^[Yy]/ @@ -370,7 +371,7 @@ def ask_yn(question) end def ask_value(question, default_value) - print "#{'[?]'.blue} #{question} [#{default_value}]: " + print "#{'[?]'.blue.bold} #{question} [#{default_value}]: " input = STDIN.gets.strip if input.nil? || input.empty? return default_value @@ -380,7 +381,7 @@ def ask_value(question, default_value) end def ask_password(question) - print "#{'[?]'.blue} #{question}: " + print "#{'[?]'.blue.bold} #{question}: " input = STDIN.noecho(&:gets).chomp print "\n" if input.nil? || input.empty? @@ -391,7 +392,7 @@ def ask_password(question) end def print_error(error) - puts "#{'[!]'.red} #{error}" + puts "#{'[!]'.red.bold} #{error}" end def delete_db @@ -519,21 +520,21 @@ def start_web_service(expect_auth: true) end if response_data[:state] == :online - puts "#{'success'.green}" + puts "#{'success'.green.bold}" puts 'MSF web service started and online' return true elsif response_data[:state] == :error - puts "#{'failed'.red}" - puts 'MSF web service appears to be started, but may not operate as expected.' + puts "#{'failed'.red.bold}" + print_error 'MSF web service appears to be started, but may not operate as expected.' puts "#{response_data[:message]}" else - puts "#{'failed'.red}" - puts 'MSF web service does not appear to be started.' + puts "#{'failed'.red.bold}" + print_error 'MSF web service does not appear to be started.' end puts "Please see #{@ws_log} for additional details." return false else - puts "#{'failed'.red}" + puts "#{'failed'.red.bold}" puts 'Failed to start MSF web service' return false end @@ -623,7 +624,7 @@ def add_web_service_workspace(name: 'default') response = response_data[:response] puts "add_web_service_workspace: add workspace response=#{response}" if @options[:debug] if response.nil? || response.dig(:data, :name) != name - puts "#{'Error'.red} creating MSF web service workspace '#{name}'" + print_error "Error creating MSF web service workspace '#{name}'" return false end return true @@ -643,21 +644,21 @@ def add_web_service_user response = response_data[:response] puts "add_web_service_user: create user response=#{response}" if @options[:debug] if response.nil? || response.dig(:data, :username) != @msf_ws_user - puts "#{'Error'.red} creating MSF web service user #{@msf_ws_user}" + print_error "Error creating MSF web service user #{@msf_ws_user}" return false end puts "\n#{' ############################################################'.cyan}" print "#{' ## '.cyan}" - print"#{'MSF Web Service Credentials'.cyan_bold_underline}" + print"#{'MSF Web Service Credentials'.cyan.bold.underline}" puts"#{' ##'.cyan}" puts "#{' ## ##'.cyan}" puts "#{' ## Please store these credentials securely. ##'.cyan}" puts "#{' ## You will need them to connect to the webservice. ##'.cyan}" puts "#{' ############################################################'.cyan}" - puts "\n#{'MSF web service username'.cyan_bold}: #{@msf_ws_user}" - puts "#{'MSF web service password'.cyan_bold}: #{@msf_ws_pass}" + puts "\n#{'MSF web service username'.cyan.bold}: #{@msf_ws_user}" + puts "#{'MSF web service password'.cyan.bold}: #{@msf_ws_pass}" # Send request to create new API token for the user generate_token_uri = get_web_service_uri(path: '/api/v1/auth/generate-token') @@ -666,10 +667,10 @@ def add_web_service_user response = response_data[:response] puts "add_web_service_user: generate token response=#{response}" if @options[:debug] if response.nil? || (@ws_api_token = response.dig(:data, :token)).nil? - puts "#{'Error'.red} creating MSF web service user API token" + print_error "Error creating MSF web service user API token" return false end - puts "#{'MSF web service user API token'.cyan_bold}: #{@ws_api_token}" + puts "#{'MSF web service user API token'.cyan.bold}: #{@ws_api_token}" return true end @@ -941,7 +942,7 @@ def invoke_command(commands, component, command) if !method.nil? send(method) else - puts "#{'Error'.red}: unrecognized command '#{command}' for #{component}" + print_error "Error: unrecognized command '#{command}' for #{component}" end end @@ -978,7 +979,7 @@ def prompt_for_deletion(command) if destructive_operations.include? command if command == :init - return if web_service_pid_status == WebServicePIDStatus::RUNNING + return if web_service_pid_status != WebServicePIDStatus::NO_PID_FILE if (@options[:component] == :all || @options[:component] == :webservice) && should_generate_web_service_ssl && (File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert])) @options[:delete_existing_data] = should_delete From 0bd21e9ba1530fe9d7cdf93af856a05589e2c2cc Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Tue, 22 Jan 2019 16:26:29 -0600 Subject: [PATCH 09/16] Skip prompts with new use-defaults option --- msfdb | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/msfdb b/msfdb index dd106c2511..cf286466f7 100755 --- a/msfdb +++ b/msfdb @@ -66,6 +66,7 @@ require 'msf/util/helper' retry_max: 10, retry_delay: 5.0, ws_user: nil, + use_defaults: false, delete_existing_data: nil } @@ -190,7 +191,7 @@ def start_db if last_log =~ /not compatible/ puts 'Please attempt to upgrade the database manually using pg_upgrade.' end - print_error "Your database may be corrupt. Try reinitializing by running #{'msfdb reinit --component database'.underline}." + print_error "Your database may be corrupt. Try reinitializing." end end @@ -458,13 +459,17 @@ def init_web_service return false end - if @options[:ws_user].nil? - @msf_ws_user = ask_value('Initial MSF web service account username?', @msf_ws_user) - else - @msf_ws_user = @options[:ws_user] + unless @options[:use_defaults] + if @options[:ws_user].nil? + @msf_ws_user = ask_value('Initial MSF web service account username?', @msf_ws_user) + else + @msf_ws_user = @options[:ws_user] + end end - if @options[:ws_pass].nil? + if @options[:use_defaults] + @msf_ws_pass = pw_gen + elsif @options[:ws_pass].nil? @msf_ws_pass = ask_password('Initial MSF web service account password? (Leave blank for random password)') else @msf_ws_pass = @options[:ws_pass] @@ -688,9 +693,9 @@ def output_web_service_information end def persist_data_service - if ask_yn('Add data service connection to local msfconsole and persist as default?') + if @options[:use_defaults] || ask_yn('Add data service connection to local msfconsole and persist as default?') data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" - data_service_name = ask_value('Data service connection name?', data_service_name) + data_service_name = ask_value('Data service connection name?', data_service_name) unless @options[:use_defaults] # execute msfconsole commands to add and persist the data service connection connect_cmd = get_db_connect_command(name: data_service_name) cmd = "msfconsole -qx \"#{connect_cmd}; db_save; exit\"" @@ -845,6 +850,9 @@ def parse_args(args) puts opts exit } + opts.on('--use-defaults', 'Accept all defaults and do not prompt for options during an init') { |d| + @options[:use_defaults] = d + } opts.separator('') opts.separator('Database Options:') @@ -997,6 +1005,7 @@ end def should_delete + return true if @options[:use_defaults] ask_yn("Would you like to delete your existing data and configurations?") end From b3b7d5205bd241ee519b86cdcb5b0c4f4ca7eeeb Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Wed, 23 Jan 2019 16:08:15 -0600 Subject: [PATCH 10/16] Don't regenerate SSL unless the user said to so do --- msfdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msfdb b/msfdb index cf286466f7..8a1a7f8237 100755 --- a/msfdb +++ b/msfdb @@ -475,7 +475,7 @@ def init_web_service @msf_ws_pass = @options[:ws_pass] end - if should_generate_web_service_ssl + if should_generate_web_service_ssl && @options[:delete_existing_data] generate_web_service_ssl(key: @options[:ssl_key], cert: @options[:ssl_cert]) end From 424c2492005957fb616caa6f18fdf732a9592631 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Wed, 23 Jan 2019 17:21:07 -0600 Subject: [PATCH 11/16] Use rex for color output --- msfdb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/msfdb b/msfdb index 8a1a7f8237..647be92044 100755 --- a/msfdb +++ b/msfdb @@ -13,6 +13,8 @@ require 'securerandom' require 'uri' require 'yaml' +include Rex::Text::Color + msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) @@ -24,7 +26,6 @@ $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] require 'msf/base/config' require 'msf/util/helper' - @script_name = File.basename(__FILE__) @framework = File.expand_path(File.dirname(__FILE__)) @@ -70,30 +71,35 @@ require 'msf/util/helper' delete_existing_data: nil } +def supports_color? + return true if Rex::Compat.is_windows + term = Rex::Compat.getenv('TERM') + term and term.match(/(?:vt10[03]|xterm(?:-color)?|linux|screen|rxvt)/i) != nil +end class String def bold - "\e[1m#{self}\e[0m" + substitute_colors("%bld#{self}%clr") end def underline - "\e[4m#{self}\e[0m" + substitute_colors("%und#{self}%clr") end def red - "\e[91m#{self}\e[0m" + substitute_colors("%red#{self}%clr") end def green - "\e[92m#{self}\e[0m" + substitute_colors("%grn#{self}%clr") end def blue - "\e[34m#{self}\e[0m" + substitute_colors("%blu#{self}%clr") end def cyan - "\e[36m#{self}\e[0m" + substitute_colors("%cya#{self}%clr") end end From 1fe205af128b9715f7e851a05a6f7ea32913f1cc Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Thu, 24 Jan 2019 13:45:44 -0600 Subject: [PATCH 12/16] Add command line option for data service name --- msfdb | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/msfdb b/msfdb index 647be92044..a1f09b89f9 100755 --- a/msfdb +++ b/msfdb @@ -67,6 +67,8 @@ require 'msf/util/helper' retry_max: 10, retry_delay: 5.0, ws_user: nil, + add_data_service: true, + data_service_name: nil, use_defaults: false, delete_existing_data: nil } @@ -699,19 +701,29 @@ def output_web_service_information end def persist_data_service - if @options[:use_defaults] || ask_yn('Add data service connection to local msfconsole and persist as default?') - data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" - data_service_name = ask_value('Data service connection name?', data_service_name) unless @options[:use_defaults] - # execute msfconsole commands to add and persist the data service connection - connect_cmd = get_db_connect_command(name: data_service_name) - cmd = "msfconsole -qx \"#{connect_cmd}; db_save; exit\"" - if run_cmd(cmd) != 0 - # attempt to execute msfconsole in the current working directory - if run_cmd(cmd, env: {'PATH' => ".:#{ENV["PATH"]}"}) != 0 - puts 'Failed to run msfconsole and persist the data service connection' - end + default_data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" + if !@options[:add_data_service] + return + elsif !@options[:data_service_name].nil? + data_service_name = @options[:data_service_name] + elsif @options[:use_defaults] + data_service_name = default_data_service_name + elsif ask_yn('Add data service connection to local msfconsole and persist as default?') + data_service_name = ask_value('Data service connection name?', default_data_service_name) + else + return + end + + # execute msfconsole commands to add and persist the data service connection + connect_cmd = get_db_connect_command(name: data_service_name) + cmd = "msfconsole -qx \"#{connect_cmd}; db_save; exit\"" + if run_cmd(cmd) != 0 + # attempt to execute msfconsole in the current working directory + if run_cmd(cmd, env: {'PATH' => ".:#{ENV["PATH"]}"}) != 0 + puts 'Failed to run msfconsole and persist the data service connection' end end + end def get_db_connect_command(name: nil) @@ -937,6 +949,14 @@ def parse_args(args) @options[:ws_pass] = p } + opts.on('--[no-]msf-data-service NAME', 'Local msfconsole data service connection name') { |n| + if !n + @options[:add_data_service] = false + else + @options[:data_service_name] = n + end + } + opts.separator('') opts.separator(subtext) end From b497b88834e6a1cec30aea72278f889ae3778dc8 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Thu, 24 Jan 2019 13:55:05 -0600 Subject: [PATCH 13/16] Update delete_existing_data to always be a boolean --- msfdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msfdb b/msfdb index a1f09b89f9..e598c63845 100755 --- a/msfdb +++ b/msfdb @@ -70,7 +70,7 @@ require 'msf/util/helper' add_data_service: true, data_service_name: nil, use_defaults: false, - delete_existing_data: nil + delete_existing_data: false } def supports_color? From 7c108a141db5b2c423e261e46ca16dba0d20e8bf Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Fri, 25 Jan 2019 12:08:28 -0600 Subject: [PATCH 14/16] Don't prompt to add a data service connection --- msfdb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/msfdb b/msfdb index e598c63845..301d1608fe 100755 --- a/msfdb +++ b/msfdb @@ -701,17 +701,11 @@ def output_web_service_information end def persist_data_service - default_data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" + data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" if !@options[:add_data_service] return elsif !@options[:data_service_name].nil? data_service_name = @options[:data_service_name] - elsif @options[:use_defaults] - data_service_name = default_data_service_name - elsif ask_yn('Add data service connection to local msfconsole and persist as default?') - data_service_name = ask_value('Data service connection name?', default_data_service_name) - else - return end # execute msfconsole commands to add and persist the data service connection From e6dc39751681424a4fe003077fd722ce13236588 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Fri, 25 Jan 2019 13:15:45 -0600 Subject: [PATCH 15/16] Actually delete files on a web service delete --- msfdb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/msfdb b/msfdb index 301d1608fe..0d9a44d33e 100755 --- a/msfdb +++ b/msfdb @@ -70,7 +70,7 @@ require 'msf/util/helper' add_data_service: true, data_service_name: nil, use_defaults: false, - delete_existing_data: false + delete_existing_data: true } def supports_color? @@ -575,6 +575,10 @@ end def delete_web_service stop_web_service + + File.delete(@ws_pid) if web_service_pid_status == WebServicePIDStatus::INACTIVE + File.delete(@options[:ssl_key]) if File.file?(@options[:ssl_key]) + File.delete(@options[:ssl_cert]) if File.file?(@options[:ssl_cert]) end def reinit_web_service From 6802bee9115c9a979c4329b3714ad8d90f0d9f35 Mon Sep 17 00:00:00 2001 From: Erin Bleiweiss Date: Fri, 25 Jan 2019 14:42:27 -0600 Subject: [PATCH 16/16] Update data service configuration message --- msfdb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/msfdb b/msfdb index 0d9a44d33e..2a5878d058 100755 --- a/msfdb +++ b/msfdb @@ -694,7 +694,14 @@ end def output_web_service_information puts "\n\n" puts 'MSF web service configuration complete' - puts 'Connect to the data service in msfconsole using the command:' + if @options[:add_data_service] + data_service_name = @options[:data_service_name] || "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" + puts "The web service has been configured as your default data service in msfconsole with the name \"#{data_service_name}\"" + else + puts "No data service has been configured in msfconsole." + end + puts '' + puts 'If needed, manually reconnect to the data service in msfconsole using the command:' puts "#{get_db_connect_command}" puts '' puts 'The username and password are credentials for the API account:'