From 3897b49ca6fe4e6c9ac36420e4d754afec9fa3f4 Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Thu, 29 Feb 2024 22:16:57 -0600 Subject: [PATCH 1/7] add mssql_version module --- lib/msf/core/exploit/remote/mssql.rb | 4 + lib/rex/proto/mssql/client_mixin.rb | 80 +++++++++++++++++++ .../auxiliary/scanner/mssql/mssql_version.rb | 26 ++++++ 3 files changed, 110 insertions(+) create mode 100644 modules/auxiliary/scanner/mssql/mssql_version.rb diff --git a/lib/msf/core/exploit/remote/mssql.rb b/lib/msf/core/exploit/remote/mssql.rb index 41088ce0f8c0..fe037495b93a 100644 --- a/lib/msf/core/exploit/remote/mssql.rb +++ b/lib/msf/core/exploit/remote/mssql.rb @@ -53,6 +53,10 @@ def set_session(client) @mssql_client = client end + def mssql_get_version + @mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT']) + @mssql_client.mssql_get_version + end # # This method sends a UDP query packet to the server and # parses out the reply packet into a hash diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index 684b9c529e9c..98d3d816f5e3 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -86,6 +86,86 @@ def mssql_print_reply(info) end end + def mssql_get_version + disconnect if self.sock + connect + pkt = "" + pkt_hdr = "" + pkt_data_token = "" + pkt_data = "" + + + pkt_hdr = [ + TYPE_PRE_LOGIN_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x00, # PacketID + 0x00 #Window + ] + + version = [0x55010008, 0x0000].pack("Vv") + + # if manually set, we will honour + if tdsencryption == true + encryption = ENCRYPT_ON + else + encryption = ENCRYPT_NOT_SUP + end + + instoptdata = "MSSQLServer\0" + + threadid = "\0\0" + Rex::Text.rand_text(2) + + idx = 21 # size of pkt_data_token + pkt_data_token << [ + 0x00, # Token 0 type Version + idx , # VersionOffset + version.length, # VersionLength + + 0x01, # Token 1 type Encryption + idx = idx + version.length, # EncryptionOffset + 0x01, # EncryptionLength + + 0x02, # Token 2 type InstOpt + idx = idx + 1, # InstOptOffset + instoptdata.length, # InstOptLength + + 0x03, # Token 3 type Threadid + idx + instoptdata.length, # ThreadIdOffset + 0x04, # ThreadIdLength + + 0xFF + ].pack("CnnCnnCnnCnnC") + + pkt_data << pkt_data_token + pkt_data << version + pkt_data << encryption + pkt_data << instoptdata + pkt_data << threadid + + pkt_hdr[2] = pkt_data.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + pkt_data + + resp = mssql_send_recv(pkt) + while resp + token = resp.slice!(0, 1) + if token.unpack('C')[0] == 255 + major = resp.slice!(0, 1).unpack('C')[0] + minor = resp.slice!(0, 1).unpack('C')[0] + build = resp.slice!(0, 2).unpack('n')[0] + break + end + end + + if major && minor && build + return "#{major}.#{minor}.#{build}" + else + return nil + end + end + def mssql_send_recv(req, timeout=15, check_status = true) sock.put(req) diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb new file mode 100644 index 000000000000..b2cfa8d28fc4 --- /dev/null +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -0,0 +1,26 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::MSSQL + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'MSSQL Version Utility', + 'Description' => 'This module simply queries the MSSQL instance for information.', + 'Author' => 'MC', + 'License' => MSF_LICENSE + ) + end + + def run_host(ip) + version = mssql_get_version + if version && !version.empty? + print_status("SQL Server for #{ip}:") + print_good("Version: #{version}") + end + end +end From 6fd8c8b903ad3245ccc13fe728417069d2791169 Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Mon, 4 Mar 2024 11:44:04 -0600 Subject: [PATCH 2/7] add session support, dry out code, update descriptions to be more specific --- lib/rex/proto/mssql/client.rb | 59 +------------------ lib/rex/proto/mssql/client_mixin.rb | 12 +++- modules/auxiliary/scanner/mssql/mssql_ping.rb | 2 +- .../auxiliary/scanner/mssql/mssql_version.rb | 10 +++- 4 files changed, 18 insertions(+), 65 deletions(-) diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index d4beb01ee6df..6647715a67ad 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -477,64 +477,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') #this method send a prelogin packet and check if encryption is off # def mssql_prelogin(enc_error=false) - pkt = "" - pkt_hdr = "" - pkt_data_token = "" - pkt_data = "" - - - pkt_hdr = [ - TYPE_PRE_LOGIN_MESSAGE, #type - STATUS_END_OF_MESSAGE, #status - 0x0000, #length - 0x0000, # SPID - 0x00, # PacketID - 0x00 #Window - ] - - version = [0x55010008, 0x0000].pack("Vv") - - # if manually set, we will honour - if tdsencryption == true - encryption = ENCRYPT_ON - else - encryption = ENCRYPT_NOT_SUP - end - - instoptdata = "MSSQLServer\0" - - threadid = "\0\0" + Rex::Text.rand_text(2) - - idx = 21 # size of pkt_data_token - pkt_data_token << [ - 0x00, # Token 0 type Version - idx , # VersionOffset - version.length, # VersionLength - - 0x01, # Token 1 type Encryption - idx = idx + version.length, # EncryptionOffset - 0x01, # EncryptionLength - - 0x02, # Token 2 type InstOpt - idx = idx + 1, # InstOptOffset - instoptdata.length, # InstOptLength - - 0x03, # Token 3 type Threadid - idx + instoptdata.length, # ThreadIdOffset - 0x04, # ThreadIdLength - - 0xFF - ].pack("CnnCnnCnnCnnC") - - pkt_data << pkt_data_token - pkt_data << version - pkt_data << encryption - pkt_data << instoptdata - pkt_data << threadid - - pkt_hdr[2] = pkt_data.length + 8 - - pkt = pkt_hdr.pack("CCnnCC") + pkt_data + pkt = mssql_prelogin_packet resp = mssql_send_recv(pkt) diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index 98d3d816f5e3..fea20cb32166 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -86,9 +86,7 @@ def mssql_print_reply(info) end end - def mssql_get_version - disconnect if self.sock - connect + def mssql_prelogin_packet pkt = "" pkt_hdr = "" pkt_data_token = "" @@ -147,6 +145,14 @@ def mssql_get_version pkt_hdr[2] = pkt_data.length + 8 pkt = pkt_hdr.pack("CCnnCC") + pkt_data + pkt + end + + def mssql_get_version + disconnect if self.sock + connect + + pkt = mssql_prelogin_packet resp = mssql_send_recv(pkt) while resp diff --git a/modules/auxiliary/scanner/mssql/mssql_ping.rb b/modules/auxiliary/scanner/mssql/mssql_ping.rb index 55140a66729f..a371720020c3 100644 --- a/modules/auxiliary/scanner/mssql/mssql_ping.rb +++ b/modules/auxiliary/scanner/mssql/mssql_ping.rb @@ -11,7 +11,7 @@ class MetasploitModule < Msf::Auxiliary def initialize super( 'Name' => 'MSSQL Ping Utility', - 'Description' => 'This module simply queries the MSSQL instance for information.', + 'Description' => 'This module simply queries the MSSQL Browser service for server information.', 'Author' => 'MC', 'License' => MSF_LICENSE ) diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb index b2cfa8d28fc4..1d629514a86c 100644 --- a/modules/auxiliary/scanner/mssql/mssql_version.rb +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -6,20 +6,24 @@ class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::MSSQL include Msf::Auxiliary::Scanner + include Msf::OptionalSession::MSSQL def initialize super( 'Name' => 'MSSQL Version Utility', - 'Description' => 'This module simply queries the MSSQL instance for information.', + 'Description' => 'This module simply queries the MSSQL instance for version information.', 'Author' => 'MC', 'License' => MSF_LICENSE ) end - def run_host(ip) + def run + if session + set_session(session.client) + end version = mssql_get_version if version && !version.empty? - print_status("SQL Server for #{ip}:") + print_status("SQL Server for #{mssql_client.address}:") print_good("Version: #{version}") end end From ae091bf17d72c50b108c6196ac09b2eb9c101c68 Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Mon, 4 Mar 2024 17:41:29 -0600 Subject: [PATCH 3/7] add encryption detection, reporting to mssql_version --- lib/rex/proto/mssql/client_mixin.rb | 40 ++++++++++++++--- .../auxiliary/scanner/mssql/mssql_version.rb | 45 ++++++++++++++++--- 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index fea20cb32166..6438d491b381 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -134,7 +134,7 @@ def mssql_prelogin_packet 0x04, # ThreadIdLength 0xFF - ].pack("CnnCnnCnnCnnC") + ].pack('CnnCnnCnnCnnC') pkt_data << pkt_data_token pkt_data << version @@ -144,7 +144,7 @@ def mssql_prelogin_packet pkt_hdr[2] = pkt_data.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + pkt_data + pkt = pkt_hdr.pack('CCnnCC') + pkt_data pkt end @@ -155,21 +155,47 @@ def mssql_get_version pkt = mssql_prelogin_packet resp = mssql_send_recv(pkt) + return unless resp + + data = {} while resp token = resp.slice!(0, 1) if token.unpack('C')[0] == 255 - major = resp.slice!(0, 1).unpack('C')[0] - minor = resp.slice!(0, 1).unpack('C')[0] + major = resp.slice!(0, 1).unpack('C')[0] + minor = resp.slice!(0, 1).unpack('C')[0] build = resp.slice!(0, 2).unpack('n')[0] break end end if major && minor && build - return "#{major}.#{minor}.#{build}" - else - return nil + build = "#{major}.#{minor}.#{build}" end + if resp + resp.slice!(0,2) + enc = resp.slice!(0,1).unpack('C')[0] + case enc + when ENCRYPT_OFF + enc_value = 'off' + when ENCRYPT_ON + enc_value = 'on' + when ENCRYPT_NOT_SUP + enc_value = 'unsupported' + when ENCRYPT_REQ + enc_value = 'required' + end + end + + if build + data['Version'] = build + end + + if enc_value + data['Encryption'] = enc_value + end + data['Status'] = 'open' + return data + end def mssql_send_recv(req, timeout=15, check_status = true) diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb index 1d629514a86c..69bf6757d6f1 100644 --- a/modules/auxiliary/scanner/mssql/mssql_version.rb +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -11,20 +11,53 @@ class MetasploitModule < Msf::Auxiliary def initialize super( 'Name' => 'MSSQL Version Utility', - 'Description' => 'This module simply queries the MSSQL instance for version information.', - 'Author' => 'MC', + 'Description' => 'Executes a TDS7 pre-login request against the MSSQL instance to query for version information.', + 'Author' => 'Zach Goldman', 'License' => MSF_LICENSE ) + + register_options([ + Opt::RPORT(1433) + ]) end def run if session set_session(session.client) end - version = mssql_get_version - if version && !version.empty? - print_status("SQL Server for #{mssql_client.address}:") - print_good("Version: #{version}") + + data = mssql_get_version + if data.nil? || data.empty? + print_error("Unable to retrieve version information for #{mssql_client.address}") + return + end + + print_status("SQL Server for #{mssql_client.address}:") + if data['Version'] && !data['Version'].empty? + print_good("Version: #{data['Version']}") + else + print_error('Unknown Version') + end + if data['Encryption'] && !data['Encryption'].empty? + print_good("Encryption is #{data['Encryption']}") + else + print_error('Unknown encryption status') end + + report_mssql_service(mssql_client.address, data) + end + + def report_mssql_service(ip, data) + mssql_info = 'Version: %s, Encryption: %s' % [ + version: data['Version'] || 'unknown', + encryption: data['Encryption'] || 'unknown' + ] + report_service( + host: ip, + port: mssql_client.port, + name: 'mssql', + info: mssql_info, + state: (data['Status'].nil? ? 'closed' : data['Status']) + ) end end From d0a714d1e8148f937bf81d16f7afaba627a10321 Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Tue, 5 Mar 2024 13:27:00 -0600 Subject: [PATCH 4/7] refactor packet parsing code --- lib/msf/core/exploit/remote/mssql.rb | 11 ++-- lib/rex/proto/mssql/client.rb | 44 ++++------------ lib/rex/proto/mssql/client_mixin.rb | 51 +++++-------------- modules/auxiliary/admin/mssql/mssql_enum.rb | 2 +- .../admin/mssql/mssql_escalate_dbowner.rb | 2 +- .../admin/mssql/mssql_escalate_execute_as.rb | 2 +- modules/auxiliary/admin/mssql/mssql_exec.rb | 2 +- .../admin/mssql/mssql_findandsampledata.rb | 2 +- modules/auxiliary/admin/mssql/mssql_idf.rb | 2 +- modules/auxiliary/admin/mssql/mssql_sql.rb | 2 +- .../auxiliary/admin/mssql/mssql_sql_file.rb | 2 +- .../auxiliary/scanner/mssql/mssql_hashdump.rb | 2 +- .../scanner/mssql/mssql_schemadump.rb | 2 +- .../auxiliary/scanner/mssql/mssql_version.rb | 24 +++++++-- .../exploits/windows/mssql/mssql_payload.rb | 12 ++--- 15 files changed, 67 insertions(+), 95 deletions(-) diff --git a/lib/msf/core/exploit/remote/mssql.rb b/lib/msf/core/exploit/remote/mssql.rb index fe037495b93a..3c6db976eb69 100644 --- a/lib/msf/core/exploit/remote/mssql.rb +++ b/lib/msf/core/exploit/remote/mssql.rb @@ -19,6 +19,11 @@ module Exploit::Remote::MSSQL attr_accessor :mssql_client + ENCRYPT_OFF = 0x00 #Encryption is available but off. + ENCRYPT_ON = 0x01 #Encryption is available and on. + ENCRYPT_NOT_SUP = 0x02 #Encryption is not available. + ENCRYPT_REQ = 0x03 #Encryption is required. + # # Creates an instance of a MSSQL exploit module. # @@ -48,15 +53,15 @@ def initialize(info = {}) register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase }) end - def set_session(client) + def set_mssql_session(client) print_status("Using existing session #{session.sid}") @mssql_client = client end - def mssql_get_version + def create_mssql_client @mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT']) - @mssql_client.mssql_get_version end + # # This method sends a UDP query packet to the server and # parses out the reply packet into a hash diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index 6647715a67ad..b870ace7fd87 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -129,8 +129,6 @@ def detect_platform_and_arch # def mssql_login(user='sa', pass='', db='', domain_name='') - disconnect if self.sock - connect mssql_prelogin if auth == Msf::Exploit::Remote::AuthOption::KERBEROS @@ -477,28 +475,20 @@ def mssql_login(user='sa', pass='', db='', domain_name='') #this method send a prelogin packet and check if encryption is off # def mssql_prelogin(enc_error=false) + disconnect if self.sock + connect + pkt = mssql_prelogin_packet resp = mssql_send_recv(pkt) idx = 0 + data = parse_prelogin_response(resp) - while resp && resp[0, 1] != "\xff" && resp.length > 5 - token = resp.slice!(0, 5) - token = token.unpack("Cnn") - idx -= 5 - if token[0] == 0x01 - - idx += token[1] - break - end - end - if idx > 0 - encryption_mode = resp[idx, 1].unpack("C")[0] - else + unless data['Encryption'] framework_module.print_error("Unable to parse encryption req " \ "during pre-login, this may not be a MSSQL server") - encryption_mode = ENCRYPT_NOT_SUP + data['Encryption'] = ENCRYPT_NOT_SUP end ########################################################## @@ -516,7 +506,7 @@ def mssql_prelogin(enc_error=false) # ########################################################## - if encryption_mode == ENCRYPT_REQ + if data['Encryption'] == ENCRYPT_REQ # restart prelogin process except that we tell SQL Server # than we are now able to encrypt disconnect if self.sock @@ -529,27 +519,15 @@ def mssql_prelogin(enc_error=false) "been enabled based on server response.") resp = mssql_send_recv(pkt) + data = parse_prelogin_response(resp) - idx = 0 - - while resp && resp[0, 1] != "\xff" && resp.length > 5 - token = resp.slice!(0, 5) - token = token.unpack("Cnn") - idx -= 5 - if token[0] == 0x01 - idx += token[1] - break - end - end - if idx > 0 - encryption_mode = resp[idx, 1].unpack("C")[0] - else + unless data['Encryption'] framework_module.print_error("Unable to parse encryption req " \ "during pre-login, this may not be a MSSQL server") - encryption_mode = ENCRYPT_NOT_SUP + data['Encryption'] = ENCRYPT_NOT_SUP end end - encryption_mode + data end def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true) diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index 6438d491b381..6ff264d88c8e 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -148,54 +148,27 @@ def mssql_prelogin_packet pkt end - def mssql_get_version - disconnect if self.sock - connect - - pkt = mssql_prelogin_packet + def parse_prelogin_response(resp) + data = {} + if resp.length > 5 # minimum size for response specification + version_index = resp.slice(1, 2).unpack('n')[0] - resp = mssql_send_recv(pkt) - return unless resp + major = resp.slice(version_index, 1).unpack('C')[0] + minor = resp.slice(version_index+1, 1).unpack('C')[0] + build = resp.slice(version_index+2, 2).unpack('n')[0] - data = {} - while resp - token = resp.slice!(0, 1) - if token.unpack('C')[0] == 255 - major = resp.slice!(0, 1).unpack('C')[0] - minor = resp.slice!(0, 1).unpack('C')[0] - build = resp.slice!(0, 2).unpack('n')[0] - break - end + enc_index = resp.slice(6, 2).unpack('n')[0] + data['Encryption'] = resp.slice(enc_index, 1).unpack('C')[0] end if major && minor && build - build = "#{major}.#{minor}.#{build}" - end - if resp - resp.slice!(0,2) - enc = resp.slice!(0,1).unpack('C')[0] - case enc - when ENCRYPT_OFF - enc_value = 'off' - when ENCRYPT_ON - enc_value = 'on' - when ENCRYPT_NOT_SUP - enc_value = 'unsupported' - when ENCRYPT_REQ - enc_value = 'required' - end + data['Version'] = "#{major}.#{minor}.#{build}" end - if build - data['Version'] = build - end - if enc_value - data['Encryption'] = enc_value - end - data['Status'] = 'open' - return data + data['Status'] = 'open' if data['Version'] || data['Encryption'] + return data end def mssql_send_recv(req, timeout=15, check_status = true) diff --git a/modules/auxiliary/admin/mssql/mssql_enum.rb b/modules/auxiliary/admin/mssql/mssql_enum.rb index 385f5cbe9420..c93dc02e667f 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum.rb @@ -25,7 +25,7 @@ module to work, valid administrative user credentials must be def run print_status("Running MS SQL Server Enumeration...") if session - set_session(session.client) + set_mssql_session(session.client) else unless mssql_login_datastore print_error("Login was unsuccessful. Check your credentials.") diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb index 82ef51712a2e..1738ebf4cfa2 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb @@ -25,7 +25,7 @@ def initialize(info = {}) def run # Check connection and issue initial query if session - set_session(session.client) + set_mssql_session(session.client) else print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") if mssql_login_datastore diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb index 60cbccf9ff7f..2d4ac576a886 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb @@ -24,7 +24,7 @@ def initialize(info = {}) def run if session - set_session(session.client) + set_mssql_session(session.client) else print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") if mssql_login_datastore diff --git a/modules/auxiliary/admin/mssql/mssql_exec.rb b/modules/auxiliary/admin/mssql/mssql_exec.rb index a80228cbf58c..7a1a01790f9b 100644 --- a/modules/auxiliary/admin/mssql/mssql_exec.rb +++ b/modules/auxiliary/admin/mssql/mssql_exec.rb @@ -39,7 +39,7 @@ def initialize(info = {}) def run if session - set_session(session.client) + set_mssql_session(session.client) else unless mssql_login_datastore print_error("Error with mssql_login call") diff --git a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb index c3471a48a00e..c4c0e409d4d0 100644 --- a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb +++ b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb @@ -342,7 +342,7 @@ def sql_statement() # CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING begin if session - set_session(session.client) + set_mssql_session(session.client) else print_line(" ") print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...") diff --git a/modules/auxiliary/admin/mssql/mssql_idf.rb b/modules/auxiliary/admin/mssql/mssql_idf.rb index f2fb6d03c0dd..7f330457a51f 100644 --- a/modules/auxiliary/admin/mssql/mssql_idf.rb +++ b/modules/auxiliary/admin/mssql/mssql_idf.rb @@ -88,7 +88,7 @@ def run begin if session - set_session(session.client) + set_mssql_session(session.client) else unless mssql_login_datastore print_error('Login failed') diff --git a/modules/auxiliary/admin/mssql/mssql_sql.rb b/modules/auxiliary/admin/mssql/mssql_sql.rb index 53c10ea061b7..0498a2ac0c23 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql.rb @@ -40,7 +40,7 @@ def cmd_select(*args) def run if session - set_session(session.client) + set_mssql_session(session.client) else unless mssql_login_datastore print_error("Error with mssql_login call") diff --git a/modules/auxiliary/admin/mssql/mssql_sql_file.rb b/modules/auxiliary/admin/mssql/mssql_sql_file.rb index 13e7a3581a51..35e3a06c940b 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql_file.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql_file.rb @@ -36,7 +36,7 @@ def run begin if session - set_session(session.client) + set_mssql_session(session.client) else unless mssql_login_datastore print_error("#{datastore['RHOST']}:#{datastore['RPORT']} - Invalid SQL Server credentials") diff --git a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb index 0b19e3cb63e1..558b72fc2666 100644 --- a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb @@ -25,7 +25,7 @@ def initialize def run_host(ip) if session - set_session(session.client) + set_mssql_session(session.client) elsif !mssql_login(datastore['USERNAME'], datastore['PASSWORD']) info = self.mssql_client.initial_connection_info if info[:errors] && !info[:errors].empty? diff --git a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb index 234cb54990ed..52dc1fe9057e 100644 --- a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb @@ -31,7 +31,7 @@ def initialize def run_host(ip) if session - set_session(session.client) + set_mssql_session(session.client) else unless mssql_login_datastore print_error("#{datastore['RHOST']}:#{datastore['RPORT']} - Invalid SQL Server credentials") diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb index 69bf6757d6f1..70a28269633e 100644 --- a/modules/auxiliary/scanner/mssql/mssql_version.rb +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -23,28 +23,44 @@ def initialize def run if session - set_session(session.client) + set_mssql_session(session.client) + else + create_mssql_client end - data = mssql_get_version + data = mssql_prelogin + if data.nil? || data.empty? print_error("Unable to retrieve version information for #{mssql_client.address}") return end print_status("SQL Server for #{mssql_client.address}:") - if data['Version'] && !data['Version'].empty? + if data['Version'] print_good("Version: #{data['Version']}") else print_error('Unknown Version') end - if data['Encryption'] && !data['Encryption'].empty? + if data['Encryption'] + case data['Encryption'] + when ENCRYPT_OFF + data['Encryption'] = 'off' + when ENCRYPT_ON + data['Encryption'] = 'on' + when ENCRYPT_NOT_SUP + data['Encryption'] = 'unsupported' + when ENCRYPT_REQ + data['Encryption'] = 'required' + else + data['Encryption'] = 'unknown' + end print_good("Encryption is #{data['Encryption']}") else print_error('Unknown encryption status') end report_mssql_service(mssql_client.address, data) + mssql_client.disconnect end def report_mssql_service(ip, data) diff --git a/modules/exploits/windows/mssql/mssql_payload.rb b/modules/exploits/windows/mssql/mssql_payload.rb index 8d8a8afd2f59..48ab2652ef60 100644 --- a/modules/exploits/windows/mssql/mssql_payload.rb +++ b/modules/exploits/windows/mssql/mssql_payload.rb @@ -70,12 +70,12 @@ def initialize(info = {}) def check if session - set_session(session.client) - else - unless mssql_login_datastore - vprint_status("Invalid SQL Server credentials") - return Exploit::CheckCode::Detected - end + set_mssql_session(session.client) + end + + unless session || mssql_login_datastore + vprint_status("Invalid SQL Server credentials") + return Exploit::CheckCode::Detected end mssql_query("select @@version", true) From d8c7a2656588fea332d902f1832578c373508671 Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Tue, 5 Mar 2024 14:12:36 -0600 Subject: [PATCH 5/7] add unit test, clean up data hash --- lib/rex/proto/mssql/client.rb | 10 +++---- lib/rex/proto/mssql/client_mixin.rb | 7 ++--- .../auxiliary/scanner/mssql/mssql_version.rb | 26 ++++++++++--------- spec/lib/rex/proto/mssql/client_spec.rb | 10 +++++++ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index b870ace7fd87..fc326ceb11f2 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -485,10 +485,10 @@ def mssql_prelogin(enc_error=false) idx = 0 data = parse_prelogin_response(resp) - unless data['Encryption'] + unless data[:encryption] framework_module.print_error("Unable to parse encryption req " \ "during pre-login, this may not be a MSSQL server") - data['Encryption'] = ENCRYPT_NOT_SUP + data[:encryption] = ENCRYPT_NOT_SUP end ########################################################## @@ -506,7 +506,7 @@ def mssql_prelogin(enc_error=false) # ########################################################## - if data['Encryption'] == ENCRYPT_REQ + if data[:encryption] == ENCRYPT_REQ # restart prelogin process except that we tell SQL Server # than we are now able to encrypt disconnect if self.sock @@ -521,10 +521,10 @@ def mssql_prelogin(enc_error=false) resp = mssql_send_recv(pkt) data = parse_prelogin_response(resp) - unless data['Encryption'] + unless data[:encryption] framework_module.print_error("Unable to parse encryption req " \ "during pre-login, this may not be a MSSQL server") - data['Encryption'] = ENCRYPT_NOT_SUP + data[:encryption] = ENCRYPT_NOT_SUP end end data diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index 6ff264d88c8e..086d235966ca 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -158,16 +158,13 @@ def parse_prelogin_response(resp) build = resp.slice(version_index+2, 2).unpack('n')[0] enc_index = resp.slice(6, 2).unpack('n')[0] - data['Encryption'] = resp.slice(enc_index, 1).unpack('C')[0] + data[:encryption] = resp.slice(enc_index, 1).unpack('C')[0] end if major && minor && build - data['Version'] = "#{major}.#{minor}.#{build}" + data[:version] = "#{major}.#{minor}.#{build}" end - - data['Status'] = 'open' if data['Version'] || data['Encryption'] - return data end diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb index 70a28269633e..4c63c229b5bb 100644 --- a/modules/auxiliary/scanner/mssql/mssql_version.rb +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -35,26 +35,28 @@ def run return end + data[:status] = 'open' if data[:version] || data[:encryption] + print_status("SQL Server for #{mssql_client.address}:") - if data['Version'] - print_good("Version: #{data['Version']}") + if data[:version] + print_good("Version: #{data[:version]}") else print_error('Unknown Version') end - if data['Encryption'] - case data['Encryption'] + if data[:encryption] + case data[:encryption] when ENCRYPT_OFF - data['Encryption'] = 'off' + data[:encryption] = 'off' when ENCRYPT_ON - data['Encryption'] = 'on' + data[:encryption] = 'on' when ENCRYPT_NOT_SUP - data['Encryption'] = 'unsupported' + data[:encryption] = 'unsupported' when ENCRYPT_REQ - data['Encryption'] = 'required' + data[:encryption] = 'required' else - data['Encryption'] = 'unknown' + data[:encryption] = 'unknown' end - print_good("Encryption is #{data['Encryption']}") + print_good("Encryption is #{data[:encryption]}") else print_error('Unknown encryption status') end @@ -65,8 +67,8 @@ def run def report_mssql_service(ip, data) mssql_info = 'Version: %s, Encryption: %s' % [ - version: data['Version'] || 'unknown', - encryption: data['Encryption'] || 'unknown' + version: data[:version] || 'unknown', + encryption: data[:encryption] || 'unknown' ] report_service( host: ip, diff --git a/spec/lib/rex/proto/mssql/client_spec.rb b/spec/lib/rex/proto/mssql/client_spec.rb index fa68f461e87d..3cf37da02244 100644 --- a/spec/lib/rex/proto/mssql/client_spec.rb +++ b/spec/lib/rex/proto/mssql/client_spec.rb @@ -69,4 +69,14 @@ end end end + + context '#parse_prelogin_response' do + let(:buf) { "\x00\x00\x15\x00\x06\x01\x00\e\x00\x01\x02\x00\x1C\x00\x01\x03\x00\x1D\x00\x00\xFF\x10\x00\x03\xE8\x00\x00\x02\x00" } + let(:client) { Rex::Proto::MSSQL::Client.allocate } + + it 'correctly parses a prelogin response' do + result = client.parse_prelogin_response(buf) + expect(result).to eq({ version: '16.0.1000', encryption: 2 }) + end + end end From 04c5d8b924b9e2c7931d6056cd3a18c0521de96f Mon Sep 17 00:00:00 2001 From: Zach Goldman <106169455+zgoldman-r7@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:27:50 -0500 Subject: [PATCH 6/7] Update modules/auxiliary/scanner/mssql/mssql_version.rb Co-authored-by: jheysel-r7 --- modules/auxiliary/scanner/mssql/mssql_version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb index 4c63c229b5bb..f9169f1ca774 100644 --- a/modules/auxiliary/scanner/mssql/mssql_version.rb +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -30,7 +30,7 @@ def run data = mssql_prelogin - if data.nil? || data.empty? + if data.blank? print_error("Unable to retrieve version information for #{mssql_client.address}") return end From c382066be844d496f44c8bceb212de7c159a67fe Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Mon, 22 Apr 2024 14:46:50 -0400 Subject: [PATCH 7/7] cache prelogin packet --- lib/rex/proto/mssql/client.rb | 5 +++-- modules/auxiliary/scanner/mssql/mssql_version.rb | 15 +++++++-------- spec/acceptance/mssql_spec.rb | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index fc326ceb11f2..c9b2c2cde731 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -129,8 +129,7 @@ def detect_platform_and_arch # def mssql_login(user='sa', pass='', db='', domain_name='') - mssql_prelogin - + prelogin_data = mssql_prelogin if auth == Msf::Exploit::Remote::AuthOption::KERBEROS idx = 0 pkt = '' @@ -234,6 +233,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') info = {:errors => []} info = mssql_parse_reply(resp, info) self.initial_connection_info = info + self.initial_connection_info[:prelogin_data] = prelogin_data return false if not info return info[:login_ack] ? true : false @@ -466,6 +466,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') info = {:errors => []} info = mssql_parse_reply(resp, info) self.initial_connection_info = info + self.initial_connection_info[:prelogin_data] = prelogin_data return false if not info info[:login_ack] ? true : false diff --git a/modules/auxiliary/scanner/mssql/mssql_version.rb b/modules/auxiliary/scanner/mssql/mssql_version.rb index f9169f1ca774..00eaff7496e7 100644 --- a/modules/auxiliary/scanner/mssql/mssql_version.rb +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -24,20 +24,20 @@ def initialize def run if session set_mssql_session(session.client) + data = mssql_client.initial_connection_info[:prelogin_data] else create_mssql_client + data = mssql_prelogin end - data = mssql_prelogin - if data.blank? - print_error("Unable to retrieve version information for #{mssql_client.address}") + print_error("Unable to retrieve version information for #{mssql_client.peerhost}") return end data[:status] = 'open' if data[:version] || data[:encryption] - print_status("SQL Server for #{mssql_client.address}:") + print_status("SQL Server for #{mssql_client.peerhost}:") if data[:version] print_good("Version: #{data[:version]}") else @@ -56,13 +56,12 @@ def run else data[:encryption] = 'unknown' end - print_good("Encryption is #{data[:encryption]}") + print_good("Encryption: #{data[:encryption]}") else print_error('Unknown encryption status') end - report_mssql_service(mssql_client.address, data) - mssql_client.disconnect + report_mssql_service(mssql_client.peerhost, data) end def report_mssql_service(ip, data) @@ -72,7 +71,7 @@ def report_mssql_service(ip, data) ] report_service( host: ip, - port: mssql_client.port, + port: mssql_client.peerport, name: 'mssql', info: mssql_info, state: (data['Status'].nil? ? 'closed' : data['Status']) diff --git a/spec/acceptance/mssql_spec.rb b/spec/acceptance/mssql_spec.rb index 3c8a7c20b7af..ecf86108be04 100644 --- a/spec/acceptance/mssql_spec.rb +++ b/spec/acceptance/mssql_spec.rb @@ -40,6 +40,20 @@ }, } }, + { + name: "auxiliary/scanner/mssql/mssql_version", + platforms: [:linux, :osx, :windows], + targets: [:session, :rhost], + skipped: false, + lines: { + all: { + required: [ + /Version: \d+.\d+.\d+/, + /Encryption: (?:on|off|unsupported|required|unknown)/ + ] + }, + } + }, { name: "auxiliary/admin/mssql/mssql_enum", platforms: [:linux, :osx, :windows],