Module: RubySMB::Client::Authentication

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

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.



10
11
12
13
14
15
16
17
18
19
20
# File 'lib/ruby_smb/client/authentication.rb', line 10

def authenticate
  if smb1
    if username.empty? && password.empty?
      smb1_anonymous_auth
    else
      smb1_authenticate
    end
  else
    smb2_authenticate
  end
end

#extract_os_version(version) ⇒ String

Extract the peer/server version number from the NTLM Type 2 (challenge) Version field.

Parameters:

  • version (String)

    the version number as a binary string

Returns:

  • (String)

    the formated version number (..)



361
362
363
# File 'lib/ruby_smb/client/authentication.rb', line 361

def extract_os_version(version)
  version.unpack('CCS').join('.')
end

#smb1_anonymous_authWindowsError::ErrorCode

Attempts an Anonymous logon to the remote server.

Returns:

  • (WindowsError::ErrorCode)

    the status code the server returned



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/ruby_smb/client/authentication.rb', line 29

def smb1_anonymous_auth
  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.



47
48
49
50
51
52
53
54
# File 'lib/ruby_smb/client/authentication.rb', line 47

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



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

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.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ruby_smb/client/authentication.rb', line 71

def smb1_authenticate
  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)

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



123
124
125
126
127
128
129
130
131
# File 'lib/ruby_smb/client/authentication.rb', line 123

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



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

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/ruby_smb/client/authentication.rb', line 163

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



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

def smb1_ntlmssp_final_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
  packet
end

#smb1_ntlmssp_negotiateString

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

Returns:

  • (String)

    the binary string response from the server



101
102
103
104
# File 'lib/ruby_smb/client/authentication.rb', line 101

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:



138
139
140
141
142
143
144
145
146
# File 'lib/ruby_smb/client/authentication.rb', line 138

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



185
186
187
188
189
190
# File 'lib/ruby_smb/client/authentication.rb', line 185

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.



198
199
200
201
202
203
204
205
206
207
208
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
# File 'lib/ruby_smb/client/authentication.rb', line 198

def smb2_authenticate
  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)

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



327
328
329
330
331
332
333
# File 'lib/ruby_smb/client/authentication.rb', line 327

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



312
313
314
315
316
317
318
319
# File 'lib/ruby_smb/client/authentication.rb', line 312

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



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/ruby_smb/client/authentication.rb', line 252

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



238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/ruby_smb/client/authentication.rb', line 238

def smb2_ntlmssp_final_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

  packet
end

#smb2_ntlmssp_negotiateString

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

Returns:

  • (String)

    the binary string response from the server



273
274
275
276
277
278
279
280
# File 'lib/ruby_smb/client/authentication.rb', line 273

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:



287
288
289
290
291
292
293
# File 'lib/ruby_smb/client/authentication.rb', line 287

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



299
300
301
302
303
304
# File 'lib/ruby_smb/client/authentication.rb', line 299

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

#store_target_info(target_info_str) ⇒ Object

Extract and store useful information about the peer/server from the NTLM Type 2 (challenge) TargetInfo fields.

Parameters:

  • target_info_str (String)

    the Target Info string



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

def store_target_info(target_info_str)
  target_info = Net::NTLM::TargetInfo.new(target_info_str)
  {
    Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME  => :@default_name,
    Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME    => :@default_domain,
    Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => :@dns_host_name,
    Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME   => :@dns_domain_name,
    Net::NTLM::TargetInfo::MSV_AV_DNS_TREE_NAME     => :@dns_tree_name
  }.each do |constant, attribute|
    if target_info.av_pairs[constant]
      value = target_info.av_pairs[constant].dup
      value.force_encoding('UTF-16LE')
      instance_variable_set(attribute, value.encode('UTF-8'))
    end
  end
end