diff --git a/Gemfile.lock b/Gemfile.lock index 365cbd6f34..0daa7a46b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,6 +36,7 @@ PATH rex-text rex-zip robots + rubyntlm rubyzip sqlite3 sshkey diff --git a/lib/metasploit/framework/mssql/client.rb b/lib/metasploit/framework/mssql/client.rb index acfed80b88..4c79a8eb0e 100644 --- a/lib/metasploit/framework/mssql/client.rb +++ b/lib/metasploit/framework/mssql/client.rb @@ -9,11 +9,6 @@ module Metasploit extend ActiveSupport::Concern include Metasploit::Framework::Tcp::Client - NTLM_CRYPT = Rex::Proto::NTLM::Crypt - NTLM_CONST = Rex::Proto::NTLM::Constants - NTLM_UTILS = Rex::Proto::NTLM::Utils - NTLM_XCEPT = Rex::Proto::NTLM::Exceptions - # Encryption ENCRYPT_OFF = 0x00 #Encryption is available but off. ENCRYPT_ON = 0x01 #Encryption is available and on. @@ -21,23 +16,23 @@ module Metasploit ENCRYPT_REQ = 0x03 #Encryption is required. # Packet Type - TYPE_SQL_BATCH = 1 # (Client) SQL command - TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused) - TYPE_RPC = 3 # (Client) RPC - TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters, + TYPE_SQL_BATCH = 1 # (Client) SQL command + TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused) + TYPE_RPC = 3 # (Client) RPC + TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters, # Request Completion, Error and Info Messages, Attention Acknowledgement - TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention - TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data + TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention + TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager - TYPE_TDS7_LOGIN = 16 # (Client) Login - TYPE_SSPI_MESSAGE = 17 # (Client) Login - TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7 + TYPE_TDS7_LOGIN = 16 # (Client) Login + TYPE_SSPI_MESSAGE = 17 # (Client) Login + TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7 # Status - STATUS_NORMAL = 0x00 - STATUS_END_OF_MESSAGE = 0x01 - STATUS_IGNORE_EVENT = 0x02 - STATUS_RESETCONNECTION = 0x08 # TDS 7.1+ + STATUS_NORMAL = 0x00 + STATUS_END_OF_MESSAGE = 0x01 + STATUS_IGNORE_EVENT = 0x02 + STATUS_RESETCONNECTION = 0x08 # TDS 7.1+ STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+ # @@ -55,14 +50,14 @@ module Metasploit idx = 0 pkt = '' pkt_hdr = '' - pkt_hdr = [ + pkt_hdr = [ TYPE_TDS7_LOGIN, #type STATUS_END_OF_MESSAGE, #status 0x0000, #length 0x0000, # SPID - 0x01, # PacketID (unused upon specification + 0x01, # PacketID (unused upon specification # but ms network monitor stil prefer 1 to decode correctly, wireshark don't care) - 0x00 #Window + 0x00 #Window ] pkt << [ @@ -85,18 +80,18 @@ module Metasploit sname = Rex::Text.to_unicode( rhost ) dname = Rex::Text.to_unicode( db ) - ntlm_options = { - :signing => false, - :usentlm2_session => use_ntlm2_session, - :use_ntlmv2 => use_ntlmv2, - :send_lm => send_lm, - :send_ntlm => send_ntlm - } - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags) + ntlm_client = ::Net::NTLM::Client.new( + user, + pass, + workstation: workstation_name, + domain: domain_name, + ) + type1 = ntlm_client.init_context + # SQL 2012, at least, does not support KEY_EXCHANGE + type1.flag &= ~ ::Net::NTLM::FLAGS[:KEY_EXCHANGE] + ntlmsspblob = type1.serialize idx = pkt.size + 50 # lengths below @@ -137,9 +132,9 @@ module Metasploit pkt << ntlmsspblob # Total packet length - pkt[0,4] = [pkt.length].pack('V') + pkt[0, 4] = [pkt.length].pack('V') - pkt_hdr[2] = pkt.length + 8 + pkt_hdr[2] = pkt.length + 8 pkt = pkt_hdr.pack("CCnnCC") + pkt @@ -147,64 +142,38 @@ module Metasploit # has a strange behavior that differs from the specifications # upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header # is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification - if tdsencryption == true proxy = TDSSSLProxy.new(sock) proxy.setup_ssl - resp = proxy.send_recv(pkt) + resp = proxy.send_recv(pkt, 15, false) else - resp = mssql_send_recv(pkt) + resp = mssql_send_recv(pkt, 15, false) end - # Get default data - begin - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp) - # a domain.length < 3 will hit this - rescue NTLM_XCEPT::NTLMMissingChallenge - return false - end - - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - default_name = blob_data[:default_name] || '' - #netbios domain - default_domain = blob_data[:default_domain] || '' - #dns name - dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - - spnopt = {:use_spn => send_spn, :name => rhost} - - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - - ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags) + # Strip the TDS header + resp = resp[3..-1] + type3 = ntlm_client.init_context([resp].pack('m')) + type3_blob = type3.serialize # Create an SSPIMessage idx = 0 pkt = '' pkt_hdr = '' - pkt_hdr = [ - TYPE_SSPI_MESSAGE, #type - STATUS_END_OF_MESSAGE, #status - 0x0000, #length - 0x0000, # SPID - 0x01, # PacketID - 0x00 #Window + pkt_hdr = [ + TYPE_SSPI_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x01, # PacketID + 0x00 #Window ] - pkt_hdr[2] = ntlmssp.length + 8 + pkt_hdr[2] = type3_blob.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + ntlmssp + pkt = pkt_hdr.pack("CCnnCC") + type3_blob if self.tdsencryption == true - resp = mssql_ssl_send_recv(pkt,proxy) + resp = mssql_ssl_send_recv(pkt, proxy) proxy.cleanup proxy = nil else @@ -283,7 +252,7 @@ module Metasploit pkt << dname # Total packet length - pkt[0,4] = [pkt.length].pack('V') + pkt[0, 4] = [pkt.length].pack('V') # Embedded packet lengths pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2 @@ -294,7 +263,7 @@ module Metasploit if self.tdsencryption == true proxy = TDSSSLProxy.new(sock) proxy.setup_ssl - resp = mssql_ssl_send_recv(pkt,proxy) + resp = mssql_ssl_send_recv(pkt, proxy) proxy.cleanup proxy = nil else @@ -304,7 +273,7 @@ module Metasploit end info = {:errors => []} - info = mssql_parse_reply(resp,info) + info = mssql_parse_reply(resp, info) disconnect @@ -316,17 +285,17 @@ module Metasploit # Parse an "environment change" TDS token # def mssql_parse_env(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) - type = buff.slice!(0,1).unpack('C')[0] + len = data.slice!(0, 2).unpack('v')[0] + buff = data.slice!(0, len) + type = buff.slice!(0, 1).unpack('C')[0] nval = '' - nlen = buff.slice!(0,1).unpack('C')[0] || 0 - nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0 + nlen = buff.slice!(0, 1).unpack('C')[0] || 0 + nval = buff.slice!(0, nlen*2).gsub("\x00", '') if nlen > 0 oval = '' - olen = buff.slice!(0,1).unpack('C')[0] || 0 - oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0 + olen = buff.slice!(0, 1).unpack('C')[0] || 0 + oval = buff.slice!(0, olen*2).gsub("\x00", '') if olen > 0 info[:envs] ||= [] info[:envs] << { :type => type, :old => oval, :new => nval } @@ -337,7 +306,7 @@ module Metasploit # Parse a "ret" TDS token # def mssql_parse_ret(data, info) - ret = data.slice!(0,4).unpack('N')[0] + ret = data.slice!(0, 4).unpack('N')[0] info[:ret] = ret info end @@ -346,7 +315,7 @@ module Metasploit # Parse a "done" TDS token # def mssql_parse_done(data, info) - status,cmd,rows = data.slice!(0,8).unpack('vvV') + status, cmd, rows = data.slice!(0, 8).unpack('vvV') info[:done] = { :status => status, :cmd => cmd, :rows => rows } info end @@ -355,11 +324,11 @@ module Metasploit # Parse an "error" TDS token # def mssql_parse_error(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + buff = data.slice!(0, len) - errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') - emsg = buff.slice!(0,elen * 2) + errno, state, sev, elen = buff.slice!(0, 8).unpack('VCCv') + emsg = buff.slice!(0, elen * 2) emsg.gsub!("\x00", '') info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" @@ -370,14 +339,14 @@ module Metasploit # Parse an "information" TDS token # def mssql_parse_info(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + buff = data.slice!(0, len) - errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') - emsg = buff.slice!(0,elen * 2) + errno, state, sev, elen = buff.slice!(0, 8).unpack('VCCv') + emsg = buff.slice!(0, elen * 2) emsg.gsub!("\x00", '') - info[:infos]||= [] + info[:infos] ||= [] info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" info end @@ -386,8 +355,8 @@ module Metasploit # Parse a "login ack" TDS token # def mssql_parse_login_ack(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + _buff = data.slice!(0, len) info[:login_ack] = true end @@ -398,7 +367,7 @@ module Metasploit info[:errors] = [] return if not data until data.empty? - token = data.slice!(0,1).unpack('C')[0] + token = data.slice!(0, 1).unpack('C')[0] case token when 0x81 mssql_parse_tds_reply(data, info) @@ -434,14 +403,14 @@ module Metasploit info[:colnames] ||= [] # Parse out the columns - cols = data.slice!(0,2).unpack('v')[0] + cols = data.slice!(0, 2).unpack('v')[0] 0.upto(cols-1) do |col_idx| col = {} info[:colinfos][col_idx] = col - col[:utype] = data.slice!(0,2).unpack('v')[0] - col[:flags] = data.slice!(0,2).unpack('v')[0] - col[:type] = data.slice!(0,1).unpack('C')[0] + col[:utype] = data.slice!(0, 2).unpack('v')[0] + col[:flags] = data.slice!(0, 2).unpack('v')[0] + col[:type] = data.slice!(0, 1).unpack('C')[0] case col[:type] when 48 @@ -458,8 +427,8 @@ module Metasploit when 34 col[:id] = :image - col[:max_size] = data.slice!(0,4).unpack('V')[0] - col[:value_length] = data.slice!(0,2).unpack('v')[0] + col[:max_size] = data.slice!(0, 4).unpack('V')[0] + col[:value_length] = data.slice!(0, 2).unpack('v')[0] col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '') when 36 @@ -467,31 +436,31 @@ module Metasploit when 38 col[:id] = :int - col[:int_size] = data.slice!(0,1).unpack('C')[0] + col[:int_size] = data.slice!(0, 1).unpack('C')[0] when 127 col[:id] = :bigint when 165 col[:id] = :hex - col[:max_size] = data.slice!(0,2).unpack('v')[0] + col[:max_size] = data.slice!(0, 2).unpack('v')[0] when 173 col[:id] = :hex # binary(2) - col[:max_size] = data.slice!(0,2).unpack('v')[0] + col[:max_size] = data.slice!(0, 2).unpack('v')[0] - when 231,175,167,239 + when 231, 175, 167, 239 col[:id] = :string - col[:max_size] = data.slice!(0,2).unpack('v')[0] - col[:codepage] = data.slice!(0,2).unpack('v')[0] - col[:cflags] = data.slice!(0,2).unpack('v')[0] - col[:charset_id] = data.slice!(0,1).unpack('C')[0] + col[:max_size] = data.slice!(0, 2).unpack('v')[0] + col[:codepage] = data.slice!(0, 2).unpack('v')[0] + col[:cflags] = data.slice!(0, 2).unpack('v')[0] + col[:charset_id] = data.slice!(0, 1).unpack('C')[0] else col[:id] = :unknown end - col[:msg_len] = data.slice!(0,1).unpack('C')[0] + col[:msg_len] = data.slice!(0, 1).unpack('C')[0] if(col[:msg_len] and col[:msg_len] > 0) col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '') @@ -517,28 +486,28 @@ module Metasploit case col[:id] when :hex str = "" - len = data.slice!(0,2).unpack('v')[0] + len = data.slice!(0, 2).unpack('v')[0] if(len > 0 and len < 65535) - str << data.slice!(0,len) + str << data.slice!(0, len) end row << str.unpack("H*")[0] when :string str = "" - len = data.slice!(0,2).unpack('v')[0] + len = data.slice!(0, 2).unpack('v')[0] if(len > 0 and len < 65535) - str << data.slice!(0,len) + str << data.slice!(0, len) end row << str.gsub("\x00", '') when :datetime - row << data.slice!(0,8).unpack("H*")[0] + row << data.slice!(0, 8).unpack("H*")[0] when :rawint - row << data.slice!(0,4).unpack('V')[0] + row << data.slice!(0, 4).unpack('V')[0] when :bigint - row << data.slice!(0,8).unpack("H*")[0] + row << data.slice!(0, 8).unpack("H*")[0] when :smallint row << data.slice!(0, 2).unpack("v")[0] @@ -551,8 +520,8 @@ module Metasploit when :image str = '' - len = data.slice!(0,1).unpack('C')[0] - str = data.slice!(0,len) if (len and len > 0) + len = data.slice!(0, 1).unpack('C')[0] + str = data.slice!(0, len) if (len and len > 0) row << str.unpack("H*")[0] when :int @@ -560,7 +529,7 @@ module Metasploit raw = data.slice!(0, len) if (len and len > 0) case len - when 0,255 + when 0, 255 row << '' when 1 row << raw.unpack("C")[0] @@ -573,7 +542,7 @@ module Metasploit when 8 row << raw.unpack('VV')[0] # XXX: missing high dword else - info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}" + info[:errors] << "invalid integer size: #{len} #{data[0, 16].unpack("H*")[0]}" end else info[:errors] << "unknown column type: #{col.inspect}" @@ -595,7 +564,7 @@ module Metasploit pkt_data = "" - pkt_hdr = [ + pkt_hdr = [ TYPE_PRE_LOGIN_MESSAGE, #type STATUS_END_OF_MESSAGE, #status 0x0000, #length @@ -604,7 +573,7 @@ module Metasploit 0x00 #Window ] - version = [0x55010008,0x0000].pack("Vv") + version = [0x55010008, 0x0000].pack("Vv") # if manually set, we will honour if tdsencryption == true @@ -615,45 +584,45 @@ module Metasploit instoptdata = "MSSQLServer\0" - threadid = "\0\0" + Rex::Text.rand_text(2) + 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 + 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 + 0x01, # Token 1 type Encryption + idx = idx + version.length, # EncryptionOffset + 0x01, # EncryptionLength - 0x02, # Token 2 type InstOpt - idx = idx + 1, # InstOptOffset - instoptdata.length, # InstOptLength + 0x02, # Token 2 type InstOpt + idx = idx + 1, # InstOptOffset + instoptdata.length, # InstOptLength - 0x03, # Token 3 type Threadid - idx + instoptdata.length, # ThreadIdOffset - 0x04, # ThreadIdLength + 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_data << pkt_data_token + pkt_data << version + pkt_data << encryption + pkt_data << instoptdata + pkt_data << threadid - pkt_hdr[2] = pkt_data.length + 8 + pkt_hdr[2] = pkt_data.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + pkt_data + pkt = pkt_hdr.pack("CCnnCC") + pkt_data resp = mssql_send_recv(pkt) idx = 0 - while resp and resp[0,1] != "\xff" and resp.length > 5 - token = resp.slice!(0,5) + while resp && resp[0, 1] != "\xff" && resp.length > 5 + token = resp.slice!(0, 5) token = token.unpack("Cnn") idx -= 5 if token[0] == 0x01 @@ -663,7 +632,7 @@ module Metasploit end end if idx > 0 - encryption_mode = resp[idx,1].unpack("C")[0] + encryption_mode = resp[idx, 1].unpack("C")[0] else raise RunTimeError, "Unable to parse encryption req. "\ "from server during prelogin" @@ -701,8 +670,8 @@ module Metasploit idx = 0 - while resp and resp[0,1] != "\xff" and resp.length > 5 - token = resp.slice!(0,5) + while resp && resp[0, 1] != "\xff" && resp.length > 5 + token = resp.slice!(0, 5) token = token.unpack("Cnn") idx -= 5 if token[0] == 0x01 @@ -711,7 +680,7 @@ module Metasploit end end if idx > 0 - encryption_mode = resp[idx,1].unpack("C")[0] + encryption_mode = resp[idx, 1].unpack("C")[0] else raise RuntimeError, "Unable to parse encryption "\ "req during pre-login" @@ -735,17 +704,17 @@ module Metasploit while(not done) head = sock.get_once(8, timeout) - if !(head and head.length == 8) + if !(head && head.length == 8) return false end # Is this the last buffer? - if(head[1,1] == "\x01" or not check_status ) + if head[1, 1] == "\x01" || !check_status done = true end # Grab this block's length - rlen = head[2,2].unpack('n')[0] - 8 + rlen = head[2, 2].unpack('n')[0] - 8 while(rlen > 0) buff = sock.get_once(rlen, timeout) @@ -758,7 +727,7 @@ module Metasploit resp end - def mssql_ssl_send_recv(req,tdsproxy,timeout=15,check_status=true) + def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true) tdsproxy.send_recv(req) end diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 5d6636e155..687e6eb4a6 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -2,10 +2,6 @@ require 'uri' require 'digest' -require 'rex/proto/ntlm/crypt' -require 'rex/proto/ntlm/constants' -require 'rex/proto/ntlm/utils' -require 'rex/proto/ntlm/exceptions' module Msf ### @@ -16,15 +12,6 @@ module Msf ### module Exploit::Remote::HttpClient include Msf::Auxiliary::Report - include Exploit::Remote::NTLM::Client - - # - # Constants - # - NTLM_CRYPT = Rex::Proto::NTLM::Crypt - NTLM_CONST = Rex::Proto::NTLM::Constants - NTLM_UTILS = Rex::Proto::NTLM::Utils - NTLM_XCEPT = Rex::Proto::NTLM::Exceptions # # Initializes an exploit module that exploits a vulnerability in an HTTP @@ -193,12 +180,6 @@ module Exploit::Remote::HttpClient 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], 'header_folding' => datastore['HTTP::header_folding'], - 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], - 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], - 'send_lm' => datastore['NTLM::SendLM'], - 'send_ntlm' => datastore['NTLM::SendNTLM'], - 'SendSPN' => datastore['NTLM::SendSPN'], - 'UseLMKey' => datastore['NTLM::UseLMKey'], 'domain' => datastore['DOMAIN'], 'DigestAuthIIS' => datastore['DigestAuthIIS'] ) @@ -255,12 +236,6 @@ module Exploit::Remote::HttpClient evade_uri_fake_end: datastore['HTTP::uri_fake_end'], evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'], evade_header_folding: datastore['HTTP::header_folding'], - ntlm_use_ntlmv2_session: datastore['NTLM::UseNTLM2_session'], - ntlm_use_ntlmv2: datastore['NTLM::UseNTLMv2'], - ntlm_send_lm: datastore['NTLM::SendLM'], - ntlm_send_ntlm: datastore['NTLM::SendNTLM'], - ntlm_send_spn: datastore['NTLM::SendSPN'], - ntlm_use_lm_key: datastore['NTLM::UseLMKey'], ntlm_domain: datastore['DOMAIN'], digest_auth_iis: datastore['DigestAuthIIS'] }.merge(conf) diff --git a/lib/msf/core/exploit/mssql.rb b/lib/msf/core/exploit/mssql.rb index 77eacf5b4e..813592e43b 100644 --- a/lib/msf/core/exploit/mssql.rb +++ b/lib/msf/core/exploit/mssql.rb @@ -1,11 +1,6 @@ # -*- coding: binary -*- require 'msf/core' require 'msf/core/exploit/mssql_commands' -require 'rex/proto/ntlm/crypt' -require 'rex/proto/ntlm/constants' -require 'rex/proto/ntlm/utils' -require 'rex/proto/ntlm/exceptions' - module Msf @@ -21,41 +16,32 @@ module Exploit::Remote::MSSQL include Exploit::Remote::Tcp include Exploit::Remote::NTLM::Client - # - # Constants - # - NTLM_CRYPT = Rex::Proto::NTLM::Crypt - NTLM_CONST = Rex::Proto::NTLM::Constants - NTLM_UTILS = Rex::Proto::NTLM::Utils - NTLM_XCEPT = Rex::Proto::NTLM::Exceptions - # Encryption 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. - # Paquet Type - TYPE_SQL_BATCH = 1 # (Client) SQL command - TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused) - TYPE_RPC = 3 # (Client) RPC - TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters, - # Request Completion, Error and Info Messages, Attention Acknowledgement - TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention - TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data + # Packet Type + TYPE_SQL_BATCH = 1 # (Client) SQL command + TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused) + TYPE_RPC = 3 # (Client) RPC + TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters, + # Request Completion, Error and Info Messages, Attention Acknowledgement + TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention + TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager - TYPE_TDS7_LOGIN = 16 # (Client) Login - TYPE_SSPI_MESSAGE = 17 # (Client) Login - TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7 + TYPE_TDS7_LOGIN = 16 # (Client) Login + TYPE_SSPI_MESSAGE = 17 # (Client) Login + TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7 # Status - STATUS_NORMAL = 0x00 - STATUS_END_OF_MESSAGE = 0x01 - STATUS_IGNORE_EVENT = 0x02 - STATUS_RESETCONNECTION = 0x08 # TDS 7.1+ + STATUS_NORMAL = 0x00 + STATUS_END_OF_MESSAGE = 0x01 + STATUS_IGNORE_EVENT = 0x02 + STATUS_RESETCONNECTION = 0x08 # TDS 7.1+ STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+ - # # Creates an instance of a MSSQL exploit module. # @@ -100,16 +86,13 @@ module Exploit::Remote::MSSQL 'MsfExploit' => self, }) - ping_sock.put("\x02") - resp, saddr, sport = ping_sock.recvfrom(65535, timeout) + resp, _saddr, _sport = ping_sock.recvfrom(65535, timeout) ping_sock.close return data if not resp return data if resp.length == 0 - var = nil - return mssql_ping_parse(resp) end @@ -145,15 +128,15 @@ module Exploit::Remote::MSSQL # # Execute a system command via xp_cmdshell # - def mssql_xpcmdshell(cmd,doprint=false,opts={}) + def mssql_xpcmdshell(cmd, doprint=false, opts={}) force_enable = false begin res = mssql_query("EXEC master..xp_cmdshell '#{cmd}'", false, opts) - if(res[:errors] and not res[:errors].empty?) - if(res[:errors].join =~ /xp_cmdshell/) - if(force_enable) + if res[:errors] && !res[:errors].empty? + if res[:errors].join =~ /xp_cmdshell/ + if force_enable print_error("The xp_cmdshell procedure is not available and could not be enabled") - raise RuntimeError, "Failed to execute command" + raise RuntimeError, "Failed to execute command" else print_status("The server may have xp_cmdshell disabled, trying to enable it...") mssql_query(mssql_xpcmdshell_enable()) @@ -167,7 +150,7 @@ module Exploit::Remote::MSSQL return res rescue RuntimeError => e - if(e.to_s =~ /xp_cmdshell disabled/) + if e.to_s =~ /xp_cmdshell disabled/ force_enable = true retry end @@ -200,7 +183,7 @@ module Exploit::Remote::MSSQL idx = 0 cnt = 500 while(idx < hex.length - 1) - mssql_xpcmdshell("cmd.exe /c echo #{hex[idx,cnt]}>>%TEMP%\\#{var_payload}", false) + mssql_xpcmdshell("cmd.exe /c echo #{hex[idx, cnt]}>>%TEMP%\\#{var_payload}", false) idx += cnt end @@ -234,7 +217,7 @@ module Exploit::Remote::MSSQL idx = 0 cnt = 500 while(idx < hex.length - 1) - mssql_xpcmdshell("cmd.exe /c echo #{hex[idx,cnt]}>>%TEMP%\\#{var_payload}", false) + mssql_xpcmdshell("cmd.exe /c echo #{hex[idx, cnt]}>>%TEMP%\\#{var_payload}", false) idx += cnt end print_status("Converting the payload utilizing PowerShell EncodedCommand...") @@ -260,17 +243,17 @@ module Exploit::Remote::MSSQL while(not done) head = sock.get_once(8, timeout) - if !(head and head.length == 8) + if !(head && head.length == 8) return false end # Is this the last buffer? - if(head[1,1] == "\x01" or not check_status ) + if(head[1, 1] == "\x01" or not check_status ) done = true end # Grab this block's length - rlen = head[2,2].unpack('n')[0] - 8 + rlen = head[2, 2].unpack('n')[0] - 8 while(rlen > 0) buff = sock.get_once(rlen, timeout) @@ -302,77 +285,77 @@ module Exploit::Remote::MSSQL pkt_data = "" - pkt_hdr = [ - TYPE_PRE_LOGIN_MESSAGE, #type - STATUS_END_OF_MESSAGE, #status - 0x0000, #length - 0x0000, # SPID - 0x00, # PacketID - 0x00 #Window - ] + 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") - encryption = ENCRYPT_NOT_SUP # off - instoptdata = "MSSQLServer\0" + version = [0x55010008, 0x0000].pack("Vv") + encryption = ENCRYPT_NOT_SUP # off + instoptdata = "MSSQLServer\0" - threadid = "\0\0" + Rex::Text.rand_text(2) + 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 + 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 + 0x01, # Token 1 type Encryption + idx = idx + version.length, # EncryptionOffset + 0x01, # EncryptionLength - 0x02, # Token 2 type InstOpt - idx = idx + 1, # InstOptOffset - instoptdata.length, # InstOptLength + 0x02, # Token 2 type InstOpt + idx = idx + 1, # InstOptOffset + instoptdata.length, # InstOptLength - 0x03, # Token 3 type Threadid - idx + instoptdata.length, # ThreadIdOffset - 0x04, # ThreadIdLength + 0x03, # Token 3 type Threadid + idx + instoptdata.length, # ThreadIdOffset + 0x04, # ThreadIdLength - 0xFF - ].pack("CnnCnnCnnCnnC") + 0xFF + ].pack("CnnCnnCnnCnnC") - pkt_data << pkt_data_token - pkt_data << version - pkt_data << encryption - pkt_data << instoptdata - pkt_data << threadid + 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_hdr[2] = pkt_data.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + pkt_data + pkt = pkt_hdr.pack("CCnnCC") + pkt_data - resp = mssql_send_recv(pkt) + resp = mssql_send_recv(pkt) - idx = 0 + idx = 0 - while resp and resp[0,1] != "\xff" and 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 - #force to ENCRYPT_NOT_SUP and hope for the best - encryption_mode = ENCRYPT_NOT_SUP + 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 encryption_mode != ENCRYPT_NOT_SUP and enc_error - raise RuntimeError,"Encryption is not supported" - end - encryption_mode + if idx > 0 + encryption_mode = resp[idx, 1].unpack("C")[0] + else + # force to ENCRYPT_NOT_SUP and hope for the best + encryption_mode = ENCRYPT_NOT_SUP + end + + if encryption_mode != ENCRYPT_NOT_SUP && enc_error + raise RuntimeError,"Encryption is not supported" + end + encryption_mode end # @@ -401,14 +384,14 @@ module Exploit::Remote::MSSQL idx = 0 pkt = '' pkt_hdr = '' - pkt_hdr = [ + pkt_hdr = [ TYPE_TDS7_LOGIN, #type STATUS_END_OF_MESSAGE, #status 0x0000, #length 0x0000, # SPID - 0x01, # PacketID (unused upon specification + 0x01, # PacketID (unused upon specification # but ms network monitor stil prefer 1 to decode correctly, wireshark don't care) - 0x00 #Window + 0x00 #Window ] pkt << [ @@ -431,19 +414,18 @@ module Exploit::Remote::MSSQL sname = Rex::Text.to_unicode( rhost ) dname = Rex::Text.to_unicode( db ) - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags) + ntlm_client = ::Net::NTLM::Client.new( + user, + pass, + workstation: workstation_name, + domain: datastore['DOMAIN'], + ) + type1 = ntlm_client.init_context + # SQL 2012, at least, does not support KEY_EXCHANGE + type1.flag &= ~ ::Net::NTLM::FLAGS[:KEY_EXCHANGE] + ntlmsspblob = type1.serialize idx = pkt.size + 50 # lengths below @@ -484,9 +466,9 @@ module Exploit::Remote::MSSQL pkt << ntlmsspblob # Total packet length - pkt[0,4] = [pkt.length].pack('V') + pkt[0, 4] = [pkt.length].pack('V') - pkt_hdr[2] = pkt.length + 8 + pkt_hdr[2] = pkt.length + 8 pkt = pkt_hdr.pack("CCnnCC") + pkt @@ -494,56 +476,36 @@ module Exploit::Remote::MSSQL # has a strange behavior that differs from the specifications # upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header # is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification - resp = mssql_send_recv(pkt,15, false) + resp = mssql_send_recv(pkt, 15, false) - # Get default data - begin - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp) - # a domain.length < 3 will hit this - rescue NTLM_XCEPT::NTLMMissingChallenge + unless resp.include?("NTLMSSP") info = {:errors => []} mssql_parse_reply(resp, info) mssql_print_reply(info) return false end - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - default_name = blob_data[:default_name] || '' - #netbios domain - default_domain = blob_data[:default_domain] || '' - #dns name - dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - - ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags) + # Get default data + resp = resp[3..-1] + type3 = ntlm_client.init_context([resp].pack('m')) + type3_blob = type3.serialize # Create an SSPIMessage idx = 0 pkt = '' pkt_hdr = '' - pkt_hdr = [ - TYPE_SSPI_MESSAGE, #type - STATUS_END_OF_MESSAGE, #status - 0x0000, #length - 0x0000, # SPID - 0x01, # PacketID - 0x00 #Window - ] + pkt_hdr = [ + TYPE_SSPI_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x01, # PacketID + 0x00 #Window + ] - pkt_hdr[2] = ntlmssp.length + 8 + pkt_hdr[2] = type3_blob.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + ntlmssp + pkt = pkt_hdr.pack("CCnnCC") + type3_blob resp = mssql_send_recv(pkt) @@ -620,7 +582,7 @@ module Exploit::Remote::MSSQL pkt << dname # Total packet length - pkt[0,4] = [pkt.length].pack('V') + pkt[0, 4] = [pkt.length].pack('V') # Embedded packet lengths pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2 @@ -637,7 +599,7 @@ module Exploit::Remote::MSSQL end info = {:errors => []} - info = mssql_parse_reply(resp,info) + info = mssql_parse_reply(resp, info) return false if not info info[:login_ack] ? true : false @@ -690,17 +652,17 @@ module Exploit::Remote::MSSQL print_status("SQL Query: #{info[:sql]}") - if(info[:done] and info[:done][:rows].to_i > 0) + if info[:done] && info[:done][:rows].to_i > 0 print_status("Row Count: #{info[:done][:rows]} (Status: #{info[:done][:status]} Command: #{info[:done][:cmd]})") end - if(info[:errors] and not info[:errors].empty?) + if info[:errors] && !info[:errors].empty? info[:errors].each do |err| print_error(err) end end - if(info[:rows] and not info[:rows].empty?) + if info[:rows] && !info[:rows].empty? tbl = Rex::Ui::Text::Table.new( 'Indent' => 1, @@ -727,14 +689,14 @@ module Exploit::Remote::MSSQL info[:colnames] ||= [] # Parse out the columns - cols = data.slice!(0,2).unpack('v')[0] + cols = data.slice!(0, 2).unpack('v')[0] 0.upto(cols-1) do |col_idx| col = {} info[:colinfos][col_idx] = col - col[:utype] = data.slice!(0,2).unpack('v')[0] - col[:flags] = data.slice!(0,2).unpack('v')[0] - col[:type] = data.slice!(0,1).unpack('C')[0] + col[:utype] = data.slice!(0, 2).unpack('v')[0] + col[:flags] = data.slice!(0, 2).unpack('v')[0] + col[:type] = data.slice!(0, 1).unpack('C')[0] case col[:type] when 48 @@ -751,8 +713,8 @@ module Exploit::Remote::MSSQL when 34 col[:id] = :image - col[:max_size] = data.slice!(0,4).unpack('V')[0] - col[:value_length] = data.slice!(0,2).unpack('v')[0] + col[:max_size] = data.slice!(0, 4).unpack('V')[0] + col[:value_length] = data.slice!(0, 2).unpack('v')[0] col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '') when 36 @@ -760,33 +722,33 @@ module Exploit::Remote::MSSQL when 38 col[:id] = :int - col[:int_size] = data.slice!(0,1).unpack('C')[0] + col[:int_size] = data.slice!(0, 1).unpack('C')[0] when 127 col[:id] = :bigint when 165 col[:id] = :hex - col[:max_size] = data.slice!(0,2).unpack('v')[0] + col[:max_size] = data.slice!(0, 2).unpack('v')[0] when 173 col[:id] = :hex # binary(2) - col[:max_size] = data.slice!(0,2).unpack('v')[0] + col[:max_size] = data.slice!(0, 2).unpack('v')[0] - when 231,175,167,239 + when 231, 175, 167, 239 col[:id] = :string - col[:max_size] = data.slice!(0,2).unpack('v')[0] - col[:codepage] = data.slice!(0,2).unpack('v')[0] - col[:cflags] = data.slice!(0,2).unpack('v')[0] - col[:charset_id] = data.slice!(0,1).unpack('C')[0] + col[:max_size] = data.slice!(0, 2).unpack('v')[0] + col[:codepage] = data.slice!(0, 2).unpack('v')[0] + col[:cflags] = data.slice!(0, 2).unpack('v')[0] + col[:charset_id] = data.slice!(0, 1).unpack('C')[0] else col[:id] = :unknown end - col[:msg_len] = data.slice!(0,1).unpack('C')[0] + col[:msg_len] = data.slice!(0, 1).unpack('C')[0] - if(col[:msg_len] and col[:msg_len] > 0) + if col[:msg_len] && col[:msg_len] > 0 col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '') end info[:colnames] << (col[:name] || 'NULL') @@ -800,7 +762,7 @@ module Exploit::Remote::MSSQL info[:errors] = [] return if not data until data.empty? - token = data.slice!(0,1).unpack('C')[0] + token = data.slice!(0, 1).unpack('C')[0] case token when 0x81 mssql_parse_tds_reply(data, info) @@ -844,28 +806,28 @@ module Exploit::Remote::MSSQL case col[:id] when :hex str = "" - len = data.slice!(0,2).unpack('v')[0] - if(len > 0 and len < 65535) - str << data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + if len > 0 && len < 65535 + str << data.slice!(0, len) end row << str.unpack("H*")[0] when :string str = "" - len = data.slice!(0,2).unpack('v')[0] - if(len > 0 and len < 65535) - str << data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + if len > 0 && len < 65535 + str << data.slice!(0, len) end row << str.gsub("\x00", '') when :datetime - row << data.slice!(0,8).unpack("H*")[0] + row << data.slice!(0, 8).unpack("H*")[0] when :rawint - row << data.slice!(0,4).unpack('V')[0] + row << data.slice!(0, 4).unpack('V')[0] when :bigint - row << data.slice!(0,8).unpack("H*")[0] + row << data.slice!(0, 8).unpack("H*")[0] when :smallint row << data.slice!(0, 2).unpack("v")[0] @@ -878,16 +840,16 @@ module Exploit::Remote::MSSQL when :image str = '' - len = data.slice!(0,1).unpack('C')[0] - str = data.slice!(0,len) if (len and len > 0) + len = data.slice!(0, 1).unpack('C')[0] + str = data.slice!(0, len) if len && len > 0 row << str.unpack("H*")[0] when :int len = data.slice!(0, 1).unpack("C")[0] - raw = data.slice!(0, len) if (len and len > 0) + raw = data.slice!(0, len) if len && len > 0 case len - when 0,255 + when 0, 255 row << '' when 1 row << raw.unpack("C")[0] @@ -900,7 +862,7 @@ module Exploit::Remote::MSSQL when 8 row << raw.unpack('VV')[0] # XXX: missing high dword else - info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}" + info[:errors] << "invalid integer size: #{len} #{data[0, 16].unpack("H*")[0]}" end else info[:errors] << "unknown column type: #{col.inspect}" @@ -915,7 +877,7 @@ module Exploit::Remote::MSSQL # Parse a "ret" TDS token # def mssql_parse_ret(data, info) - ret = data.slice!(0,4).unpack('N')[0] + ret = data.slice!(0, 4).unpack('N')[0] info[:ret] = ret info end @@ -924,7 +886,7 @@ module Exploit::Remote::MSSQL # Parse a "done" TDS token # def mssql_parse_done(data, info) - status,cmd,rows = data.slice!(0,8).unpack('vvV') + status, cmd, rows = data.slice!(0, 8).unpack('vvV') info[:done] = { :status => status, :cmd => cmd, :rows => rows } info end @@ -933,11 +895,11 @@ module Exploit::Remote::MSSQL # Parse an "error" TDS token # def mssql_parse_error(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + buff = data.slice!(0, len) - errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') - emsg = buff.slice!(0,elen * 2) + errno, state, sev, elen = buff.slice!(0, 8).unpack('VCCv') + emsg = buff.slice!(0, elen * 2) emsg.gsub!("\x00", '') info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" @@ -948,17 +910,17 @@ module Exploit::Remote::MSSQL # Parse an "environment change" TDS token # def mssql_parse_env(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) - type = buff.slice!(0,1).unpack('C')[0] + len = data.slice!(0, 2).unpack('v')[0] + buff = data.slice!(0, len) + type = buff.slice!(0, 1).unpack('C')[0] nval = '' - nlen = buff.slice!(0,1).unpack('C')[0] || 0 - nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0 + nlen = buff.slice!(0, 1).unpack('C')[0] || 0 + nval = buff.slice!(0, nlen * 2).gsub("\x00", '') if nlen > 0 oval = '' - olen = buff.slice!(0,1).unpack('C')[0] || 0 - oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0 + olen = buff.slice!(0, 1).unpack('C')[0] || 0 + oval = buff.slice!(0, olen * 2).gsub("\x00", '') if olen > 0 info[:envs] ||= [] info[:envs] << { :type => type, :old => oval, :new => nval } @@ -969,14 +931,14 @@ module Exploit::Remote::MSSQL # Parse an "information" TDS token # def mssql_parse_info(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + buff = data.slice!(0, len) - errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') - emsg = buff.slice!(0,elen * 2) + errno, state, sev, elen = buff.slice!(0, 8).unpack('VCCv') + emsg = buff.slice!(0, elen * 2) emsg.gsub!("\x00", '') - info[:infos]||= [] + info[:infos] ||= [] info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" info end @@ -985,8 +947,8 @@ module Exploit::Remote::MSSQL # Parse a "login ack" TDS token # def mssql_parse_login_ack(data, info) - len = data.slice!(0,2).unpack('v')[0] - buff = data.slice!(0,len) + len = data.slice!(0, 2).unpack('v')[0] + _buff = data.slice!(0, len) info[:login_ack] = true end end diff --git a/lib/msf/core/exploit/ntlm.rb b/lib/msf/core/exploit/ntlm.rb index ff9f573a18..cd20619a21 100644 --- a/lib/msf/core/exploit/ntlm.rb +++ b/lib/msf/core/exploit/ntlm.rb @@ -17,12 +17,6 @@ module Msf module Exploit::NTLM - NTLM_CONST = ::Rex::Proto::NTLM::Constants - NTLM_CRYPT = ::Rex::Proto::NTLM::Crypt - NTLM_UTILS = ::Rex::Proto::NTLM::Utils - NTLM_BASE = ::Rex::Proto::NTLM::Base - NTLM_MESSAGE = ::Rex::Proto::NTLM::Message - module Client def initialize(info = {}) super diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 3af1e4080d..81b5d83ad0 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -3,10 +3,6 @@ require 'rex/socket' require 'rex/proto/http' require 'rex/text' require 'digest' -require 'rex/proto/ntlm/crypt' -require 'rex/proto/ntlm/constants' -require 'rex/proto/ntlm/utils' -require 'rex/proto/ntlm/exceptions' require 'rex/proto/http/client_request' @@ -313,7 +309,6 @@ class Client # Send a series of requests to complete Digest Authentication # # @param opts [Hash] the options used to build an HTTP request - # # @return [Response] the last valid HTTP response we received def digest_auth(opts={}) @nonce_count = 0 @@ -457,13 +452,6 @@ class Client # # @return [Response] the last valid HTTP response we received def negotiate_auth(opts={}) - ntlm_options = { - :signing => false, - :usentlm2_session => self.config['usentlm2_session'], - :use_ntlmv2 => self.config['use_ntlmv2'], - :send_lm => self.config['send_lm'], - :send_ntlm => self.config['send_ntlm'] - } to = opts['timeout'] || 20 opts['username'] ||= '' @@ -472,28 +460,27 @@ class Client if opts['provider'] and opts['provider'].include? 'Negotiate' provider = "Negotiate " else - provider = 'NTLM ' + provider = "NTLM " end opts['method']||= 'GET' opts['headers']||= {} - ntlmssp_flags = ::Rex::Proto::NTLM::Utils.make_ntlm_flags(ntlm_options) workstation_name = Rex::Text.rand_text_alpha(rand(8)+6) domain_name = self.config['domain'] - b64_blob = Rex::Text::encode_base64( - ::Rex::Proto::NTLM::Utils::make_ntlmssp_blob_init( - domain_name, - workstation_name, - ntlmssp_flags - )) - - ntlm_message_1 = provider + b64_blob + ntlm_client = ::Net::NTLM::Client.new( + opts['username'], + opts['password'], + workstation: workstation_name, + domain: domain_name, + ) + type1 = ntlm_client.init_context begin # First request to get the challenge - opts['headers']['Authorization'] = ntlm_message_1 + opts['headers']['Authorization'] = provider + type1.encode64 + r = request_cgi(opts) resp = _send_recv(r, to) unless resp.kind_of? Rex::Proto::Http::Response @@ -506,47 +493,10 @@ class Client ntlm_challenge = resp.headers['WWW-Authenticate'].scan(/#{provider}([A-Z0-9\x2b\x2f=]+)/ni).flatten[0] return resp unless ntlm_challenge - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = ::Rex::Proto::NTLM::Utils.parse_ntlm_type_2_blob(ntlm_message_2) - - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - default_name = blob_data[:default_name] || '' #netbios name - default_domain = blob_data[:default_domain] || '' #netbios domain - dns_host_name = blob_data[:dns_host_name] || '' #dns name - dns_domain_name = blob_data[:dns_domain_name] || '' #dns domain - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' #Client time - - spnopt = {:use_spn => self.config['SendSPN'], :name => self.hostname} - - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = ::Rex::Proto::NTLM::Utils.create_lm_ntlm_responses( - opts['username'], - opts['password'], - challenge_key, - domain_name, - default_name, - default_domain, - dns_host_name, - dns_domain_name, - chall_MsvAvTimestamp, - spnopt, - ntlm_options - ) - - ntlm_message_3 = ::Rex::Proto::NTLM::Utils.make_ntlmssp_blob_auth( - domain_name, - workstation_name, - opts['username'], - resp_lm, - resp_ntlm, - '', - ntlmssp_flags - ) - - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + ntlm_message_3 = ntlm_client.init_context(ntlm_challenge) # Send the response - opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" + opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3.encode64}" r = request_cgi(opts) resp = _send_recv(r, to, true) unless resp.kind_of? Rex::Proto::Http::Response @@ -558,6 +508,7 @@ class Client return nil end end + # # Read a response from the server # @@ -713,7 +664,6 @@ protected attr_accessor :hostname, :port # :nodoc: - end end diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index f3a81c5c60..a587dd24b4 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -4,6 +4,8 @@ module Proto module SMB class Client + require 'net/ntlm' + require 'rex/text' require 'rex/struct2' require 'rex/proto/smb/constants' @@ -166,14 +168,14 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Scan the packet receive cache for a matching response def smb_recv_cache_find_match(expected_type) - + clean = [] found = nil @smb_recv_cache.each do |cent| pkt, data, tstamp = cent - # Return matching packets and mark for removal + # Return matching packets and mark for removal if pkt['Payload']['SMB'].v['Command'] == expected_type found = [pkt,data] clean << cent @@ -623,16 +625,15 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Authenticate and establish a session - def session_setup(*args) - + def session_setup(user='', pass='', domain='', do_recv=true) if (self.dialect =~ /^(NT LANMAN 1.0|NT LM 0.12)$/) if (self.challenge_key) - return self.session_setup_no_ntlmssp(*args) + return self.session_setup_no_ntlmssp(user, pass, domain, do_recv) end if ( self.extended_security ) - return self.session_setup_with_ntlmssp(*args) + return self.session_setup_with_ntlmssp(user, pass, domain, nil, do_recv) end end @@ -831,7 +832,16 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils name = Rex::Text.rand_text_alphanumeric(16) end - blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name, ntlmssp_flags) + @ntlm_client = Net::NTLM::Client.new( + user, + pass, + workstation: name, + domain: domain, + flags: ntlmssp_flags + ) + + + blob = @ntlm_client.init_context.serialize native_data = '' native_data << self.native_os + "\x00" @@ -892,37 +902,9 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Save the temporary UserID for use in the next request temp_user_id = ack['Payload']['SMB'].v['UserID'] - # Get default data - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(blob) - self.challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - self.default_name = blob_data[:default_name] || '' - #netbios domain - self.default_domain = blob_data[:default_domain] || '' - #dns name - self.dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - self.dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - - - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, self.challenge_key, domain, - default_name, default_domain, dns_host_name, - dns_domain_name, chall_MsvAvTimestamp , - self.spnopt, ntlm_options) - enc_session_key = '' - self.sequence_counter = 0 - - if self.require_signing - self.signing_key, enc_session_key, ntlmssp_flags = NTLM_UTILS.create_session_key(ntlmssp_flags, server_ntlmssp_flags, user, pass, domain, - self.challenge_key, client_challenge, ntlm_cli_challenge, - ntlm_options) - end - - # Create the security blob data - blob = NTLM_UTILS.make_ntlmssp_secblob_auth(domain, name, user, resp_lm, resp_ntlm, enc_session_key, ntlmssp_flags) + type3 = @ntlm_client.init_context([blob].pack('m')) + type3_blob = type3.serialize + self.signing_key = @ntlm_client.session_key pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct self.smb_defaults(pkt['Payload']['SMB']) @@ -944,8 +926,8 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils pkt['Payload'].v['VCNum'] = 1 pkt['Payload'].v['Capabilities'] = 0x8000d05c pkt['Payload'].v['SessionKey'] = self.session_id - pkt['Payload'].v['SecurityBlobLen'] = blob.length - pkt['Payload'].v['Payload'] = blob + native_data + pkt['Payload'].v['SecurityBlobLen'] = type3_blob.length + pkt['Payload'].v['Payload'] = type3_blob + native_data # NOTE: if do_recv is set to false, we cant reach here... self.smb_send(pkt.to_s) @@ -1771,7 +1753,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Remove the NetBIOS header resp_rpkt.slice!(0, 4) - resp_parm = resp_rpkt[poff, pcnt] + _resp_parm = resp_rpkt[poff, pcnt] resp_data = resp_rpkt[doff, dcnt] return resp_data @@ -1797,7 +1779,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Remove the NetBIOS header resp_rpkt.slice!(0, 4) - resp_parm = resp_rpkt[poff, pcnt] + _resp_parm = resp_rpkt[poff, pcnt] resp_data = resp_rpkt[doff, dcnt] return resp_data @@ -1958,7 +1940,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils resp = find_next(last_search_id, last_offset, last_filename) # Flip bit so response params will parse correctly - search_next = 1 + search_next = 1 end files @@ -1973,7 +1955,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils 260, # Level of interest resume_key, # Resume key from previous (Last name offset) 6, # Close search if end of search - ].pack('vvvVv') + + ].pack('vvvVv') + last_filename.to_s + # Last filename returned from find_first or find_next "\x00" # Terminate the file name @@ -2006,7 +1988,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils search_path = "#{current_path}#{fname}\\" file_search(search_path, regex, depth).each {|fn| files << fn } rescue Rex::Proto::SMB::Exceptions::ErrorCode => e - + # Ignore common errors related to permissions and non-files if %W{ STATUS_ACCESS_DENIED @@ -2030,9 +2012,9 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Creates a new directory on the mounted tree def create_directory(name) - files = { } parm = [0].pack('V') + name + "\x00" resp = trans2(CONST::TRANS2_CREATE_DIRECTORY, parm, '') + resp end # public read/write methods diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 28dc816820..166150065a 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -75,6 +75,8 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'msgpack' # get list of network interfaces, like eth* from OS. spec.add_runtime_dependency 'network_interface' + # NTLM authentication + spec.add_runtime_dependency 'rubyntlm' # Needed by anemone crawler spec.add_runtime_dependency 'nokogiri' # Needed by db.rb and Msf::Exploit::Capture diff --git a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb index dd9d13985d..404bab4757 100644 --- a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb @@ -31,7 +31,7 @@ class MetasploitModule < Msf::Auxiliary def run_host(ip) if !mssql_login_datastore - print_error("#{rhost}:#{rport} - Invalid SQL Server credentials") + print_error("Invalid SQL Server credentials") return end @@ -150,7 +150,7 @@ class MetasploitModule < Msf::Auxiliary login = create_credential_login(login_data) tbl << [row[0], row[1]] - print_good("#{rhost}:#{rport} - Saving #{hashtype} = #{row[0]}:#{row[1]}") + print_good("Saving #{hashtype} = #{row[0]}:#{row[1]}") end end @@ -160,7 +160,7 @@ class MetasploitModule < Msf::Auxiliary is_sysadmin = mssql_query(mssql_is_sysadmin())[:rows][0][0] if is_sysadmin == 0 - print_error("#{rhost}:#{rport} - The provided credentials do not have privileges to read the password hashes") + print_error("The provided credentials do not have privileges to read the password hashes") return nil end