Module: RubySMB::Client::Authentication

Includes:
PeerInfo
Included in:
RubySMB::Client
Defined in:
lib/ruby_smb/client/authentication.rb

Overview

This module holds all the backend client methods for authentication.

Instance Method Summary collapse

Methods included from PeerInfo

#extract_os_version, #store_target_info

Instance Method Details

#authenticateWindowsError::NTStatus

Responsible for handling Authentication and Session Setup for the SMB Client. It returns the final Status code from the authentication exchange.

Returns:

  • (WindowsError::NTStatus)

    the NTStatus object from the SessionSetup exchange.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/ruby_smb/client/authentication.rb', line 14

def authenticate
  if smb1
    if username.empty? && password.empty?
      smb1_anonymous_auth
    elsif @smb1_negotiate_challenge
      # Non-extended security negotiated (e.g. Windows 95/98). Use legacy
      # LM/NTLM challenge-response rather than NTLMSSP.
      smb1_legacy_authenticate
    else
      smb1_authenticate
    end
  else
    smb2_authenticate
  end
end

#smb1_anonymous_authWindowsError::ErrorCode

Attempts an Anonymous logon to the remote server.

Returns:

  • (WindowsError::ErrorCode)

    the status code the server returned



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ruby_smb/client/authentication.rb', line 37

def smb1_anonymous_auth
  @mech_type = :anonymous
  request       = smb1_anonymous_auth_request
  raw_response  = send_recv(request)
  response      = smb1_anonymous_auth_response(raw_response)
  response_code = response.status_code

  if response_code == WindowsError::NTStatus::STATUS_SUCCESS
    self.user_id = response.smb_header.uid
    self.peer_native_os = response.data_block.native_os.to_s
    self.peer_native_lm = response.data_block.native_lan_man.to_s
    self.primary_domain = response.data_block.primary_domain.to_s
  end

  response_code
end

#smb1_anonymous_auth_requestObject

Creates a SessionSetupRequest for an anonymous access session.



56
57
58
59
60
61
62
63
# File 'lib/ruby_smb/client/authentication.rb', line 56

def smb1_anonymous_auth_request
  packet = RubySMB::SMB1::Packet::SessionSetupLegacyRequest.new
  packet.data_block.oem_password = "\x00"
  packet.parameter_block.max_buffer_size = self.max_buffer_size
  packet.parameter_block.max_mpx_count = 50
  packet.parameter_block.capabilities.extended_security = 0
  packet
end

#smb1_anonymous_auth_response(raw_response) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ruby_smb/client/authentication.rb', line 65

def smb1_anonymous_auth_response(raw_response)
  packet = RubySMB::SMB1::Packet::SessionSetupLegacyResponse.read(raw_response)

  unless packet.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
      packet:         packet
    )
  end
  packet
end

#smb1_authenticateObject

Handles the SMB1 NTLMSSP 4-way handshake for Authentication and store information about the peer/server.



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
106
# File 'lib/ruby_smb/client/authentication.rb', line 80

def smb1_authenticate
  @mech_type = :ntlm
  response = smb1_ntlmssp_negotiate
  challenge_packet = smb1_ntlmssp_challenge_packet(response)

  # Store the available OS information before going forward.
  @peer_native_os = challenge_packet.data_block.native_os.to_s
  @peer_native_lm = challenge_packet.data_block.native_lan_man.to_s

  user_id = challenge_packet.smb_header.uid
  type2_b64_message = smb1_type2_message(challenge_packet)
  type3_message = @ntlm_client.init_context(type2_b64_message)

  @application_key = @session_key = @ntlm_client.session_key
  challenge_message = @ntlm_client.session.challenge_message
  store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
  @os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?

  raw = smb1_ntlmssp_authenticate(type3_message, user_id)
  response = smb1_ntlmssp_final_packet(raw)
  response_code = response.status_code

  # Use the UID from the final response (some servers, e.g. Samba, reassign it on success).
  @user_id = response.smb_header.uid if response_code == WindowsError::NTStatus::STATUS_SUCCESS

  response_code
end

#smb1_legacy_auth_request(lm_response, ntlm_response) ⇒ Object



234
235
236
237
238
239
240
241
242
243
# File 'lib/ruby_smb/client/authentication.rb', line 234

def smb1_legacy_auth_request(lm_response, ntlm_response)
  packet = RubySMB::SMB1::Packet::SessionSetupLegacyRequest.new
  packet.parameter_block.max_buffer_size = self.max_buffer_size
  packet.parameter_block.max_mpx_count   = 50
  packet.data_block.oem_password         = lm_response
  packet.data_block.unicode_password     = ntlm_response
  packet.data_block.         = @username.encode('ASCII', invalid: :replace, undef: :replace)
  packet.data_block.primary_domain       = @domain.encode('ASCII', invalid: :replace, undef: :replace)
  packet
end

#smb1_legacy_auth_response(raw_response) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
# File 'lib/ruby_smb/client/authentication.rb', line 245

def smb1_legacy_auth_response(raw_response)
  packet = RubySMB::SMB1::Packet::SessionSetupLegacyResponse.read(raw_response)
  unless packet.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
      packet:         packet
    )
  end
  packet
end

#smb1_legacy_authenticateObject

Handles SMB1 authentication against servers that negotiated non-extended (legacy) security — Windows 95/98/ME and old Samba builds. These hosts provide a raw 8-byte challenge in the Negotiate response and expect LM + NTLM hash responses in SessionSetupLegacyRequest.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/ruby_smb/client/authentication.rb', line 212

def smb1_legacy_authenticate
  challenge  = @smb1_negotiate_challenge
  lm_hash    = Net::NTLM.lm_hash(@password)
  ntlm_hash  = Net::NTLM.ntlm_hash(@password)
  lm_resp    = Net::NTLM.lm_response(lm_hash: lm_hash, challenge: challenge)
  ntlm_resp  = Net::NTLM.ntlm_response(ntlm_hash: ntlm_hash, challenge: challenge)

  packet       = smb1_legacy_auth_request(lm_resp, ntlm_resp)
  raw_response = send_recv(packet)
  response     = smb1_legacy_auth_response(raw_response)
  response_code = response.status_code

  if response_code == WindowsError::NTStatus::STATUS_SUCCESS
    self.user_id        = response.smb_header.uid
    self.peer_native_os = response.data_block.native_os.to_s
    self.peer_native_lm = response.data_block.native_lan_man.to_s
    self.primary_domain = response.data_block.primary_domain.to_s
  end

  response_code
end

#smb1_ntlmssp_auth_packet(type3_message, user_id) ⇒ RubySMB::SMB1::Packet::SessionSetupRequest

Generates the SMB1::Packet::SessionSetupRequest packet with the NTLM Type 3 (Auth) message in the security_blob field.

Parameters:

  • type3_message (String)

    the NTLM Type 3 message

  • user_id (Integer)

    the temporary user ID from the Type 2 response

Returns:



134
135
136
137
138
139
140
141
142
# File 'lib/ruby_smb/client/authentication.rb', line 134

def smb1_ntlmssp_auth_packet(type3_message, user_id)
  packet = RubySMB::SMB1::Packet::SessionSetupRequest.new
  packet.smb_header.uid = user_id
  packet.set_type3_blob(type3_message.serialize)
  packet.parameter_block.max_buffer_size = self.max_buffer_size
  packet.parameter_block.max_mpx_count = 50
  packet.smb_header.flags2.extended_security = 1
  packet
end

#smb1_ntlmssp_authenticate(type3_message, user_id) ⇒ String

Takes the NTLM Type 3 (authenticate) message and calls the routines to build the Auth packet, sends the packet and receives the raw response.

Parameters:

  • type3_message (String)

    the NTLM Type 3 message

  • user_id (Integer)

    the temporary user ID from the Type 2 response

Returns:

  • (String)

    the raw binary response from the server



123
124
125
126
# File 'lib/ruby_smb/client/authentication.rb', line 123

def smb1_ntlmssp_authenticate(type3_message, user_id)
  packet = smb1_ntlmssp_auth_packet(type3_message, user_id)
  send_recv(packet)
end

#smb1_ntlmssp_challenge_packet(raw_response) ⇒ Object

Takes the raw binary string and returns a SMB1::Packet::SessionSetupResponse



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/ruby_smb/client/authentication.rb', line 179

def smb1_ntlmssp_challenge_packet(raw_response)
  packet = RubySMB::SMB1::Packet::SessionSetupResponse.read(raw_response)
  unless packet.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
      packet:         packet
    )
  end

  status_code = packet.status_code
  unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
    raise RubySMB::Error::UnexpectedStatusCode, status_code
  end

  packet
end

#smb1_ntlmssp_final_packet(raw_response) ⇒ Object

Takes the raw binary string and returns a SMB1::Packet::SessionSetupResponse



174
175
176
# File 'lib/ruby_smb/client/authentication.rb', line 174

def smb1_ntlmssp_final_packet(raw_response)
  smb1_session_setup_response(raw_response)
end

#smb1_ntlmssp_negotiateString

Sends the SMB1::Packet::SessionSetupRequest packet and receives the response.

Returns:

  • (String)

    the binary string response from the server



112
113
114
115
# File 'lib/ruby_smb/client/authentication.rb', line 112

def smb1_ntlmssp_negotiate
  packet = smb1_ntlmssp_negotiate_packet
  send_recv(packet)
end

#smb1_ntlmssp_negotiate_packetRubySMB::SMB1::Packet::SessionSetupRequest

Creates the SMB1::Packet::SessionSetupRequest packet for the first part of the NTLMSSP 4-way hnadshake. This packet initializes negotiations for the NTLMSSP authentication

Returns:



149
150
151
152
153
154
155
156
157
# File 'lib/ruby_smb/client/authentication.rb', line 149

def smb1_ntlmssp_negotiate_packet
  type1_message = ntlm_client.init_context
  packet = RubySMB::SMB1::Packet::SessionSetupRequest.new
  packet.set_type1_blob(type1_message.serialize)
  packet.parameter_block.max_buffer_size = self.max_buffer_size
  packet.parameter_block.max_mpx_count = 50
  packet.smb_header.flags2.extended_security = 1
  packet
end

#smb1_session_setup_response(raw_response) ⇒ Object

Takes the raw binary string and returns a SMB1::Packet::SessionSetupResponse



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/ruby_smb/client/authentication.rb', line 160

def smb1_session_setup_response(raw_response)
  packet = RubySMB::SMB1::Packet::SessionSetupResponse.read(raw_response)

  unless packet.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
      packet:         packet
    )
  end
  packet
end

#smb1_type2_message(response_packet) ⇒ String

Parses out the NTLM Type 2 Message from a SMB1::Packet::SessionSetupResponse

Parameters:

Returns:

  • (String)

    the base64 encoded NTLM Challenge (Type2 Message) from the response



201
202
203
204
205
206
# File 'lib/ruby_smb/client/authentication.rb', line 201

def smb1_type2_message(response_packet)
  sec_blob = response_packet.data_block.security_blob
  ntlmssp_offset = sec_blob.index('NTLMSSP')
  type2_blob = sec_blob.slice(ntlmssp_offset..-1)
  [type2_blob].pack('m')
end

#smb2_authenticateObject

Handles the SMB2 NTLMSSP 4-way handshake for Authentication and store information about the peer/server.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/ruby_smb/client/authentication.rb', line 263

def smb2_authenticate
  @mech_type = :ntlm
  response = smb2_ntlmssp_negotiate
  challenge_packet = smb2_ntlmssp_challenge_packet(response)
  if @dialect == '0x0311'
    update_preauth_hash(challenge_packet)
  end
  @session_id = challenge_packet.smb2_header.session_id
  type2_b64_message = smb2_type2_message(challenge_packet)
  type3_message = @ntlm_client.init_context(type2_b64_message)

  @application_key = @session_key = @ntlm_client.session_key
  challenge_message = ntlm_client.session.challenge_message
  store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
  @os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?

  raw = smb2_ntlmssp_authenticate(type3_message, @session_id)
  response = smb2_ntlmssp_final_packet(raw)
  @session_is_guest = response.session_flags.guest == 1

  if @smb3
    if response.session_flags.encrypt_data == 1
      # if the server indicates that encryption is required, enable it
      @session_encrypt_data = true
    elsif (@session_is_guest && password != '') || (username == '' && password == '')
      # disable encryption when necessary
      @session_encrypt_data = false
    end

    # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/7fd079ca-17e6-4f02-8449-46b606ea289c
    if @dialect == '0x0300' || @dialect == '0x0302'
      @application_key = RubySMB::Crypto::KDF.counter_mode(
        @session_key,
        "SMB2APP\x00",
        "SmbRpc\x00"
      )
    else
      @application_key = RubySMB::Crypto::KDF.counter_mode(
        @session_key,
        "SMBAppKey\x00",
        @preauth_integrity_hash_value
      )
    end
    # otherwise, leave encryption to the default value that it was initialized to
  end
  ######
  # DEBUG
  #puts "Session ID = #{@session_id.to_binary_s.each_byte.map {|e| '%02x' % e}.join}"
  #puts "Session key = #{@session_key.each_byte.map {|e| '%02x' % e}.join}"
  #puts "PreAuthHash = #{@preauth_integrity_hash_value.each_byte.map {|e| '%02x' % e}.join}" if @preauth_integrity_hash_value
  ######

  response.status_code
end

#smb2_ntlmssp_auth_packet(type3_message, session_id) ⇒ RubySMB::SMB2::Packet::SessionSetupRequest

Generates the SMB2::Packet::SessionSetupRequest packet with the NTLM Type 3 (Auth) message in the security_blob field.

Parameters:

  • type3_message (String)

    the NTLM Type 3 message

  • session_id (Integer)

    the temporary session id from the Type 2 response

Returns:



413
414
415
416
417
418
419
# File 'lib/ruby_smb/client/authentication.rb', line 413

def smb2_ntlmssp_auth_packet(type3_message, session_id)
  packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
  packet.smb2_header.session_id = session_id
  packet.set_type3_blob(type3_message.serialize)
  packet.security_mode.signing_enabled = 1
  packet
end

#smb2_ntlmssp_authenticate(type3_message, user_id) ⇒ String

Takes the NTLM Type 3 (authenticate) message and calls the routines to build the Auth packet, sends the packet and receives the raw response.

Parameters:

  • type3_message (String)

    the NTLM Type 3 message

  • user_id (Integer)

    the temporary user ID from the Type 2 response

Returns:

  • (String)

    the raw binary response from the server



398
399
400
401
402
403
404
405
# File 'lib/ruby_smb/client/authentication.rb', line 398

def smb2_ntlmssp_authenticate(type3_message, user_id)
  packet = smb2_ntlmssp_auth_packet(type3_message, user_id)
  response = send_recv(packet)
  if @dialect == '0x0311'
    update_preauth_hash(packet)
  end
  response
end

#smb2_ntlmssp_challenge_packet(raw_response) ⇒ Object

Takes the raw binary string and returns a SMB2::Packet::SessionSetupResponse



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/ruby_smb/client/authentication.rb', line 338

def smb2_ntlmssp_challenge_packet(raw_response)
  packet = RubySMB::SMB2::Packet::SessionSetupResponse.read(raw_response)
  unless packet.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
      packet:         packet
    )
  end

  status_code = packet.status_code
  unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
    raise RubySMB::Error::UnexpectedStatusCode, status_code
  end
  packet
end

#smb2_ntlmssp_final_packet(raw_response) ⇒ Object

Takes the raw binary string and returns a SMB2::Packet::SessionSetupResponse



333
334
335
# File 'lib/ruby_smb/client/authentication.rb', line 333

def smb2_ntlmssp_final_packet(raw_response)
  smb2_session_setup_response(raw_response)
end

#smb2_ntlmssp_negotiateString

Sends the SMB2::Packet::SessionSetupRequest packet and receives the response.

Returns:

  • (String)

    the binary string response from the server



359
360
361
362
363
364
365
366
# File 'lib/ruby_smb/client/authentication.rb', line 359

def smb2_ntlmssp_negotiate
  packet = smb2_ntlmssp_negotiate_packet
  response = send_recv(packet)
  if @dialect == '0x0311'
    update_preauth_hash(packet)
  end
  response
end

#smb2_ntlmssp_negotiate_packetRubySMB::SMB2::Packet::SessionSetupRequest

Creates the SMB2::Packet::SessionSetupRequest packet for the first part of the NTLMSSP 4-way handshake. This packet initializes negotiations for the NTLMSSP authentication

Returns:



373
374
375
376
377
378
379
# File 'lib/ruby_smb/client/authentication.rb', line 373

def smb2_ntlmssp_negotiate_packet
  type1_message = ntlm_client.init_context
  packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
  packet.set_type1_blob(type1_message.serialize)
  packet.security_mode.signing_enabled = 1
  packet
end

#smb2_session_setup_response(raw_response) ⇒ Object

Takes the raw binary string and returns a SMB2::Packet::SessionSetupResponse



319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/ruby_smb/client/authentication.rb', line 319

def smb2_session_setup_response(raw_response)
  packet = RubySMB::SMB2::Packet::SessionSetupResponse.read(raw_response)
  unless packet.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
      packet:         packet
    )
  end

  packet
end

#smb2_type2_message(response_packet) ⇒ String

Parses out the NTLM Type 2 Message from a SMB2::Packet::SessionSetupResponse

Parameters:

Returns:

  • (String)

    the base64 encoded NTLM Challenge (Type2 Message) from the response



385
386
387
388
389
390
# File 'lib/ruby_smb/client/authentication.rb', line 385

def smb2_type2_message(response_packet)
  sec_blob = response_packet.buffer
  ntlmssp_offset = sec_blob.index('NTLMSSP')
  type2_blob = sec_blob.slice(ntlmssp_offset..-1)
  [type2_blob].pack('m')
end