Skip to content

Commit

Permalink
Add MSSQL session type
Browse files Browse the repository at this point in the history
  • Loading branch information
zgoldman-r7 committed Feb 2, 2024
1 parent 558fc6e commit 0c65b9e
Show file tree
Hide file tree
Showing 36 changed files with 1,017 additions and 68 deletions.
9 changes: 7 additions & 2 deletions lib/metasploit/framework/login_scanner/mssql.rb
Expand Up @@ -47,6 +47,8 @@ class MSSQL
# @return [Boolean] Whether to use Windows Authentication instead of SQL Server Auth.
attr_accessor :windows_authentication

attr_accessor :use_client_as_proof

attr_accessor :max_send_size
attr_accessor :send_delay

Expand All @@ -71,6 +73,11 @@ def attempt_login(credential)
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 # replacing the ensure so the client doesn't disconnect on login - is this right?
end
else
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
Expand All @@ -81,8 +88,6 @@ def attempt_login(credential)
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)
Expand Down
3 changes: 1 addition & 2 deletions lib/metasploit/framework/tcp/client.rb
Expand Up @@ -73,7 +73,6 @@ module Client
# @see Rex::Socket::Tcp
# @see Rex::Socket::Tcp.create
def connect(global = true, opts={})

dossl = false
if(opts.has_key?('SSL'))
dossl = opts['SSL']
Expand All @@ -93,7 +92,7 @@ def connect(global = true, opts={})
'SSLCipher' => opts['SSLCipher'] || ssl_cipher,
'Proxies' => proxies,
'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module }
)
# enable evasions on this socket
set_tcp_evasions(nsock)
Expand Down
7 changes: 7 additions & 0 deletions lib/msf/base/config.rb
Expand Up @@ -227,6 +227,9 @@ def self.smb_session_history
def self.postgresql_session_history
self.new.postgresql_session_history
end
def self.mssql_session_history
self.new.mssql_session_history
end

# Returns the full path to the MySQL session history file.
#
Expand Down Expand Up @@ -352,6 +355,10 @@ def mysql_session_history
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
Expand Down
143 changes: 143 additions & 0 deletions lib/msf/base/sessions/mssql.rb
@@ -0,0 +1,143 @@
# -*- 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
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 #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")
session.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
11 changes: 3 additions & 8 deletions lib/msf/core/exploit/remote/mssql.rb
Expand Up @@ -72,6 +72,9 @@ def mssql_ping(timeout=5)
return mssql_ping_parse(resp)
end

#
# Parse a 'ping' response and format as a hash
#
def mssql_ping_parse(data)
res = []
var = nil
Expand Down Expand Up @@ -216,13 +219,5 @@ def mssql_tds_encrypt(pass)
# Convert to unicode, swap 4 bits both ways, xor with 0xa5
Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
end

#
# Encrypt a password according to the TDS protocol (encode)
#
def mssql_tds_encrypt(pass)
# Convert to unicode, swap 4 bits both ways, xor with 0xa5
Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
end
end
end
7 changes: 7 additions & 0 deletions lib/msf/core/feature_manager.rb
Expand Up @@ -25,6 +25,7 @@ class FeatureManager
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,
Expand Down Expand Up @@ -83,6 +84,12 @@ class FeatureManager
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',
Expand Down
12 changes: 11 additions & 1 deletion lib/msf/core/optional_session.rb
Expand Up @@ -37,6 +37,16 @@ def initialize(info = {})
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)
]
Expand All @@ -46,7 +56,7 @@ def initialize(info = {})
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
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/core/post/common.rb
Expand Up @@ -30,7 +30,7 @@ def rhost
session.sock.peerhost
when 'shell', 'powershell'
session.session_host
when 'postgresql', 'mysql'
when 'postgresql', 'mysql', 'mssql'
session.address
end
rescue
Expand All @@ -45,7 +45,7 @@ def rport
session.sock.peerport
when 'shell', 'powershell'
session.session_port
when 'postgresql', 'mysql'
when 'postgresql', 'mysql', 'mssql'
session.port
end
rescue
Expand Down
1 change: 1 addition & 0 deletions lib/rex/post.rb
Expand Up @@ -5,6 +5,7 @@
require 'rex/post/smb'
require 'rex/post/postgresql'
require 'rex/post/mysql'
require 'rex/post/mssql'

module Rex::Post

Expand Down
3 changes: 3 additions & 0 deletions lib/rex/post/mssql.rb
@@ -0,0 +1,3 @@
# -*- coding: binary -*-

require 'rex/post/mssql/ui'
3 changes: 3 additions & 0 deletions lib/rex/post/mssql/ui.rb
@@ -0,0 +1,3 @@
# -*- coding: binary -*-

require 'rex/post/mssql/ui/console'

0 comments on commit 0c65b9e

Please sign in to comment.