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
# 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

  @user_id = user_id if response_code == WindowsError::NTStatus::STATUS_SUCCESS

  response_code
end

#smb1_legacy_auth_request(lm_response, ntlm_response) ⇒ Object



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

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



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

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.



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

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:



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

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



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

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



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

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



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

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



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

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:



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

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



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

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



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

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.



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

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:



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

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



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

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



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

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



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

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



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

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:



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

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



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

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



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

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