From 1164c025a234ae66db02986a1d4262c62121bcf3 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 5 Jul 2016 15:22:44 -0500 Subject: [PATCH] Revert "Land #7009, egypt's rubyntlm cleanup" This reverts commit d90f0779f80ae126ba64228f07890cc1f6a58898, reversing changes made to e3e360cc83f1e2512663e974f121fb14a75aae29. --- Gemfile.lock | 1 - lib/metasploit/framework/mssql/client.rb | 283 +++++++------ lib/msf/core/exploit/http/client.rb | 25 ++ lib/msf/core/exploit/mssql.rb | 380 ++++++++++-------- lib/msf/core/exploit/ntlm.rb | 6 + lib/rex/proto/http/client.rb | 76 +++- lib/rex/proto/smb/client.rb | 74 ++-- metasploit-framework.gemspec | 2 - .../auxiliary/scanner/mssql/mssql_hashdump.rb | 6 +- 9 files changed, 509 insertions(+), 344 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 04b61fc948..72d63ae86b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,7 +36,6 @@ 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 4c79a8eb0e..acfed80b88 100644 --- a/lib/metasploit/framework/mssql/client.rb +++ b/lib/metasploit/framework/mssql/client.rb @@ -9,6 +9,11 @@ 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. @@ -16,23 +21,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+ # @@ -50,14 +55,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 << [ @@ -80,18 +85,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) - 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 + ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags) idx = pkt.size + 50 # lengths below @@ -132,9 +137,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 @@ -142,38 +147,64 @@ 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, 15, false) + resp = proxy.send_recv(pkt) else - resp = mssql_send_recv(pkt, 15, false) + resp = mssql_send_recv(pkt) end - # Strip the TDS header - resp = resp[3..-1] - type3 = ntlm_client.init_context([resp].pack('m')) - type3_blob = type3.serialize + # 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) # 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] = type3_blob.length + 8 + pkt_hdr[2] = ntlmssp.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + type3_blob + pkt = pkt_hdr.pack("CCnnCC") + ntlmssp if self.tdsencryption == true - resp = mssql_ssl_send_recv(pkt, proxy) + resp = mssql_ssl_send_recv(pkt,proxy) proxy.cleanup proxy = nil else @@ -252,7 +283,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 @@ -263,7 +294,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 @@ -273,7 +304,7 @@ module Metasploit end info = {:errors => []} - info = mssql_parse_reply(resp, info) + info = mssql_parse_reply(resp,info) disconnect @@ -285,17 +316,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 } @@ -306,7 +337,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 @@ -315,7 +346,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 @@ -324,11 +355,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}" @@ -339,14 +370,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 @@ -355,8 +386,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 @@ -367,7 +398,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) @@ -403,14 +434,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 @@ -427,8 +458,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 @@ -436,31 +467,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", '') @@ -486,28 +517,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] @@ -520,8 +551,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 @@ -529,7 +560,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] @@ -542,7 +573,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}" @@ -564,7 +595,7 @@ module Metasploit pkt_data = "" - pkt_hdr = [ + pkt_hdr = [ TYPE_PRE_LOGIN_MESSAGE, #type STATUS_END_OF_MESSAGE, #status 0x0000, #length @@ -573,7 +604,7 @@ module Metasploit 0x00 #Window ] - version = [0x55010008, 0x0000].pack("Vv") + version = [0x55010008,0x0000].pack("Vv") # if manually set, we will honour if tdsencryption == true @@ -584,45 +615,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 && resp[0, 1] != "\xff" && resp.length > 5 - token = resp.slice!(0, 5) + 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 @@ -632,7 +663,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" @@ -670,8 +701,8 @@ module Metasploit idx = 0 - while resp && resp[0, 1] != "\xff" && resp.length > 5 - token = resp.slice!(0, 5) + 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 @@ -680,7 +711,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" @@ -704,17 +735,17 @@ module Metasploit while(not done) head = sock.get_once(8, timeout) - if !(head && head.length == 8) + if !(head and head.length == 8) return false end # Is this the last buffer? - if head[1, 1] == "\x01" || !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) @@ -727,7 +758,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 687e6eb4a6..5d6636e155 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -2,6 +2,10 @@ 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 ### @@ -12,6 +16,15 @@ 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 @@ -180,6 +193,12 @@ 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'] ) @@ -236,6 +255,12 @@ 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 813592e43b..77eacf5b4e 100644 --- a/lib/msf/core/exploit/mssql.rb +++ b/lib/msf/core/exploit/mssql.rb @@ -1,6 +1,11 @@ # -*- 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 @@ -16,32 +21,41 @@ 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. - # 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 + # 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 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. # @@ -86,13 +100,16 @@ 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 @@ -128,15 +145,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] && !res[:errors].empty? - if res[:errors].join =~ /xp_cmdshell/ - if force_enable + if(res[:errors] and not 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()) @@ -150,7 +167,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 @@ -183,7 +200,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 @@ -217,7 +234,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...") @@ -243,17 +260,17 @@ module Exploit::Remote::MSSQL while(not done) head = sock.get_once(8, timeout) - if !(head && head.length == 8) + if !(head and 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) @@ -285,77 +302,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 && 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 + 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 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 - end - - if encryption_mode != ENCRYPT_NOT_SUP && enc_error - raise RuntimeError,"Encryption is not supported" - end - encryption_mode + if encryption_mode != ENCRYPT_NOT_SUP and enc_error + raise RuntimeError,"Encryption is not supported" + end + encryption_mode end # @@ -384,14 +401,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 << [ @@ -414,18 +431,19 @@ module Exploit::Remote::MSSQL sname = Rex::Text.to_unicode( rhost ) dname = Rex::Text.to_unicode( db ) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + 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'] + } - 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 + 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) idx = pkt.size + 50 # lengths below @@ -466,9 +484,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 @@ -476,36 +494,56 @@ 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) - unless resp.include?("NTLMSSP") + # 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 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] || '' - # Get default data - resp = resp[3..-1] - type3 = ntlm_client.init_context([resp].pack('m')) - type3_blob = type3.serialize + 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) # 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] = type3_blob.length + 8 + pkt_hdr[2] = ntlmssp.length + 8 - pkt = pkt_hdr.pack("CCnnCC") + type3_blob + pkt = pkt_hdr.pack("CCnnCC") + ntlmssp resp = mssql_send_recv(pkt) @@ -582,7 +620,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 @@ -599,7 +637,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 @@ -652,17 +690,17 @@ module Exploit::Remote::MSSQL print_status("SQL Query: #{info[:sql]}") - if info[:done] && info[:done][:rows].to_i > 0 + if(info[:done] and 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] && !info[:errors].empty? + if(info[:errors] and not info[:errors].empty?) info[:errors].each do |err| print_error(err) end end - if info[:rows] && !info[:rows].empty? + if(info[:rows] and not info[:rows].empty?) tbl = Rex::Ui::Text::Table.new( 'Indent' => 1, @@ -689,14 +727,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 @@ -713,8 +751,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 @@ -722,33 +760,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] && col[:msg_len] > 0 + if(col[:msg_len] and col[:msg_len] > 0) col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '') end info[:colnames] << (col[:name] || 'NULL') @@ -762,7 +800,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) @@ -806,28 +844,28 @@ module Exploit::Remote::MSSQL case col[:id] when :hex str = "" - len = data.slice!(0, 2).unpack('v')[0] - if len > 0 && len < 65535 - str << data.slice!(0, len) + len = data.slice!(0,2).unpack('v')[0] + if(len > 0 and 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 && len < 65535 - str << data.slice!(0, len) + len = data.slice!(0,2).unpack('v')[0] + if(len > 0 and 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] @@ -840,16 +878,16 @@ module Exploit::Remote::MSSQL when :image str = '' - len = data.slice!(0, 1).unpack('C')[0] - str = data.slice!(0, len) if len && 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 len = data.slice!(0, 1).unpack("C")[0] - raw = data.slice!(0, len) if len && len > 0 + 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] @@ -862,7 +900,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}" @@ -877,7 +915,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 @@ -886,7 +924,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 @@ -895,11 +933,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}" @@ -910,17 +948,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 } @@ -931,14 +969,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 @@ -947,8 +985,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 cd20619a21..ff9f573a18 100644 --- a/lib/msf/core/exploit/ntlm.rb +++ b/lib/msf/core/exploit/ntlm.rb @@ -17,6 +17,12 @@ 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 81b5d83ad0..3af1e4080d 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -3,6 +3,10 @@ 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' @@ -309,6 +313,7 @@ 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 @@ -452,6 +457,13 @@ 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'] ||= '' @@ -460,27 +472,28 @@ 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'] - ntlm_client = ::Net::NTLM::Client.new( - opts['username'], - opts['password'], - workstation: workstation_name, - domain: domain_name, - ) - type1 = ntlm_client.init_context + 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 begin # First request to get the challenge - opts['headers']['Authorization'] = provider + type1.encode64 - + opts['headers']['Authorization'] = ntlm_message_1 r = request_cgi(opts) resp = _send_recv(r, to) unless resp.kind_of? Rex::Proto::Http::Response @@ -493,10 +506,47 @@ 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_3 = ntlm_client.init_context(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) # Send the response - opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3.encode64}" + opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" r = request_cgi(opts) resp = _send_recv(r, to, true) unless resp.kind_of? Rex::Proto::Http::Response @@ -508,7 +558,6 @@ class Client return nil end end - # # Read a response from the server # @@ -664,6 +713,7 @@ 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 a587dd24b4..f3a81c5c60 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -4,8 +4,6 @@ module Proto module SMB class Client - require 'net/ntlm' - require 'rex/text' require 'rex/struct2' require 'rex/proto/smb/constants' @@ -168,14 +166,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 @@ -625,15 +623,16 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Authenticate and establish a session - def session_setup(user='', pass='', domain='', do_recv=true) + def session_setup(*args) + if (self.dialect =~ /^(NT LANMAN 1.0|NT LM 0.12)$/) if (self.challenge_key) - return self.session_setup_no_ntlmssp(user, pass, domain, do_recv) + return self.session_setup_no_ntlmssp(*args) end if ( self.extended_security ) - return self.session_setup_with_ntlmssp(user, pass, domain, nil, do_recv) + return self.session_setup_with_ntlmssp(*args) end end @@ -832,16 +831,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils name = Rex::Text.rand_text_alphanumeric(16) end - @ntlm_client = Net::NTLM::Client.new( - user, - pass, - workstation: name, - domain: domain, - flags: ntlmssp_flags - ) - - - blob = @ntlm_client.init_context.serialize + blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name, ntlmssp_flags) native_data = '' native_data << self.native_os + "\x00" @@ -902,9 +892,37 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Save the temporary UserID for use in the next request temp_user_id = ack['Payload']['SMB'].v['UserID'] - type3 = @ntlm_client.init_context([blob].pack('m')) - type3_blob = type3.serialize - self.signing_key = @ntlm_client.session_key + # 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) pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct self.smb_defaults(pkt['Payload']['SMB']) @@ -926,8 +944,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'] = type3_blob.length - pkt['Payload'].v['Payload'] = type3_blob + native_data + pkt['Payload'].v['SecurityBlobLen'] = blob.length + pkt['Payload'].v['Payload'] = blob + native_data # NOTE: if do_recv is set to false, we cant reach here... self.smb_send(pkt.to_s) @@ -1753,7 +1771,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 @@ -1779,7 +1797,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 @@ -1940,7 +1958,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 @@ -1955,7 +1973,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 @@ -1988,7 +2006,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 @@ -2012,9 +2030,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 166150065a..28dc816820 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -75,8 +75,6 @@ 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 404bab4757..dd9d13985d 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("Invalid SQL Server credentials") + print_error("#{rhost}:#{rport} - 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("Saving #{hashtype} = #{row[0]}:#{row[1]}") + print_good("#{rhost}:#{rport} - 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("The provided credentials do not have privileges to read the password hashes") + print_error("#{rhost}:#{rport} - The provided credentials do not have privileges to read the password hashes") return nil end