ruby_smb 2.0.10 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/auth_capture.rb +71 -0
  4. data/lib/ruby_smb/client/negotiation.rb +1 -1
  5. data/lib/ruby_smb/client.rb +9 -8
  6. data/lib/ruby_smb/dialect.rb +45 -0
  7. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  8. data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
  9. data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
  10. data/lib/ruby_smb/gss/provider.rb +35 -0
  11. data/lib/ruby_smb/gss.rb +56 -63
  12. data/lib/ruby_smb/ntlm.rb +45 -0
  13. data/lib/ruby_smb/server/server_client/negotiation.rb +155 -0
  14. data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
  15. data/lib/ruby_smb/server/server_client.rb +163 -0
  16. data/lib/ruby_smb/server.rb +54 -0
  17. data/lib/ruby_smb/signing.rb +59 -0
  18. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
  19. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
  20. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  21. data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
  22. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
  23. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
  24. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
  25. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
  26. data/lib/ruby_smb/smb2.rb +3 -1
  27. data/lib/ruby_smb/version.rb +1 -1
  28. data/lib/ruby_smb.rb +2 -1
  29. data/spec/lib/ruby_smb/client_spec.rb +7 -9
  30. data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
  31. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
  32. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
  33. data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
  34. data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
  35. data/spec/lib/ruby_smb/server_spec.rb +32 -0
  36. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
  37. data.tar.gz.sig +0 -0
  38. metadata +25 -3
  39. metadata.gz.sig +0 -0
  40. data/lib/ruby_smb/client/signing.rb +0 -64
@@ -0,0 +1,163 @@
1
+ module RubySMB
2
+ class Server
3
+ # This class represents a single connected client to the server. It stores and processes connection specific related
4
+ # information.
5
+ class ServerClient
6
+
7
+ require 'ruby_smb/dialect'
8
+ require 'ruby_smb/signing'
9
+ require 'ruby_smb/server/server_client/negotiation'
10
+ require 'ruby_smb/server/server_client/session_setup'
11
+
12
+ include RubySMB::Signing
13
+ include RubySMB::Server::ServerClient::Negotiation
14
+ include RubySMB::Server::ServerClient::SessionSetup
15
+
16
+ attr_reader :dialect, :identity, :state, :session_key
17
+
18
+ # @param [Server] server the server that accepted this connection
19
+ # @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
20
+ def initialize(server, dispatcher)
21
+ @server = server
22
+ @dispatcher = dispatcher
23
+ @state = :negotiate
24
+ @dialect = nil
25
+ @message_id = 0
26
+ @session_id = nil
27
+ @session_key = nil
28
+ @gss_authenticator = server.gss_provider.new_authenticator(self)
29
+ @identity = nil
30
+ @tree_connections = {}
31
+ @preauth_integrity_hash_algorithm = nil
32
+ @preauth_integrity_hash_value = nil
33
+ end
34
+
35
+ #
36
+ # The dialects metadata definition.
37
+ #
38
+ # @return [Dialect::Definition]
39
+ def metadialect
40
+ Dialect::ALL[@dialect]
41
+ end
42
+
43
+ #
44
+ # The peername of the connected socket. This is a combination of the IPv4 or IPv6 address and port number.
45
+ #
46
+ # @example Parse the value into an IP address
47
+ # ::Socket::unpack_sockaddr_in(server_client.getpeername)
48
+ #
49
+ # @return [String]
50
+ def getpeername
51
+ @dispatcher.tcp_socket.getpeername
52
+ end
53
+
54
+ #
55
+ # Handle an authenticated request. This is the main handler for all requests after the connection has been
56
+ # authenticated.
57
+ #
58
+ # @param [String] raw_request the request that should be handled
59
+ def handle_authenticated(raw_request)
60
+ response = nil
61
+
62
+ case raw_request[0...4].unpack1('L>')
63
+ when RubySMB::SMB1::SMB_PROTOCOL_ID
64
+ raise NotImplementedError
65
+ when RubySMB::SMB2::SMB2_PROTOCOL_ID
66
+ raise NotImplementedError
67
+ end
68
+
69
+ if response.nil?
70
+ disconnect!
71
+ return
72
+ end
73
+
74
+ send_packet(response)
75
+ end
76
+
77
+ #
78
+ # Process a GSS authentication buffer. If no buffer is specified, the request is assumed to be the first in the
79
+ # negotiation sequence.
80
+ #
81
+ # @param [String, nil] buffer the request GSS request buffer that should be processed
82
+ # @return [Gss::Provider::Result] the result of the processed GSS request
83
+ def process_gss(buffer=nil)
84
+ @gss_authenticator.process(buffer)
85
+ end
86
+
87
+ #
88
+ # Run the processing loop to receive and handle requests. This loop runs until an exception occurs or the
89
+ # dispatcher socket is closed.
90
+ #
91
+ def run
92
+ loop do
93
+ begin
94
+ raw_request = recv_packet
95
+ rescue RubySMB::Error::CommunicationError
96
+ break
97
+ end
98
+
99
+ case @state
100
+ when :negotiate
101
+ handle_negotiate(raw_request)
102
+ when :session_setup
103
+ handle_session_setup(raw_request)
104
+ when :authenticated
105
+ handle_authenticated(raw_request)
106
+ end
107
+
108
+ break if @dispatcher.tcp_socket.closed?
109
+ end
110
+ end
111
+
112
+ #
113
+ # Disconnect the remote client.
114
+ #
115
+ def disconnect!
116
+ @state = nil
117
+ @dispatcher.tcp_socket.close
118
+ end
119
+
120
+ #
121
+ # Receive a single SMB packet from the dispatcher.
122
+ #
123
+ # @return [String] the raw packet
124
+ def recv_packet
125
+ @dispatcher.recv_packet
126
+ end
127
+
128
+ #
129
+ # Send a single SMB packet using the dispatcher. If necessary, the packet will be signed.
130
+ #
131
+ # @param [GenericPacket] packet the packet to send
132
+ def send_packet(packet)
133
+ if @state == :authenticated && @identity != Gss::Provider::IDENTITY_ANONYMOUS && !@session_key.nil?
134
+ case metadialect.family
135
+ when Dialect::FAMILY_SMB2
136
+ packet = smb2_sign(packet)
137
+ when Dialect::FAMILY_SMB3
138
+ packet = smb3_sign(packet)
139
+ end
140
+ end
141
+
142
+ @dispatcher.send_packet(packet)
143
+ end
144
+
145
+ #
146
+ # Update the preauth integrity hash as used by dialect 3.1.1 for various cryptographic operations. The algorithm
147
+ # and hash values must have been initialized prior to calling this.
148
+ #
149
+ # @param [String] data the data with which to update the preauth integrity hash
150
+ def update_preauth_hash(data)
151
+ unless @preauth_integrity_hash_algorithm
152
+ raise RubySMB::Error::EncryptionError.new(
153
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
154
+ )
155
+ end
156
+ @preauth_integrity_hash_value = OpenSSL::Digest.digest(
157
+ @preauth_integrity_hash_algorithm,
158
+ @preauth_integrity_hash_value + data.to_binary_s
159
+ )
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,54 @@
1
+ require 'socket'
2
+
3
+ module RubySMB
4
+ # This class provides the SMB server core. Settings that are relevant server wide are managed by this object.
5
+ # Currently, the server only supports negotiating and authenticating requests. No other server functionality is
6
+ # available at this time. The negotiating and authentication is supported for SMB versions 1 through 3.1.1.
7
+ class Server
8
+ require 'ruby_smb/server/server_client'
9
+ require 'ruby_smb/gss/provider/ntlm'
10
+
11
+ Connection = Struct.new(:client, :thread)
12
+
13
+ # @param server_sock the socket on which the server should listen
14
+ # @param [Gss::Provider] the authentication provider
15
+ def initialize(server_sock: nil, gss_provider: nil)
16
+ server_sock = ::TCPServer.new(445) if server_sock.nil?
17
+
18
+ @guid = Random.new.bytes(16)
19
+ @socket = server_sock
20
+ @connections = []
21
+ @gss_provider = gss_provider || Gss::Provider::NTLM.new
22
+ # reject the wildcard dialect because it's not a real dialect we can use for this purpose
23
+ @dialects = RubySMB::Dialect::ALL.keys.reject { |dialect| dialect == "0x%04x" % RubySMB::SMB2::SMB2_WILDCARD_REVISION }.reverse
24
+ end
25
+
26
+ # Run the server and accept any connections. For each connection, the block will be executed if specified. When the
27
+ # block returns false, the loop will exit and the server will no long accept new connections.
28
+ def run(&block)
29
+ loop do
30
+ sock = @socket.accept
31
+ server_client = ServerClient.new(self, RubySMB::Dispatcher::Socket.new(sock))
32
+ @connections << Connection.new(server_client, Thread.new { server_client.run })
33
+
34
+ break unless block.nil? || block.call(server_client)
35
+ end
36
+ end
37
+
38
+ # The dialects that this server will negotiate with clients, in ascending order of preference.
39
+ # @!attribute [r] dialects
40
+ # @return [Array<String>]
41
+ attr_accessor :dialects
42
+
43
+ # The GSS Provider instance that this server will use to authenticate
44
+ # incoming client connections.
45
+ # @!attribute [r] gss_provider
46
+ # @return [RubySMB::Gss::Provider::Base]
47
+ attr_reader :gss_provider
48
+
49
+ # The 16 byte GUID that uniquely identifies this server instance.
50
+ # @!attribute [r] guid
51
+ attr_reader :guid
52
+ end
53
+ end
54
+
@@ -0,0 +1,59 @@
1
+ module RubySMB
2
+ # Contains the methods for handling packet signing
3
+ module Signing
4
+ # The NTLM Session Key used for signing
5
+ # @!attribute [rw] session_key
6
+ # @return [String]
7
+ attr_accessor :session_key
8
+
9
+ # Take an SMB1 packet and sign it.
10
+ #
11
+ # @param packet [RubySMB::GenericPacket] the packet to sign
12
+ # @return [RubySMB::GenericPacket] the signed packet
13
+ def smb1_sign(packet)
14
+ # Pack the Sequence counter into a int64le
15
+ packed_sequence_counter = [sequence_counter].pack('Q<')
16
+ packet.smb_header.security_features = packed_sequence_counter
17
+ signature = OpenSSL::Digest::MD5.digest(session_key + packet.to_binary_s)[0, 8]
18
+ packet.smb_header.security_features = signature
19
+ @sequence_counter += 1
20
+
21
+ packet
22
+ end
23
+
24
+ # Take an SMB2 packet and sign it.
25
+ #
26
+ # @param packet [RubySMB::GenericPacket] the packet to sign
27
+ # @return [RubySMB::GenericPacket] the signed packet
28
+ def smb2_sign(packet)
29
+ packet.smb2_header.flags.signed = 1
30
+ packet.smb2_header.signature = "\x00" * 16
31
+ hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), session_key, packet.to_binary_s)
32
+ packet.smb2_header.signature = hmac[0, 16]
33
+
34
+ packet
35
+ end
36
+
37
+ # Take an SMB3 packet and sign it.
38
+ #
39
+ # @param packet [RubySMB::GenericPacket] the packet to sign
40
+ # @return [RubySMB::GenericPacket] the signed packet
41
+ def smb3_sign(packet)
42
+ case @dialect
43
+ when '0x0300', '0x0302'
44
+ signing_key = Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
45
+ when '0x0311'
46
+ signing_key = Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
47
+ else
48
+ raise Error::SigningError.new("Dialect #{@dialect.inspect} is incompatible with SMBv3 signing")
49
+ end
50
+
51
+ packet.smb2_header.flags.signed = 1
52
+ packet.smb2_header.signature = "\x00" * 16
53
+ hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
54
+ packet.smb2_header.signature = hmac[0, 16]
55
+
56
+ packet
57
+ end
58
+ end
59
+ end
@@ -8,17 +8,17 @@ module RubySMB
8
8
 
9
9
  # An SMB_Parameters Block as defined by the {NegotiateResponse}.
10
10
  class ParameterBlock < RubySMB::SMB1::ParameterBlock
11
- uint16 :dialect_index, label: 'Dialect Index'
12
- security_mode :security_mode
13
- uint16 :max_mpx_count, label: 'Max Multiplex Count'
14
- uint16 :max_number_vcs, label: 'Max Virtual Circuits'
15
- uint32 :max_buffer_size, label: 'Max Buffer Size'
16
- uint32 :max_raw_size, label: 'Max Raw Size'
17
- uint32 :session_key, label: 'Session Key'
18
- capabilities :capabilities
19
- file_time :system_time, label: 'Server System Time'
20
- int16 :server_time_zone, label: 'Server TimeZone'
21
- uint8 :challenge_length, label: 'Challenge Length', initial_value: 0x08
11
+ uint16 :dialect_index, label: 'Dialect Index'
12
+ security_mode :security_mode, onlyif: -> { dialect_index != 0xffff }
13
+ uint16 :max_mpx_count, label: 'Max Multiplex Count', onlyif: -> { dialect_index != 0xffff }
14
+ uint16 :max_number_vcs, label: 'Max Virtual Circuits', onlyif: -> { dialect_index != 0xffff }
15
+ uint32 :max_buffer_size, label: 'Max Buffer Size', onlyif: -> { dialect_index != 0xffff }
16
+ uint32 :max_raw_size, label: 'Max Raw Size', onlyif: -> { dialect_index != 0xffff }
17
+ uint32 :session_key, label: 'Session Key', onlyif: -> { dialect_index != 0xffff }
18
+ capabilities :capabilities, onlyif: -> { dialect_index != 0xffff }
19
+ file_time :system_time, label: 'Server System Time', onlyif: -> { dialect_index != 0xffff }
20
+ int16 :server_time_zone, label: 'Server TimeZone', onlyif: -> { dialect_index != 0xffff }
21
+ uint8 :challenge_length, label: 'Challenge Length', initial_value: 0x08, onlyif: -> { dialect_index != 0xffff }
22
22
  end
23
23
 
24
24
  # An SMB_Data Block as defined by the {NegotiateResponse}
@@ -8,7 +8,7 @@ module RubySMB
8
8
 
9
9
  # An SMB_Parameters Block as defined by the {NegotiateResponseExtended}.
10
10
  class ParameterBlock < RubySMB::SMB1::ParameterBlock
11
- uint16 :dialect_index, label: 'Dialect Index'
11
+ uint16 :dialect_index, label: 'Dialect Index'
12
12
  security_mode :security_mode
13
13
  uint16 :max_mpx_count, label: 'Max Multiplex Count'
14
14
  uint16 :max_number_vcs, label: 'Max Virtual Circuits'
@@ -47,7 +47,7 @@ module RubySMB
47
47
 
48
48
  # Takes an NTLM Type 3 Message and creates the GSS Security Blob
49
49
  # for it and sets it in the {RubySMB::SMB1::Packet::SessionSetupRequest::DataBlock#security_blob}
50
- # field. It also automaticaly sets the length in
50
+ # field. It also automatically sets the length in
51
51
  # {RubySMB::SMB1::Packet::SessionSetupRequest::ParameterBlock#security_blob_length}
52
52
  #
53
53
  # @param type3_message [String] the serialized Type 3 NTLM message
@@ -69,9 +69,22 @@ module RubySMB
69
69
  class NetnameNegotiateContextId < BinData::Record
70
70
  endian :little
71
71
 
72
- stringz16 :net_name, label: 'Net Name'
72
+ count_bytes_remaining :bytes_remaining
73
+ default_parameter data_length: nil
74
+ hide :bytes_remaining
75
+
76
+ string16 :net_name, label: 'Net Name', read_length: -> { data_length.nil? ? bytes_remaining : data_length }
73
77
  end
74
78
 
79
+ # An SMB2 TRANSPORT_CAPABILITIES context struct as defined in
80
+ # [2.2.3.1.5 SMB2_TRANSPORT_CAPABILITIES](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/450a1888-a645-4988-8638-5a11f4617545)
81
+ class TransportCapabilities < BinData::Record
82
+ SMB2_ACCEPT_TRANSPORT_LEVEL_SECURITY = 1 # Transport security is offered to skip SMB2 encryption on this connection.
83
+
84
+ endian :little
85
+
86
+ uint32 :flags, label: 'Flags'
87
+ end
75
88
 
76
89
  # An SMB2 NEGOTIATE_CONTEXT struct as defined in
77
90
  # [2.2.3.1 SMB2 NEGOTIATE_CONTEXT Request Values](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/15332256-522e-4a53-8cd7-0bd17678a2f7)
@@ -84,6 +97,8 @@ module RubySMB
84
97
  SMB2_COMPRESSION_CAPABILITIES = 0x0003
85
98
  # The NegotiateContext Data field contains the server name to which the client connects.
86
99
  SMB2_NETNAME_NEGOTIATE_CONTEXT_ID = 0x0005
100
+ # The NegotiateContext Data field contains the transport capabilities, as specified in section 2.2.3.1.5.
101
+ SMB2_TRANSPORT_CAPABILITIES = 0x0006
87
102
 
88
103
  endian :little
89
104
 
@@ -95,7 +110,8 @@ module RubySMB
95
110
  preauth_integrity_capabilities SMB2_PREAUTH_INTEGRITY_CAPABILITIES, label: 'Preauthentication Integrity Capabilities'
96
111
  encryption_capabilities SMB2_ENCRYPTION_CAPABILITIES, label: 'Encryption Capabilities'
97
112
  compression_capabilities SMB2_COMPRESSION_CAPABILITIES, label: 'Compression Capabilities'
98
- netname_negotiate_context_id SMB2_NETNAME_NEGOTIATE_CONTEXT_ID, label: 'Netname Negotiate Context ID'
113
+ netname_negotiate_context_id SMB2_NETNAME_NEGOTIATE_CONTEXT_ID, label: 'Netname Negotiate Context ID', data_length: :data_length
114
+ transport_capabilities SMB2_TRANSPORT_CAPABILITIES, label: 'Transport Capabilities'
99
115
  end
100
116
 
101
117
  def pad_length
@@ -64,6 +64,15 @@ module RubySMB
64
64
  self.negotiate_context_list
65
65
  end
66
66
 
67
+ # Find the first Negotiate Context structure that matches the given
68
+ # context type
69
+ #
70
+ # @param [Integer] the Negotiate Context structure you wish to add
71
+ # @return [NegotiateContext] the Negotiate Context structure or nil if
72
+ # not found
73
+ def find_negotiate_context(type)
74
+ negotiate_context_list.find { |nc| nc.context_type == type }
75
+ end
67
76
 
68
77
  private
69
78
 
@@ -59,7 +59,6 @@ module RubySMB
59
59
  self.negotiate_context_list
60
60
  end
61
61
 
62
-
63
62
  private
64
63
 
65
64
  # Determines the correct length for the padding, so that the next
@@ -11,8 +11,8 @@ module RubySMB
11
11
  uint16 :structure_size, label: 'Structure Size', initial_value: 9
12
12
  session_flags :session_flags
13
13
  uint16 :security_buffer_offset, label: 'Security Buffer Offset', initial_value: 0x48
14
- uint16 :security_buffer_length, label: 'Security Buffer Length'
15
- string :buffer, label: 'Security Buffer', length: -> { security_buffer_length }
14
+ uint16 :security_buffer_length, label: 'Security Buffer Length', initial_value: -> { buffer.length }
15
+ string :buffer, label: 'Security Buffer', read_length: -> { security_buffer_length }
16
16
 
17
17
  def initialize_instance
18
18
  super
@@ -101,7 +101,7 @@ module RubySMB
101
101
  path.to_binary_s.length
102
102
  end
103
103
  end
104
- string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
104
+ string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }, read_length: -> { path_length }
105
105
  tree_connect_request_extension :tree_connect_request_extension, label: 'Tree Connect Request Extension', onlyif: -> { flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
106
106
  end
107
107
  end
data/lib/ruby_smb/smb2.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  module RubySMB
2
2
  # A packet parsing and manipulation library for the SMB2 protocol
3
3
  #
4
- # [[MS-SMB2] Server Mesage Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
4
+ # [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
5
5
  module SMB2
6
6
  # Protocol ID value. Translates to \xFESMB
7
7
  SMB2_PROTOCOL_ID = 0xFE534D42
8
+ # Wildcard revision, see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/63abf97c-0d09-47e2-88d6-6bfa552949a5
9
+ SMB2_WILDCARD_REVISION = 0x02ff
8
10
 
9
11
  require 'ruby_smb/smb2/info_type'
10
12
  require 'ruby_smb/smb2/commands'
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '2.0.10'.freeze
2
+ VERSION = '2.0.11'.freeze
3
3
  end
data/lib/ruby_smb.rb CHANGED
@@ -21,10 +21,11 @@ module RubySMB
21
21
  require 'ruby_smb/generic_packet'
22
22
  require 'ruby_smb/dispatcher'
23
23
  require 'ruby_smb/version'
24
- require 'ruby_smb/version'
25
24
  require 'ruby_smb/smb2'
26
25
  require 'ruby_smb/smb1'
27
26
  require 'ruby_smb/client'
28
27
  require 'ruby_smb/crypto'
29
28
  require 'ruby_smb/compression'
29
+ require 'ruby_smb/server'
30
+ require 'ruby_smb/dialect'
30
31
  end