ruby_smb 3.2.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ce8d4ea99335efaf6bbc1ef003231ffa8a809382760e54dc26be04b76e53529
4
- data.tar.gz: f284dabf7fe032ae02933a517abb2371f7c7c37eb641876f1cbf2b8cc0e4c2ac
3
+ metadata.gz: 7b1e12b49f282f15459e21c5f16c6a6fc0f1074b107b97d85395d75a8531df1c
4
+ data.tar.gz: bccbfe2ef2823c390974786165e822eaa2fb7d1e1a96e8171daabbe871c6b324
5
5
  SHA512:
6
- metadata.gz: 5cf115ffd4c634f593bceef2bb503d2c0a2b19c5cfa73391887d35ce2691472fabda03e47462b716f8c870d7dedbc5dc815f644887b259d1268658752396ea17
7
- data.tar.gz: 46ba6053ca246692ef29c545c8a9682b6675c4bf50d10d3611107bf537470226986aeece854516d59d99afa1642ee2d72fed99f39c1731112292fcbb6d8138d2
6
+ metadata.gz: fc4ba500076138ce97e1dc369733675600c4892669d9b5db815b4d09c1dc1b47efd0068da6cb7530fafdcdfc326d00b42b91937e44ed5fe2fdb9b4e5b37ffd9a
7
+ data.tar.gz: b851e6cc8f2846b7b6f7c36b01b14adb089c5d75257315940a282793bb62cce5bdf1f944c7ca3c95f75464914e26753f4ce1577ac446deea2c42c5a84986f82e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -33,17 +33,15 @@ jobs:
33
33
  fail-fast: true
34
34
  matrix:
35
35
  ruby:
36
- - 2.6
37
36
  - 2.7
38
37
  - 3.0
39
38
  - 3.1
40
39
  os:
41
- - ubuntu-18.04
42
- - ubuntu-22.04
40
+ - ubuntu-20.04
41
+ - ubuntu-latest
43
42
  exclude:
44
- - { os: ubuntu-22.04, ruby: 2.6 }
45
- - { os: ubuntu-22.04, ruby: 2.7 }
46
- - { os: ubuntu-22.04, ruby: 3.0 }
43
+ - { os: ubuntu-latest, ruby: 2.7 }
44
+ - { os: ubuntu-latest, ruby: 3.0 }
47
45
  test_cmd:
48
46
  - bundle exec rspec
49
47
 
@@ -4,6 +4,7 @@ module RubySMB
4
4
  class Client
5
5
  require 'ruby_smb/ntlm'
6
6
  require 'ruby_smb/signing'
7
+ require 'ruby_smb/utils'
7
8
  require 'ruby_smb/client/negotiation'
8
9
  require 'ruby_smb/client/authentication'
9
10
  require 'ruby_smb/client/tree_connect'
@@ -13,6 +14,7 @@ module RubySMB
13
14
  require 'ruby_smb/client/encryption'
14
15
 
15
16
  include RubySMB::Signing
17
+ include RubySMB::NTLM
16
18
  include RubySMB::Client::Negotiation
17
19
  include RubySMB::Client::Authentication
18
20
  include RubySMB::Client::TreeConnect
@@ -322,7 +324,7 @@ module RubySMB
322
324
  @pid = rand(0xFFFF)
323
325
  @domain = domain
324
326
  @local_workstation = local_workstation
325
- @password = password.encode('utf-8') || ''.encode('utf-8')
327
+ @password = RubySMB::Utils.safe_encode((password||''), 'utf-8')
326
328
  @sequence_counter = 0
327
329
  @session_id = 0x00
328
330
  @session_key = ''
@@ -332,7 +334,7 @@ module RubySMB
332
334
  @smb1 = smb1
333
335
  @smb2 = smb2
334
336
  @smb3 = smb3
335
- @username = username.encode('utf-8') || ''.encode('utf-8')
337
+ @username = RubySMB::Utils.safe_encode((username||''), 'utf-8')
336
338
  @max_buffer_size = MAX_BUFFER_SIZE
337
339
  # These sizes will be modified during negotiation
338
340
  @server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
@@ -415,8 +417,8 @@ module RubySMB
415
417
  local_workstation: self.local_workstation, ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS)
416
418
  @domain = domain
417
419
  @local_workstation = local_workstation
418
- @password = pass.encode('utf-8') || ''.encode('utf-8')
419
- @username = user.encode('utf-8') || ''.encode('utf-8')
420
+ @password = RubySMB::Utils.safe_encode((pass||''), 'utf-8')
421
+ @username = RubySMB::Utils.safe_encode((user||''), 'utf-8')
420
422
 
421
423
  @ntlm_client = RubySMB::NTLM::Client.new(
422
424
  @username,
@@ -5,14 +5,16 @@ module RubySMB
5
5
  class Client
6
6
  require 'bindata'
7
7
  require 'windows_error'
8
- require 'net/ntlm'
8
+ require 'ruby_smb/ntlm'
9
9
  require 'ruby_smb/dcerpc'
10
10
  require 'ruby_smb/gss'
11
11
  require 'ruby_smb/peer_info'
12
+ require 'ruby_smb/utils'
12
13
 
13
14
  include Dcerpc
14
15
  include Epm
15
16
  include PeerInfo
17
+ include Utils
16
18
 
17
19
  # The default maximum size of a RPC message that the Client accepts (in bytes)
18
20
  MAX_BUFFER_SIZE = 64512
@@ -118,15 +120,15 @@ module RubySMB
118
120
  @read_timeout = read_timeout
119
121
  @domain = domain
120
122
  @local_workstation = local_workstation
121
- @username = username.encode('utf-8')
122
- @password = password.encode('utf-8')
123
+ @username = RubySMB::Utils.safe_encode(username, 'utf-8')
124
+ @password = RubySMB::Utils.safe_encode(password, 'utf-8')
123
125
  @max_buffer_size = MAX_BUFFER_SIZE
124
126
  @call_id = 1
125
127
  @ctx_id = 0
126
128
  @auth_ctx_id_base = rand(0xFFFFFFFF)
127
129
 
128
130
  unless username.empty? && password.empty?
129
- @ntlm_client = Net::NTLM::Client.new(
131
+ @ntlm_client = RubySMB::NTLM::Client.new(
130
132
  @username,
131
133
  @password,
132
134
  workstation: @local_workstation,
@@ -35,7 +35,7 @@ module RubySMB
35
35
  def cert_server_request(attributes:, authority:, csr:)
36
36
  cert_server_request_request = CertServerRequestRequest.new(
37
37
  pwsz_authority: authority,
38
- pctb_attribs: { pb: (attributes.map { |k,v| "#{k}:#{v}" }.join("\n").encode('UTF-16le').force_encoding('ASCII-8bit') + "\x00\x00".b) },
38
+ pctb_attribs: { pb: (RubySMB::Utils.safe_encode(attributes.map { |k,v| "#{k}:#{v}" }.join("\n"), 'UTF-16le').force_encoding('ASCII-8bit') + "\x00\x00".b) },
39
39
  pctb_request: { pb: csr.to_der }
40
40
  )
41
41
 
@@ -247,12 +247,22 @@ module RubySMB::Dcerpc::Ndr
247
247
 
248
248
  def do_read(io)
249
249
  if is_a?(ConfPlugin) && should_process_max_count?
250
- set_max_count(io.readbytes(4).unpack('L<').first)
250
+ max_count = io.readbytes(4).unpack1('L<')
251
+ BinData.trace_message do |tracer|
252
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
253
+ end
254
+ set_max_count(max_count)
251
255
  end
252
256
 
253
257
  if is_a?(VarPlugin)
254
258
  @offset = io.readbytes(4).unpack('L<').first
259
+ BinData.trace_message do |tracer|
260
+ tracer.trace_obj("#{debug_name}.offset", @offset.to_s)
261
+ end
255
262
  @actual_count = @read_until_index = io.readbytes(4).unpack('L<').first
263
+ BinData.trace_message do |tracer|
264
+ tracer.trace_obj("#{debug_name}.actual_count", @actual_count.to_s)
265
+ end
256
266
  end
257
267
 
258
268
  if has_elements_to_read?
@@ -523,7 +533,11 @@ module RubySMB::Dcerpc::Ndr
523
533
 
524
534
  def do_read(io)
525
535
  if should_process_max_count?
526
- set_max_count(io.readbytes(4).unpack('L<').first)
536
+ max_count = io.readbytes(4).unpack1('L<')
537
+ BinData.trace_message do |tracer|
538
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
539
+ end
540
+ set_max_count(max_count)
527
541
  end
528
542
  super
529
543
  end
@@ -582,7 +596,13 @@ module RubySMB::Dcerpc::Ndr
582
596
 
583
597
  def do_read(io)
584
598
  @offset = io.readbytes(4).unpack('L<').first
599
+ BinData.trace_message do |tracer|
600
+ tracer.trace_obj("#{debug_name}.offset", @offset.to_s)
601
+ end
585
602
  @actual_count = io.readbytes(4).unpack('L<').first
603
+ BinData.trace_message do |tracer|
604
+ tracer.trace_obj("#{debug_name}.actual_count", @actual_count.to_s)
605
+ end
586
606
  super if @actual_count > 0
587
607
  end
588
608
 
@@ -702,7 +722,11 @@ module RubySMB::Dcerpc::Ndr
702
722
 
703
723
  def do_read(io)
704
724
  if should_process_max_count?
705
- set_max_count(io.readbytes(4).unpack('L<').first)
725
+ max_count = io.readbytes(4).unpack1('L<')
726
+ BinData.trace_message do |tracer|
727
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
728
+ end
729
+ set_max_count(max_count)
706
730
 
707
731
  # Align the structure according to the alignment rules for the structure
708
732
  if respond_to?(:referent_bytes_align)
@@ -1034,6 +1058,9 @@ module RubySMB::Dcerpc::Ndr
1034
1058
  end
1035
1059
  else
1036
1060
  @ref_id = io.readbytes(4).unpack('L<').first
1061
+ BinData.trace_message do |tracer|
1062
+ tracer.trace_obj("#{debug_name}.ref_id", @ref_id.to_s)
1063
+ end
1037
1064
  parent_obj = nil
1038
1065
  if parent&.is_a?(ConstructedTypePlugin)
1039
1066
  parent_obj = parent.get_top_level_constructed_type
@@ -320,7 +320,7 @@ module RubySMB
320
320
 
321
321
  def self.encrypt_password(password, key)
322
322
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
323
- password = password.encode('UTF-16LE').force_encoding('ASCII-8bit')
323
+ password = RubySMB::Utils.safe_encode(password, 'UTF-16LE').force_encoding('ASCII-8bit')
324
324
  buffer = password.rjust(512, "\x00") + [ password.length ].pack('V')
325
325
  salt = SecureRandom.random_bytes(16)
326
326
  key = OpenSSL::Digest::MD5.new(salt + key).digest
@@ -18,6 +18,7 @@ module RubySMB
18
18
  end
19
19
 
20
20
  class Authenticator < Authenticator::Base
21
+
21
22
  def reset!
22
23
  super
23
24
  @server_challenge = nil
@@ -141,7 +142,10 @@ module RubySMB
141
142
  case type3_msg.ntlm_version
142
143
  when :ntlmv1
143
144
  my_ntlm_response = Net::NTLM::ntlm_response(
144
- ntlm_hash: Net::NTLM::ntlm_hash(account.password.encode('UTF-16LE'), unicode: true),
145
+ ntlm_hash: Net::NTLM::ntlm_hash(
146
+ RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
147
+ unicode: true
148
+ ),
145
149
  challenge: @server_challenge
146
150
  )
147
151
  matches = my_ntlm_response == type3_msg.ntlm_response
@@ -151,9 +155,9 @@ module RubySMB
151
155
  their_blob = type3_msg.ntlm_response[digest.digest_length..-1]
152
156
 
153
157
  ntlmv2_hash = Net::NTLM.ntlmv2_hash(
154
- account.username.encode('UTF-16LE'),
155
- account.password.encode('UTF-16LE'),
156
- type3_msg.domain.encode('UTF-16LE'), # don't use the account domain because of the special '.' value
158
+ RubySMB::Utils.safe_encode(account.username, 'UTF-16LE'),
159
+ RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
160
+ RubySMB::Utils.safe_encode(type3_msg.domain, 'UTF-16LE'), # don't use the account domain because of the special '.' value
157
161
  {client_challenge: their_blob[16...24], unicode: true}
158
162
  )
159
163
 
@@ -305,7 +309,10 @@ module RubySMB
305
309
  username = username.downcase
306
310
  domain = @default_domain if domain.nil? || domain == '.'.encode(domain.encoding)
307
311
  domain = domain.downcase
308
- @accounts.find { |account| account.username.encode(username.encoding).downcase == username && account.domain.encode(domain.encoding).downcase == domain }
312
+ @accounts.find do |account|
313
+ RubySMB::Utils.safe_encode(account.username, username.encoding).downcase == username &&
314
+ RubySMB::Utils.safe_encode(account.domain, domain.encoding).downcase == domain
315
+ end
309
316
  end
310
317
 
311
318
  #
@@ -51,6 +51,10 @@ module RubySMB::NTLM
51
51
  !challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
52
52
  end
53
53
 
54
+ def ntlmv2_hash
55
+ @ntlmv2_hash ||= RubySMB::NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
56
+ end
57
+
54
58
  def calculate_user_session_key!
55
59
  if is_anonymous?
56
60
  # see MS-NLMP section 3.4
@@ -0,0 +1,19 @@
1
+ require 'net/ntlm'
2
+
3
+ module Custom
4
+ module NTLM
5
+
6
+ def self.prepended(base)
7
+ base.singleton_class.send(:prepend, ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def encode_utf16le(str)
12
+ str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('ASCII-8BIT')
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ Net::NTLM::EncodeUtil.send(:prepend, Custom::NTLM)
data/lib/ruby_smb/ntlm.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'ruby_smb/ntlm/custom/ntlm'
2
+
1
3
  module RubySMB
2
4
  module NTLM
3
5
  # [[MS-NLMP] 2.2.2.5](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832)
@@ -56,6 +58,41 @@ module RubySMB
56
58
  "Version #{major}.#{minor} (Build #{build}); NTLM Current Revision #{ntlm_revision}"
57
59
  end
58
60
  end
61
+
62
+ class << self
63
+
64
+ # Generate a NTLMv2 Hash
65
+ # @param [String] user The username
66
+ # @param [String] password The password
67
+ # @param [String] target The domain or workstation to authenticate to
68
+ # @option opt :unicode (false) Unicode encode the domain
69
+ def ntlmv2_hash(user, password, target, opt={})
70
+ if Net::NTLM.is_ntlm_hash? password
71
+ decoded_password = Net::NTLM::EncodeUtil.decode_utf16le(password)
72
+ ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
73
+ else
74
+ ntlmhash = Net::NTLM.ntlm_hash(password, opt)
75
+ end
76
+
77
+ if opt[:unicode]
78
+ # Uppercase operation on username containing non-ASCII characters
79
+ # after being unicode encoded with `EncodeUtil.encode_utf16le`
80
+ # doesn't play well. Upcase should be done before encoding.
81
+ user_upcase = Net::NTLM::EncodeUtil.decode_utf16le(user).upcase
82
+ user_upcase = Net::NTLM::EncodeUtil.encode_utf16le(user_upcase)
83
+ else
84
+ user_upcase = user.upcase
85
+ end
86
+ userdomain = user_upcase + target
87
+
88
+ unless opt[:unicode]
89
+ userdomain = Net::NTLM::EncodeUtil.encode_utf16le(userdomain)
90
+ end
91
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
92
+ end
93
+
94
+ end
95
+
59
96
  end
60
97
  end
61
98
 
@@ -7,7 +7,7 @@ module RubySMB
7
7
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/b062f3e3-1b65-4a9a-854a-0ee432499d8f
8
8
  response = RubySMB::SMB1::Packet::TreeConnectResponse.new
9
9
 
10
- share_name = request.data_block.path.encode('UTF-8').split('\\', 4).last
10
+ share_name = RubySMB::Utils.safe_encode(request.data_block.path, 'UTF-8').split('\\', 4).last
11
11
  share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
12
12
  if share_provider.nil?
13
13
  logger.warn("Received TREE_CONNECT request for non-existent share: #{share_name}")
@@ -49,7 +49,7 @@ module RubySMB
49
49
  return response
50
50
  end
51
51
 
52
- share_name = request.path.encode('UTF-8').split('\\', 4).last
52
+ share_name = RubySMB::Utils.safe_encode(request.path, 'UTF-8').split('\\', 4).last
53
53
  share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
54
54
 
55
55
  if share_provider.nil?
@@ -147,6 +147,10 @@ module RubySMB
147
147
  break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
148
148
  raw_data = read(bytes: @tree.client.max_buffer_size)
149
149
  dcerpc_response = dcerpc_response_from_raw_response(raw_data)
150
+ if options[:auth_level] &&
151
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
152
+ handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
153
+ end
150
154
  stub_data << dcerpc_response.stub.to_s
151
155
  end
152
156
  stub_data
@@ -145,6 +145,10 @@ module RubySMB
145
145
  break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
146
146
  raw_data = read(bytes: @tree.client.max_buffer_size)
147
147
  dcerpc_response = dcerpc_response_from_raw_response(raw_data)
148
+ if options[:auth_level] &&
149
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
150
+ handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
151
+ end
148
152
  stub_data << dcerpc_response.stub.to_s
149
153
  end
150
154
  stub_data
@@ -0,0 +1,15 @@
1
+ module RubySMB
2
+ module Utils
3
+
4
+ def self.safe_encode(str, encoding)
5
+ str.encode(encoding)
6
+ rescue EncodingError
7
+ if str.encoding == ::Encoding::ASCII_8BIT
8
+ str.dup.force_encoding(encoding)
9
+ else
10
+ raise
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '3.2.0'.freeze
2
+ VERSION = '3.2.1'.freeze
3
3
  end
data/lib/ruby_smb.rb CHANGED
@@ -6,11 +6,13 @@ require 'openssl/ccm'
6
6
  require 'openssl/cmac'
7
7
  require 'windows_error'
8
8
  require 'windows_error/nt_status'
9
+ require 'ruby_smb/ntlm/custom/ntlm'
9
10
  # A packet parsing and manipulation library for the SMB1 and SMB2 protocols
10
11
  #
11
12
  # [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
12
13
  # [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
13
14
  module RubySMB
15
+ require 'ruby_smb/utils'
14
16
  require 'ruby_smb/error'
15
17
  require 'ruby_smb/create_actions'
16
18
  require 'ruby_smb/dispositions'
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_smb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Metasploit Hackers
@@ -97,7 +97,7 @@ cert_chain:
97
97
  EknWpNgVhohbot1lfVAMmIhdtOVaRVcQQixWPwprDj/ydB8ryDMDosIMcw+fkoXU
98
98
  9GJsSaSRRYQ9UUkVL27b64okU8D48m8=
99
99
  -----END CERTIFICATE-----
100
- date: 2022-08-30 00:00:00.000000000 Z
100
+ date: 2022-11-17 00:00:00.000000000 Z
101
101
  dependencies:
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: redcarpet
@@ -493,6 +493,7 @@ files:
493
493
  - lib/ruby_smb/nbss/session_request.rb
494
494
  - lib/ruby_smb/ntlm.rb
495
495
  - lib/ruby_smb/ntlm/client.rb
496
+ - lib/ruby_smb/ntlm/custom/ntlm.rb
496
497
  - lib/ruby_smb/peer_info.rb
497
498
  - lib/ruby_smb/server.rb
498
499
  - lib/ruby_smb/server/cli.rb
@@ -673,6 +674,7 @@ files:
673
674
  - lib/ruby_smb/smb2/smb2_header.rb
674
675
  - lib/ruby_smb/smb2/tree.rb
675
676
  - lib/ruby_smb/smb_error.rb
677
+ - lib/ruby_smb/utils.rb
676
678
  - lib/ruby_smb/version.rb
677
679
  - ruby_smb.gemspec
678
680
  - spec/lib/ruby_smb/client_spec.rb
metadata.gz.sig CHANGED
Binary file