diff --git a/lib/ruby_smb/gss/provider/ntlm.rb b/lib/ruby_smb/gss/provider/ntlm.rb index 6def51ca..542596c2 100644 --- a/lib/ruby_smb/gss/provider/ntlm.rb +++ b/lib/ruby_smb/gss/provider/ntlm.rb @@ -194,10 +194,23 @@ def process_gss_type1(gss_api) # take the GSS blob, extract the NTLM type 3 message and pass it to the process method to build the response # which is then put back into a new GSS reply-blob def process_gss_type3(gss_api) - neg_token_init = Hash[RubySMB::Gss.asn1dig(gss_api, 0).value.map { |obj| [obj.tag, obj.value[0].value] }] + # a NegTokenResp carries every field as OPTIONAL (RFC 4178 section 4.2.2), and a tagged + # element may be empty, so a response_token is only present when a tag-[2] field exists + # and wraps a value + neg_token_init = {} + RubySMB::Gss.asn1dig(gss_api, 0).value.each do |obj| + inner = obj.value.is_a?(Array) ? obj.value[0] : nil + neg_token_init[obj.tag] = inner.value if inner.respond_to?(:value) + end raw_type3_msg = neg_token_init[2] + return if raw_type3_msg.nil? - type3_msg = Net::NTLM::Message.parse(raw_type3_msg) + begin + type3_msg = Net::NTLM::Message.parse(raw_type3_msg) + rescue StandardError => e + logger.error("Failed to parse the NTLM type 3 message (#{e.class}: #{e.message})") + return + end if type3_msg.flag & NTLM::NEGOTIATE_FLAGS[:UNICODE] == NTLM::NEGOTIATE_FLAGS[:UNICODE] type3_msg.domain.force_encoding('UTF-16LE') type3_msg.user.force_encoding('UTF-16LE') diff --git a/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb b/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb index b8c539bd..193de2f8 100644 --- a/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +++ b/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb @@ -59,6 +59,61 @@ expect(result.identity).to be_nil expect(authenticator.session_key).to be_nil end + + context 'when the NegTokenResp is malformed' do + def wrap_neg_token_resp(fields) + OpenSSL::ASN1::ASN1Data.new( + [OpenSSL::ASN1::Sequence.new(fields)], + 1, + :CONTEXT_SPECIFIC + ).to_der + end + + it 'returns nil for an empty inner sequence' do + expect { authenticator.process(wrap_neg_token_resp([])) }.not_to raise_error + expect(authenticator.process(wrap_neg_token_resp([]))).to be_nil + end + + it 'returns nil when the response_token is absent' do + buf = wrap_neg_token_resp([ + OpenSSL::ASN1::ASN1Data.new( + [OpenSSL::ASN1::Enumerated.new(OpenSSL::BN.new(1))], + 0, + :CONTEXT_SPECIFIC + ) + ]) + expect(authenticator.process(buf)).to be_nil + end + + it 'returns nil when only mech_list_mic is present' do + buf = wrap_neg_token_resp([ + OpenSSL::ASN1::ASN1Data.new( + [OpenSSL::ASN1::OctetString.new('AAAA')], + 3, + :CONTEXT_SPECIFIC + ) + ]) + expect(authenticator.process(buf)).to be_nil + end + + it 'returns nil when the response_token is empty' do + buf = wrap_neg_token_resp([ + OpenSSL::ASN1::ASN1Data.new([], 2, :CONTEXT_SPECIFIC) + ]) + expect(authenticator.process(buf)).to be_nil + end + + it 'returns nil when the response_token is not an NTLM message' do + buf = wrap_neg_token_resp([ + OpenSSL::ASN1::ASN1Data.new( + [OpenSSL::ASN1::OctetString.new('not-ntlm-bytes')], + 2, + :CONTEXT_SPECIFIC + ) + ]) + expect(authenticator.process(buf)).to be_nil + end + end end describe '#process_ntlm_type1' do