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

def authenticate
  if smb1
    if username.empty? && password.empty?
      smb1_anonymous_auth
    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



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ruby_smb/client/authentication.rb', line 33

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.



52
53
54
55
56
57
58
59
# File 'lib/ruby_smb/client/authentication.rb', line 52

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



61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ruby_smb/client/authentication.rb', line 61

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.



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

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_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:



129
130
131
132
133
134
135
136
137
# File 'lib/ruby_smb/client/authentication.rb', line 129

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



118
119
120
121
# File 'lib/ruby_smb/client/authentication.rb', line 118

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



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/ruby_smb/client/authentication.rb', line 174

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



169
170
171
# File 'lib/ruby_smb/client/authentication.rb', line 169

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



107
108
109
110
# File 'lib/ruby_smb/client/authentication.rb', line 107

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:



144
145
146
147
148
149
150
151
152
# File 'lib/ruby_smb/client/authentication.rb', line 144

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



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ruby_smb/client/authentication.rb', line 155

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



196
197
198
199
200
201
# File 'lib/ruby_smb/client/authentication.rb', line 196

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.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/ruby_smb/client/authentication.rb', line 209

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:



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

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



344
345
346
347
348
349
350
351
# File 'lib/ruby_smb/client/authentication.rb', line 344

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



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/ruby_smb/client/authentication.rb', line 284

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



279
280
281
# File 'lib/ruby_smb/client/authentication.rb', line 279

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



305
306
307
308
309
310
311
312
# File 'lib/ruby_smb/client/authentication.rb', line 305

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:



319
320
321
322
323
324
325
# File 'lib/ruby_smb/client/authentication.rb', line 319

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



265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/ruby_smb/client/authentication.rb', line 265

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



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

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