diff --git a/lib/msf/core/exploit/remote/mssql.rb b/lib/msf/core/exploit/remote/mssql.rb index 41088ce0f8c0..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,11 +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 create_mssql_client + @mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT']) + 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 d4beb01ee6df..c9b2c2cde731 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -129,10 +129,7 @@ def detect_platform_and_arch # def mssql_login(user='sa', pass='', db='', domain_name='') - disconnect if self.sock - connect - mssql_prelogin - + prelogin_data = mssql_prelogin if auth == Msf::Exploit::Remote::AuthOption::KERBEROS idx = 0 pkt = '' @@ -236,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 @@ -468,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 @@ -477,85 +476,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) - 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 + disconnect if self.sock + connect - pkt = pkt_hdr.pack("CCnnCC") + pkt_data + 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 ########################################################## @@ -573,7 +507,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 @@ -586,27 +520,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 684b9c529e9c..086d235966ca 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -86,6 +86,88 @@ def mssql_print_reply(info) end end + def mssql_prelogin_packet + 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 + end + + def parse_prelogin_response(resp) + data = {} + if resp.length > 5 # minimum size for response specification + version_index = resp.slice(1, 2).unpack('n')[0] + + 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] + + enc_index = resp.slice(6, 2).unpack('n')[0] + data[:encryption] = resp.slice(enc_index, 1).unpack('C')[0] + end + + if major && minor && build + data[:version] = "#{major}.#{minor}.#{build}" + end + + return data + end + def mssql_send_recv(req, timeout=15, check_status = true) sock.put(req) 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_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_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 new file mode 100644 index 000000000000..00eaff7496e7 --- /dev/null +++ b/modules/auxiliary/scanner/mssql/mssql_version.rb @@ -0,0 +1,80 @@ +## +# 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 + include Msf::OptionalSession::MSSQL + + def initialize + super( + 'Name' => 'MSSQL Version Utility', + '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_mssql_session(session.client) + data = mssql_client.initial_connection_info[:prelogin_data] + else + create_mssql_client + data = mssql_prelogin + end + + if data.blank? + 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.peerhost}:") + if data[:version] + print_good("Version: #{data[:version]}") + else + print_error('Unknown Version') + end + 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: #{data[:encryption]}") + else + print_error('Unknown encryption status') + end + + report_mssql_service(mssql_client.peerhost, 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.peerport, + name: 'mssql', + info: mssql_info, + state: (data['Status'].nil? ? 'closed' : data['Status']) + ) + end +end 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) 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], 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