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.



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.



353
354
355
# File 'lib/ruby_smb/client/authentication.rb', line 353

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

#smb1_anonymous_authWindowsError::ErrorCode

Attempts an Anonymous logon to the remote server.



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.



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.



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.



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



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



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

  if @smb3 && !@session_encrypt_data && response.session_flags.encrypt_data == 1
    @session_encrypt_data = true
  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.



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

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.



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

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



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

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



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

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.



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

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



279
280
281
282
283
284
285
# File 'lib/ruby_smb/client/authentication.rb', line 279

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



291
292
293
294
295
296
# File 'lib/ruby_smb/client/authentication.rb', line 291

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.



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/ruby_smb/client/authentication.rb', line 331

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