diff --git a/lib/net/ntlm/client/session.rb b/lib/net/ntlm/client/session.rb index f2f00e3..44a6ae4 100644 --- a/lib/net/ntlm/client/session.rb +++ b/lib/net/ntlm/client/session.rb @@ -26,8 +26,8 @@ def initialize(client, challenge_message, channel_binding = nil) def authenticate! calculate_user_session_key! type3_opts = { - :lm_response => lmv2_resp, - :ntlm_response => ntlmv2_resp, + :lm_response => is_anonymous? ? "\x00".b : lmv2_resp, + :ntlm_response => is_anonymous? ? '' : ntlmv2_resp, :domain => domain, :user => username, :workstation => workstation, @@ -80,6 +80,10 @@ def unseal_message(emessage) server_cipher.encrypt(emessage) end + def is_anonymous? + username == '' && password == '' + end + private @@ -138,7 +142,8 @@ def timestamp end def use_oem_strings? - challenge_message.has_flag? :OEM + # @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832 + !challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM) end def negotiate_key_exchange? @@ -174,7 +179,12 @@ def ntlmv2_hash end def calculate_user_session_key! - @user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str) + if is_anonymous? + # see MS-NLMP section 3.4 + @user_session_key = "\x00".b * 16 + else + @user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str) + end end def lmv2_resp @@ -212,7 +222,6 @@ def target_info end end end - end end end diff --git a/lib/net/ntlm/message.rb b/lib/net/ntlm/message.rb index eba8bc1..82925c1 100644 --- a/lib/net/ntlm/message.rb +++ b/lib/net/ntlm/message.rb @@ -35,7 +35,6 @@ module NTLM :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY] } - # @private false class Message < FieldSet class << Message @@ -87,7 +86,7 @@ def dump_flags def serialize deflag - super + security_buffers.map{|n, f| f.value}.join + super + security_buffers.map{|n, f| f.value + (has_flag?(:UNICODE) ? "\x00".b * (f.value.length % 2) : '')}.join end def encode64 @@ -117,6 +116,7 @@ def deflag security_buffers.inject(head_size){|cur, a| a[1].offset = cur cur += a[1].data_size + has_flag?(:UNICODE) ? cur + cur % 2 : cur } end diff --git a/spec/lib/net/ntlm/client/session_spec.rb b/spec/lib/net/ntlm/client/session_spec.rb index 8789e6e..3ebc56a 100644 --- a/spec/lib/net/ntlm/client/session_spec.rb +++ b/spec/lib/net/ntlm/client/session_spec.rb @@ -65,4 +65,23 @@ end end + context 'when authenticating anonymously' do + let(:inst) { Net::NTLM::Client::Session.new(Net::NTLM::Client.new('', ''), t2_challenge) } + + describe "#authenticate!" do + it "should set the response fields correctly" do + t3 = inst.authenticate! + expect(t3).to be_a(Net::NTLM::Message::Type3) + expect(t3.lm_response).to eq("\x00".b) + expect(t3.ntlm_response).to eq('') + end + end + + describe "#is_anonymous?" do + it "should be true" do + expect(inst.is_anonymous?).to be_truthy + end + end + end + end diff --git a/spec/lib/net/ntlm/message/type3_spec.rb b/spec/lib/net/ntlm/message/type3_spec.rb index 5249331..f42f491 100644 --- a/spec/lib/net/ntlm/message/type3_spec.rb +++ b/spec/lib/net/ntlm/message/type3_spec.rb @@ -222,4 +222,25 @@ end + describe '.serialize' do + subject(:message) { described_class.create(opts) } + context 'with the UNICODE flag set' do + let(:opts) { {lm_response: "\x00".b, ntlm_response: '', domain: '', workstation: '', user: '', flag: Net::NTLM::DEFAULT_FLAGS[:TYPE3] | Net::NTLM::FLAGS[:UNICODE] } } + + it 'should pad the domain field to a multiple of 2' do + message.serialize + expect(message[:domain][:offset].value % 2).to eq 0 + end + + it 'should pad the user field to a multiple of 2' do + message.serialize + expect(message[:user][:offset].value % 2).to eq 0 + end + + it 'should pad the workstation field to a multiple of 2' do + message.serialize + expect(message[:workstation][:offset].value % 2).to eq 0 + end + end + end end