Class: RubySMB::Gss::Provider::NTLM::Authenticator
- Inherits:
-
Authenticator::Base
- Object
- Authenticator::Base
- RubySMB::Gss::Provider::NTLM::Authenticator
- Defined in:
- lib/ruby_smb/gss/provider/ntlm.rb
Instance Attribute Summary collapse
-
#server_challenge ⇒ Object
Returns the value of attribute server_challenge.
Instance Method Summary collapse
- #process(request_buffer = nil) ⇒ Object
-
#process_ntlm_type1(type1_msg) ⇒ Net::NTLM::Message::Type2
Process the NTLM type 1 message and build a type 2 response message.
-
#process_ntlm_type3(type3_msg) ⇒ WindowsError::ErrorCode
Process the NTLM type 3 message and either accept or reject the authentication attempt.
- #reset! ⇒ Object
Instance Attribute Details
#server_challenge ⇒ Object
Returns the value of attribute server_challenge.
192 193 194 |
# File 'lib/ruby_smb/gss/provider/ntlm.rb', line 192 def server_challenge @server_challenge end |
Instance Method Details
#process(request_buffer = nil) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/ruby_smb/gss/provider/ntlm.rb', line 27 def process(request_buffer=nil) if request_buffer.nil? # this is only NTLMSSP (as opposed to SPNEGO + NTLMSSP) buffer = OpenSSL::ASN1::ASN1Data.new([ Gss::OID_SPNEGO, OpenSSL::ASN1::ASN1Data.new([ OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::ASN1Data.new([ OpenSSL::ASN1::Sequence.new([ Gss::OID_NTLMSSP ]) ], 0, :CONTEXT_SPECIFIC), OpenSSL::ASN1::ASN1Data.new([ OpenSSL::ASN1::ASN1Data.new([ OpenSSL::ASN1::ASN1Data.new([ OpenSSL::ASN1::GeneralString.new('not_defined_in_RFC4178@please_ignore') ], 0, :CONTEXT_SPECIFIC) ], 16, :UNIVERSAL) ], 3, :CONTEXT_SPECIFIC) ]) ], 0, :CONTEXT_SPECIFIC) ], 0, :APPLICATION).to_der return Result.new(buffer, WindowsError::NTStatus::STATUS_SUCCESS) end begin gss_api = OpenSSL::ASN1.decode(request_buffer) rescue OpenSSL::ASN1::ASN1Error => e logger.error("Failed to parse the ASN1-encoded authentication request (#{e.})") return end if gss_api&.tag == 0 && gss_api&.tag_class == :APPLICATION result = process_gss_type1(gss_api) elsif gss_api&.tag == 1 && gss_api&.tag_class == :CONTEXT_SPECIFIC result = process_gss_type3(gss_api) end result end |
#process_ntlm_type1(type1_msg) ⇒ Net::NTLM::Message::Type2
Process the NTLM type 1 message and build a type 2 response message.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/ruby_smb/gss/provider/ntlm.rb', line 74 def process_ntlm_type1(type1_msg) type2_msg = Net::NTLM::Message::Type2.new.tap do |msg| msg.target_name = 'LOCALHOST'.encode('UTF-16LE').b msg.flag = 0 %i{ KEY56 KEY128 KEY_EXCHANGE UNICODE TARGET_INFO VERSION_INFO }.each do |flag| msg.flag |= NTLM::NEGOTIATE_FLAGS.fetch(flag) end if type1_msg.flag & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] == NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] msg.flag |= NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] end @server_challenge = @provider.generate_server_challenge msg.challenge = @server_challenge.unpack1('Q<') # 64-bit unsigned, little endian (uint64_t) target_info = Net::NTLM::TargetInfo.new('') target_info.av_pairs.merge!({ Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME => @provider.netbios_domain.encode('UTF-16LE').b, Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME => @provider.netbios_hostname.encode('UTF-16LE').b, Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME => @provider.dns_domain.encode('UTF-16LE').b, Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => @provider.dns_hostname.encode('UTF-16LE').b, Net::NTLM::TargetInfo::MSV_AV_TIMESTAMP => [(Time.now.to_i + Net::NTLM::TIME_OFFSET) * Field::FileTime::NS_MULTIPLIER].pack('Q') }) msg.target_info = target_info.to_s msg.enable(:target_info) msg.context = 0 msg.enable(:context) msg.os_version = NTLM::OSVersion.new(major: 6, minor: 3).to_binary_s msg.enable(:os_version) end type2_msg end |
#process_ntlm_type3(type3_msg) ⇒ WindowsError::ErrorCode
Process the NTLM type 3 message and either accept or reject the authentication attempt.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/ruby_smb/gss/provider/ntlm.rb', line 114 def process_ntlm_type3(type3_msg) if type3_msg.user == '' && type3_msg.domain == '' if @provider.allow_anonymous @session_key = "\x00".b * 16 # see MS-NLMP section 3.4 return WindowsError::NTStatus::STATUS_SUCCESS end return WindowsError::NTStatus::STATUS_LOGON_FAILURE end dbg_string = "#{type3_msg.domain.encode(''.encoding)}\\#{type3_msg.user.encode(''.encoding)}" logger.debug("NTLM authentication request received for #{dbg_string}") account = @provider.get_account( type3_msg.user, domain: type3_msg.domain ) if account.nil? if @provider.allow_guests logger.info("NTLM authentication request succeeded for #{dbg_string} (guest)") @session_key = "\x00".b * 16 # see MS-NLMP section 3.4 return WindowsError::NTStatus::STATUS_SUCCESS end logger.info("NTLM authentication request failed for #{dbg_string} (no account)") return WindowsError::NTStatus::STATUS_LOGON_FAILURE end matches = false case type3_msg.ntlm_version when :ntlmv1 my_ntlm_response = Net::NTLM::ntlm_response( ntlm_hash: Net::NTLM::ntlm_hash( RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'), unicode: true ), challenge: @server_challenge ) matches = my_ntlm_response == type3_msg.ntlm_response when :ntlmv2 digest = OpenSSL::Digest::MD5.new their_nt_proof_str = type3_msg.ntlm_response[0...digest.digest_length] their_blob = type3_msg.ntlm_response[digest.digest_length..-1] ntlmv2_hash = Net::NTLM.ntlmv2_hash( RubySMB::Utils.safe_encode(account.username, 'UTF-16LE'), RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'), RubySMB::Utils.safe_encode(type3_msg.domain, 'UTF-16LE'), # don't use the account domain because of the special '.' value {client_challenge: their_blob[16...24], unicode: true} ) my_nt_proof_str = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, @server_challenge + their_blob) matches = my_nt_proof_str == their_nt_proof_str if matches user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, my_nt_proof_str) if type3_msg.flag & NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] == NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] && type3_msg.session_key.length == 16 rc4 = OpenSSL::Cipher.new('rc4') rc4.decrypt rc4.key = user_session_key @session_key = rc4.update type3_msg.session_key @session_key << rc4.final else @session_key = user_session_key end end else # the only other value Net::NTLM will return for this is ntlm_session raise NotImplementedError, "authentication via ntlm version #{type3_msg.ntlm_version} is not supported" end unless matches logger.info("NTLM authentication request failed for #{dbg_string} (bad password)") return WindowsError::NTStatus::STATUS_LOGON_FAILURE end logger.info("NTLM authentication request succeeded for #{dbg_string}") WindowsError::NTStatus::STATUS_SUCCESS end |
#reset! ⇒ Object
22 23 24 25 |
# File 'lib/ruby_smb/gss/provider/ntlm.rb', line 22 def reset! super @server_challenge = nil end |