Add MSSQL session Type
This commit is contained in:
parent
5975d668f6
commit
2c60780dc0
|
@ -47,7 +47,16 @@ module Metasploit
|
|||
# @return [Boolean] Whether to use Windows Authentication instead of SQL Server Auth.
|
||||
attr_accessor :windows_authentication
|
||||
|
||||
# @!attribute use_client_as_proof
|
||||
# @return [Boolean] If a login is successful and this attribute is true - an MSSQL::Client instance is used as proof
|
||||
attr_accessor :use_client_as_proof
|
||||
|
||||
# @!attribute max_send_size
|
||||
# @return [Integer] The max size of the data to encapsulate in a single packet
|
||||
attr_accessor :max_send_size
|
||||
|
||||
# @!attribute send_delay
|
||||
# @return [Integer] The delay between sending packets
|
||||
attr_accessor :send_delay
|
||||
|
||||
validates :windows_authentication,
|
||||
|
@ -71,6 +80,11 @@ module Metasploit
|
|||
client = Rex::Proto::MSSQL::Client.new(framework_module, framework, host, port)
|
||||
if client.mssql_login(credential.public, credential.private, '', credential.realm)
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
if use_client_as_proof
|
||||
result_options[:proof] = client
|
||||
else
|
||||
client.disconnect
|
||||
end
|
||||
else
|
||||
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
|
@ -81,8 +95,6 @@ module Metasploit
|
|||
elog(e)
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
result_options[:proof] = e
|
||||
ensure
|
||||
client.disconnect
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
|
|
|
@ -228,6 +228,13 @@ class Config < Hash
|
|||
self.new.postgresql_session_history
|
||||
end
|
||||
|
||||
# Returns the full path to the MSSQL session history file.
|
||||
#
|
||||
# @return [String] path to the history file.
|
||||
def self.mssql_session_history
|
||||
self.new.mssql_session_history
|
||||
end
|
||||
|
||||
# Returns the full path to the MySQL session history file.
|
||||
#
|
||||
# @return [String] path to the history file.
|
||||
|
@ -352,6 +359,10 @@ class Config < Hash
|
|||
config_directory + FileSep + "mysql_session_history"
|
||||
end
|
||||
|
||||
def mssql_session_history
|
||||
config_directory + FileSep + "mssql_session_history"
|
||||
end
|
||||
|
||||
def pry_history
|
||||
config_directory + FileSep + "pry_history"
|
||||
end
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
# -*- coding:binary -*-
|
||||
|
||||
require 'rex/post/mssql'
|
||||
|
||||
class Msf::Sessions::MSSQL
|
||||
|
||||
include Msf::Session::Basic
|
||||
include Msf::Sessions::Scriptable
|
||||
|
||||
# @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
|
||||
attr_accessor :port
|
||||
attr_reader :framework
|
||||
|
||||
def initialize(rstream, opts = {})
|
||||
@client = opts.fetch(:client)
|
||||
self.console = Rex::Post::MSSQL::Ui::Console.new(self, opts)
|
||||
|
||||
super(rstream, opts)
|
||||
end
|
||||
|
||||
def bootstrap(datastore = {}, handler = nil)
|
||||
session = self
|
||||
session.init_ui(user_input, user_output)
|
||||
|
||||
@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
|
||||
'MSSQL'
|
||||
end
|
||||
|
||||
def self.can_cleanup_files
|
||||
false
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the session description.
|
||||
#
|
||||
def desc
|
||||
'MSSQL'
|
||||
end
|
||||
|
||||
def address
|
||||
return @address if @address
|
||||
|
||||
@address, @port = client.sock.peerinfo.split(':')
|
||||
@address
|
||||
end
|
||||
|
||||
def port
|
||||
return @port if @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
|
|
@ -25,6 +25,7 @@ module Msf
|
|||
SMB_SESSION_TYPE = 'smb_session_type'
|
||||
POSTGRESQL_SESSION_TYPE = 'postgresql_session_type'
|
||||
MYSQL_SESSION_TYPE = 'mysql_session_type'
|
||||
MSSQL_SESSION_TYPE = 'mssql_session_type'
|
||||
DEFAULTS = [
|
||||
{
|
||||
name: WRAPPED_TABLES,
|
||||
|
@ -83,6 +84,12 @@ module Msf
|
|||
requires_restart: true,
|
||||
default_value: false
|
||||
}.freeze,
|
||||
{
|
||||
name: MSSQL_SESSION_TYPE,
|
||||
description: 'When enabled will allow for the creation/use of mssql sessions',
|
||||
requires_restart: true,
|
||||
default_value: false
|
||||
}.freeze,
|
||||
{
|
||||
name: DNS_FEATURE,
|
||||
description: 'When enabled, allows configuration of DNS resolution behaviour in Metasploit',
|
||||
|
|
|
@ -37,8 +37,18 @@ module Msf::OptionalSession
|
|||
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
|
||||
Msf::OptString.new('DATABASE', [ false, 'The database to authenticate against', 'postgres']),
|
||||
Msf::OptString.new('USERNAME', [ false, 'The username to authenticate as', 'postgres']),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
if framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)
|
||||
register_options(
|
||||
[
|
||||
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
|
||||
Msf::OptString.new('DATABASE', [ false, 'The database to authenticate against', 'MSSQL']),
|
||||
Msf::OptString.new('USERNAME', [ false, 'The username to authenticate as', 'MSSQL']),
|
||||
Msf::Opt::RHOST(nil, false),
|
||||
Msf::Opt::RPORT(nil, false)
|
||||
Msf::Opt::RPORT(1433, false)
|
||||
]
|
||||
)
|
||||
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
|
||||
|
@ -46,7 +56,7 @@ module Msf::OptionalSession
|
|||
end
|
||||
|
||||
def session
|
||||
return nil unless (framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE) || framework.features.enabled?(Msf::FeatureManager::POSTGRESQL_SESSION_TYPE) || framework.features.enabled?(Msf::FeatureManager::MYSQL_SESSION_TYPE))
|
||||
return nil unless (framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE) || framework.features.enabled?(Msf::FeatureManager::POSTGRESQL_SESSION_TYPE) || framework.features.enabled?(Msf::FeatureManager::MYSQL_SESSION_TYPE) || framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE))
|
||||
|
||||
super
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'rex/post/meterpreter'
|
|||
require 'rex/post/smb'
|
||||
require 'rex/post/postgresql'
|
||||
require 'rex/post/mysql'
|
||||
require 'rex/post/mssql'
|
||||
|
||||
module Rex::Post
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/mssql/ui'
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/mssql/ui/console'
|
|
@ -0,0 +1,147 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module MSSQL
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# This class provides a shell driven interface to the MSSQL client API.
|
||||
#
|
||||
###
|
||||
class Console
|
||||
include Rex::Ui::Text::DispatcherShell
|
||||
|
||||
# Dispatchers
|
||||
require 'rex/post/mssql/ui/console/command_dispatcher'
|
||||
require 'rex/post/mssql/ui/console/command_dispatcher/core'
|
||||
require 'rex/post/mssql/ui/console/command_dispatcher/client'
|
||||
require 'rex/post/mssql/ui/console/command_dispatcher/modules'
|
||||
|
||||
#
|
||||
# Initialize the MSSQL console.
|
||||
#
|
||||
# @param [Msf::Sessions::MSSQL] session
|
||||
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"
|
||||
history_manager = Msf::Config.mssql_session_history
|
||||
super(prompt, '>', history_manager, nil, :mssql)
|
||||
|
||||
# Queued commands array
|
||||
self.commands = []
|
||||
|
||||
# Point the input/output handles elsewhere
|
||||
reset_ui
|
||||
|
||||
enstack_dispatcher(::Rex::Post::MSSQL::Ui::Console::CommandDispatcher::Core)
|
||||
enstack_dispatcher(::Rex::Post::MSSQL::Ui::Console::CommandDispatcher::Client)
|
||||
enstack_dispatcher(::Rex::Post::MSSQL::Ui::Console::CommandDispatcher::Modules)
|
||||
|
||||
# Set up logging to whatever logsink 'core' is using
|
||||
if ! $dispatcher['mssql']
|
||||
$dispatcher['mssql'] = $dispatcher['core']
|
||||
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.
|
||||
#
|
||||
# @param [Object] msg
|
||||
# @return [Object]
|
||||
def log_error(msg)
|
||||
print_error(msg)
|
||||
|
||||
elog(msg, 'MSSQL')
|
||||
|
||||
dlog("Call stack:\n#{$@.join("\n")}", 'mssql')
|
||||
end
|
||||
|
||||
# @return [Msf::Sessions::MSSQL]
|
||||
attr_reader :session
|
||||
|
||||
# @return [MSSQL::Client]
|
||||
attr_reader :client
|
||||
|
||||
# @return [String]
|
||||
attr_accessor :cwd
|
||||
|
||||
# @param [Object] val
|
||||
# @return [String]
|
||||
def format_prompt(val)
|
||||
self.cwd ||= ''
|
||||
prompt = "%undMSSQL @ #{client.sock.peerinfo} (#{@cwd})%clr > "
|
||||
substitute_colors(prompt, true)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :session, :client # :nodoc:
|
||||
attr_accessor :commands # :nodoc:
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,113 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/ui/text/dispatcher_shell'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module MSSQL
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# Base class for all command dispatchers within the MSSQL 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::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
|
||||
|
||||
#
|
||||
# Return the subdir of the `documentation/` directory that should be used
|
||||
# to find usage documentation
|
||||
#
|
||||
# @return [String]
|
||||
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')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,147 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'pathname'
|
||||
require 'reline'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module MSSQL
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# Core MSSQL client commands
|
||||
#
|
||||
###
|
||||
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
|
||||
|
||||
# @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) || []
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/mssql'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module MSSQL
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# Core MSSQL client commands
|
||||
#
|
||||
###
|
||||
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
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,95 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'pathname'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module MSSQL
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# MSSQL client commands for running modules
|
||||
#
|
||||
###
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -49,7 +49,7 @@ module ClientMixin
|
|||
|
||||
tbl = Rex::Text::Table.new(
|
||||
'Indent' => 1,
|
||||
'Header' => "",
|
||||
'Header' => "Response",
|
||||
'Columns' => info[:colnames],
|
||||
'SortIndex' => -1
|
||||
)
|
||||
|
|
|
@ -346,7 +346,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
column_data = result[:rows]
|
||||
print_good("Successfully connected to #{rhost}:#{rport}")
|
||||
rescue
|
||||
print_error("Failed to connect to #{rhost}:#{rport}.")
|
||||
print_error("Failed to connect to #{rhost}:#{rport}")
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
require 'metasploit/framework/credential_collection'
|
||||
require 'metasploit/framework/login_scanner/mssql'
|
||||
require 'rex/proto/mssql/client'
|
||||
require 'rex/post/mssql'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::MSSQL
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
|
||||
include Msf::Auxiliary::CommandShell
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize
|
||||
|
@ -35,14 +36,36 @@ class MetasploitModule < Msf::Auxiliary
|
|||
OptBool.new('TDSENCRYPTION', [ true, 'Use TLS/SSL for TDS data "Force Encryption"', true]),
|
||||
])
|
||||
|
||||
deregister_options('PASSWORD_SPRAY')
|
||||
options_to_deregister = %w[PASSWORD_SPRAY]
|
||||
if framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)
|
||||
add_info('New in Metasploit 6.4 - The %grnCreateSession%clr option within this module can open an interactive session')
|
||||
else
|
||||
options_to_deregister << 'CreateSession'
|
||||
end
|
||||
deregister_options(*options_to_deregister)
|
||||
end
|
||||
|
||||
def create_session?
|
||||
if framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)
|
||||
datastore['CreateSession']
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
print_status("#{rhost}:#{rport} - MSSQL - Starting authentication scanner.")
|
||||
|
||||
if datastore['TDSENCRYPTION']
|
||||
print_status("Manually enabled TLS/SSL to encrypt TDS payloads.")
|
||||
if create_session?
|
||||
raise Msf::OptionValidateError.new(
|
||||
{
|
||||
'TDSENCRYPTION' => "Cannot create sessions when encryption is enabled. See https://github.com/rapid7/metasploit-framework/issues/18745 to vote for this feature"
|
||||
}
|
||||
)
|
||||
else
|
||||
print_status("TDS Encryption enabled")
|
||||
end
|
||||
end
|
||||
|
||||
cred_collection = build_credential_collection(
|
||||
|
@ -68,6 +91,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
tdsencryption: datastore['TDSENCRYPTION'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
use_client_as_proof: create_session?,
|
||||
ssl: datastore['SSL'],
|
||||
ssl_version: datastore['SSLVersion'],
|
||||
ssl_verify_mode: datastore['SSLVerifyMode'],
|
||||
|
@ -86,12 +110,37 @@ class MetasploitModule < Msf::Auxiliary
|
|||
credential_core = create_credential(credential_data)
|
||||
credential_data[:core] = credential_core
|
||||
create_credential_login(credential_data)
|
||||
|
||||
print_good "#{ip}:#{rport} - Login Successful: #{result.credential}"
|
||||
|
||||
if create_session?
|
||||
begin
|
||||
mssql_client = result.proof
|
||||
session_setup(result, mssql_client)
|
||||
rescue ::StandardError => e
|
||||
elog('Failed: ', error: e)
|
||||
print_error(e)
|
||||
result.proof.conn.close if result.proof&.conn
|
||||
end
|
||||
end
|
||||
else
|
||||
invalidate_login(credential_data)
|
||||
vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def session_setup(result, client)
|
||||
return unless (result && client)
|
||||
rstream = client.sock
|
||||
my_session = Msf::Sessions::MSSQL.new(rstream, { client: client }) # is cwd right?
|
||||
merging = {
|
||||
'USERPASS_FILE' => nil,
|
||||
'USER_FILE' => nil,
|
||||
'PASS_FILE' => nil,
|
||||
'USERNAME' => result.credential.public,
|
||||
'PASSWORD' => result.credential.private
|
||||
}
|
||||
|
||||
start_session(self, nil, merging, false, my_session.rstream, my_session)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require 'rex/post/mssql/ui/console/command_dispatcher/core'
|
||||
|
||||
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(: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']]}
|
||||
end
|
||||
let(:log_source) { "session_#{name}" }
|
||||
let(:type) { 'MSSQL' }
|
||||
let(:description) { 'MSSQL' }
|
||||
let(:can_cleanup_files) { false }
|
||||
let(:address) { '192.0.2.1' }
|
||||
let(:port) { '1433' }
|
||||
let(:peer_info) { "#{address}:#{port}" }
|
||||
let(:console) do
|
||||
console = Rex::Post::MSSQL::Ui::Console.new(session)
|
||||
console.disable_output = true
|
||||
console
|
||||
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).and_return(query_result)
|
||||
allow(rstream).to receive(:peerinfo).and_return(peer_info)
|
||||
end
|
||||
|
||||
subject(:session) do
|
||||
mssql_session = described_class.new(rstream, opts)
|
||||
mssql_session.user_input = user_input
|
||||
mssql_session.user_output = user_output
|
||||
mssql_session.name = name
|
||||
mssql_session
|
||||
end
|
||||
|
||||
describe '.type' do
|
||||
it 'should have the correct type' do
|
||||
expect(described_class.type).to eq(type)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.can_cleanup_files' do
|
||||
it 'should be able to cleanup files' do
|
||||
expect(described_class.can_cleanup_files).to eq(can_cleanup_files)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#desc' do
|
||||
it 'should have the correct description' do
|
||||
expect(subject.desc).to eq(description)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#type' do
|
||||
it 'should have the correct type' do
|
||||
expect(subject.type).to eq(type)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
context 'without a client' do
|
||||
let(:opts) { {} }
|
||||
|
||||
it 'raises a KeyError' do
|
||||
expect { subject }.to raise_exception(KeyError)
|
||||
end
|
||||
end
|
||||
context 'with a client' do
|
||||
it 'does not raise an exception' do
|
||||
expect { subject }.not_to raise_exception
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates a new console' do
|
||||
expect(subject.console).to be_a(console_class)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bootstrap' do
|
||||
subject { session.bootstrap }
|
||||
|
||||
it 'keeps the sessions user input' do
|
||||
expect { subject }.not_to change(session, :user_input).from(user_input)
|
||||
end
|
||||
|
||||
it 'keeps the sessions user output' do
|
||||
expect { subject }.not_to change(session, :user_output).from(user_output)
|
||||
end
|
||||
|
||||
it 'sets the console input' do
|
||||
expect { subject }.to change(session.console, :input).to(user_input)
|
||||
end
|
||||
|
||||
it 'sets the console output' do
|
||||
expect { subject }.to change(session.console, :output).to(user_output)
|
||||
end
|
||||
|
||||
it 'sets the log source' do
|
||||
expect { subject }.to change(session.console, :log_source).to(log_source)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reset_ui' do
|
||||
before(:each) do
|
||||
session.bootstrap
|
||||
end
|
||||
|
||||
subject { session.reset_ui }
|
||||
|
||||
it 'keeps the sessions user input' do
|
||||
expect { subject }.not_to change(session, :user_input).from(user_input)
|
||||
end
|
||||
|
||||
it 'keeps the sessions user output' do
|
||||
expect { subject }.not_to change(session, :user_output).from(user_output)
|
||||
end
|
||||
|
||||
it 'resets the console input' do
|
||||
expect { subject }.to change(session.console, :input).from(user_input).to(nil)
|
||||
end
|
||||
|
||||
it 'resets the console output' do
|
||||
expect { subject }.to change(session.console, :output).from(user_output).to(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#exit' do
|
||||
subject { session.exit }
|
||||
|
||||
it 'exits the session' do
|
||||
expect { subject }.to change(session.console, :stopped?).from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#address' do
|
||||
subject { session.address }
|
||||
|
||||
it { is_expected.to eq(address) }
|
||||
end
|
||||
|
||||
describe '#port' do
|
||||
subject { session.port }
|
||||
|
||||
it { is_expected.to eq(port) }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require 'rex/post/mssql/ui/console/command_dispatcher/core'
|
||||
|
||||
RSpec.describe Rex::Post::MSSQL::Ui::Console::CommandDispatcher::Core do
|
||||
let(:rstream) { instance_double(::Rex::Socket) }
|
||||
let(:client) { instance_double(Rex::Proto::MSSQL::Client) }
|
||||
let(:session) { Msf::Sessions::MSSQL.new(nil, { client: client, cwd: 'mssql' }) }
|
||||
let(:address) { '192.0.2.1' }
|
||||
let(:port) { '1433' }
|
||||
let(:peer_info) { "#{address}:#{port}" }
|
||||
let(:console) do
|
||||
console = Rex::Post::MSSQL::Ui::Console.new(session)
|
||||
console.disable_output = true
|
||||
console
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
allow(client).to receive(:sock).and_return(rstream)
|
||||
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)
|
||||
allow(session).to receive(:name).and_return('test client name')
|
||||
allow(session).to receive(:sid).and_return('test client sid')
|
||||
end
|
||||
|
||||
subject(:command_dispatcher) { described_class.new(session.console) }
|
||||
|
||||
it_behaves_like 'session command dispatcher'
|
||||
end
|
Loading…
Reference in New Issue