ruby_smb 1.0.4 → 2.0.2
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.travis.yml +3 -2
- data/Gemfile +6 -2
- data/README.md +35 -47
- data/examples/enum_registry_key.rb +28 -0
- data/examples/enum_registry_values.rb +30 -0
- data/examples/negotiate.rb +51 -8
- data/examples/pipes.rb +2 -1
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +32 -0
- data/lib/ruby_smb.rb +4 -1
- data/lib/ruby_smb/client.rb +207 -18
- data/lib/ruby_smb/client/authentication.rb +27 -8
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +153 -12
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +4 -4
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/client/winreg.rb +46 -0
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +38 -0
- data/lib/ruby_smb/dcerpc/bind.rb +2 -2
- data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
- data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
- data/lib/ruby_smb/dcerpc/request.rb +28 -9
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
- data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
- data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
- data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
- data/lib/ruby_smb/dispatcher/socket.rb +4 -3
- data/lib/ruby_smb/error.rb +28 -1
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +6 -4
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +79 -3
- data/lib/ruby_smb/smb1/tree.rb +12 -3
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +25 -43
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/error_packet.rb +9 -4
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
- data/lib/ruby_smb/smb2/pipe.rb +77 -3
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +23 -17
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1441 -61
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
- data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +29 -2
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +220 -149
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
- metadata +187 -81
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
data/lib/ruby_smb/version.rb
CHANGED
data/ruby_smb.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'ruby_smb/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'ruby_smb'
|
8
8
|
spec.version = RubySMB::VERSION
|
9
|
-
spec.authors = ['David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
|
10
|
-
spec.email = ['
|
9
|
+
spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
|
10
|
+
spec.email = ['msfdev@metasploit.com']
|
11
11
|
spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
|
12
12
|
spec.description = ''
|
13
13
|
spec.homepage = 'https://github.com/rapid7/ruby_smb'
|
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.platform = Gem::Platform::RUBY
|
27
27
|
end
|
28
28
|
|
29
|
-
spec.required_ruby_version = '>= 2.
|
29
|
+
spec.required_ruby_version = '>= 2.5'
|
30
30
|
|
31
31
|
spec.add_development_dependency 'bundler'
|
32
32
|
spec.add_development_dependency 'fivemat'
|
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_runtime_dependency 'rubyntlm'
|
37
37
|
spec.add_runtime_dependency 'windows_error'
|
38
38
|
spec.add_runtime_dependency 'bindata'
|
39
|
+
spec.add_runtime_dependency 'openssl-ccm'
|
40
|
+
spec.add_runtime_dependency 'openssl-cmac'
|
39
41
|
end
|
@@ -6,11 +6,66 @@ RSpec.describe RubySMB::Client do
|
|
6
6
|
let(:username) { 'msfadmin' }
|
7
7
|
let(:password) { 'msfpasswd' }
|
8
8
|
subject(:client) { described_class.new(dispatcher, username: username, password: password) }
|
9
|
-
let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
|
10
|
-
let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
|
9
|
+
let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
|
10
|
+
let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
|
11
|
+
let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
|
11
12
|
let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
|
12
13
|
let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
|
13
14
|
|
15
|
+
it { is_expected.to respond_to :dispatcher }
|
16
|
+
it { is_expected.to respond_to :domain }
|
17
|
+
it { is_expected.to respond_to :local_workstation }
|
18
|
+
it { is_expected.to respond_to :ntlm_client }
|
19
|
+
it { is_expected.to respond_to :password }
|
20
|
+
it { is_expected.to respond_to :peer_native_os }
|
21
|
+
it { is_expected.to respond_to :peer_native_lm }
|
22
|
+
it { is_expected.to respond_to :primary_domain }
|
23
|
+
it { is_expected.to respond_to :default_name }
|
24
|
+
it { is_expected.to respond_to :default_domain }
|
25
|
+
it { is_expected.to respond_to :dns_host_name }
|
26
|
+
it { is_expected.to respond_to :dns_domain_name }
|
27
|
+
it { is_expected.to respond_to :dns_tree_name }
|
28
|
+
it { is_expected.to respond_to :os_version }
|
29
|
+
it { is_expected.to respond_to :dialect }
|
30
|
+
it { is_expected.to respond_to :sequence_counter }
|
31
|
+
it { is_expected.to respond_to :session_id }
|
32
|
+
it { is_expected.to respond_to :signing_required }
|
33
|
+
it { is_expected.to respond_to :smb1 }
|
34
|
+
it { is_expected.to respond_to :smb2 }
|
35
|
+
it { is_expected.to respond_to :smb3 }
|
36
|
+
it { is_expected.to respond_to :smb2_message_id }
|
37
|
+
it { is_expected.to respond_to :username }
|
38
|
+
it { is_expected.to respond_to :user_id }
|
39
|
+
it { is_expected.to respond_to :max_buffer_size }
|
40
|
+
it { is_expected.to respond_to :server_max_buffer_size }
|
41
|
+
it { is_expected.to respond_to :server_max_write_size }
|
42
|
+
it { is_expected.to respond_to :server_max_read_size }
|
43
|
+
it { is_expected.to respond_to :server_max_transact_size }
|
44
|
+
it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
|
45
|
+
it { is_expected.to respond_to :preauth_integrity_hash_value }
|
46
|
+
it { is_expected.to respond_to :encryption_algorithm }
|
47
|
+
it { is_expected.to respond_to :client_encryption_key }
|
48
|
+
it { is_expected.to respond_to :server_encryption_key }
|
49
|
+
it { is_expected.to respond_to :session_encrypt_data }
|
50
|
+
it { is_expected.to respond_to :server_encryption_algorithms }
|
51
|
+
it { is_expected.to respond_to :server_compression_algorithms }
|
52
|
+
it { is_expected.to respond_to :negotiated_smb_version }
|
53
|
+
it { is_expected.to respond_to :session_key }
|
54
|
+
it { is_expected.to respond_to :tree_connects }
|
55
|
+
it { is_expected.to respond_to :open_files }
|
56
|
+
it { is_expected.to respond_to :use_ntlmv2 }
|
57
|
+
it { is_expected.to respond_to :usentlm2_session }
|
58
|
+
it { is_expected.to respond_to :send_lm }
|
59
|
+
it { is_expected.to respond_to :use_lanman_key }
|
60
|
+
it { is_expected.to respond_to :send_ntlm }
|
61
|
+
it { is_expected.to respond_to :spnopt }
|
62
|
+
it { is_expected.to respond_to :evasion_opts }
|
63
|
+
it { is_expected.to respond_to :native_os }
|
64
|
+
it { is_expected.to respond_to :native_lm }
|
65
|
+
it { is_expected.to respond_to :verify_signature }
|
66
|
+
it { is_expected.to respond_to :auth_user }
|
67
|
+
it { is_expected.to respond_to :last_file_id }
|
68
|
+
|
14
69
|
describe '#initialize' do
|
15
70
|
it 'should raise an ArgumentError without a valid dispatcher' do
|
16
71
|
expect { described_class.new(nil) }.to raise_error(ArgumentError)
|
@@ -26,14 +81,21 @@ RSpec.describe RubySMB::Client do
|
|
26
81
|
|
27
82
|
it 'accepts an argument to disable smb1 support' do
|
28
83
|
expect(smb2_client.smb1).to be false
|
84
|
+
expect(smb3_client.smb1).to be false
|
29
85
|
end
|
30
86
|
|
31
87
|
it 'accepts an argument to disable smb2 support' do
|
32
88
|
expect(smb1_client.smb2).to be false
|
89
|
+
expect(smb3_client.smb2).to be false
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'accepts an argument to disable smb3 support' do
|
93
|
+
expect(smb1_client.smb3).to be false
|
94
|
+
expect(smb2_client.smb3).to be false
|
33
95
|
end
|
34
96
|
|
35
|
-
it 'raises an exception if
|
36
|
-
expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
97
|
+
it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
|
98
|
+
expect { described_class.new(dispatcher, smb1: false, smb2: false, smb3: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
37
99
|
end
|
38
100
|
|
39
101
|
it 'sets the username attribute' do
|
@@ -44,6 +106,11 @@ RSpec.describe RubySMB::Client do
|
|
44
106
|
expect(client.password).to eq password
|
45
107
|
end
|
46
108
|
|
109
|
+
it 'sets the session_encrypt_data attribute' do
|
110
|
+
client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
|
111
|
+
expect(client.session_encrypt_data).to eq true
|
112
|
+
end
|
113
|
+
|
47
114
|
it 'creates an NTLM client' do
|
48
115
|
expect(client.ntlm_client).to be_a Net::NTLM::Client
|
49
116
|
end
|
@@ -58,7 +125,7 @@ RSpec.describe RubySMB::Client do
|
|
58
125
|
expect(opt[:workstation]).to eq(local_workstation)
|
59
126
|
expect(opt[:domain]).to eq(domain)
|
60
127
|
flags = Net::NTLM::Client::DEFAULT_FLAGS |
|
61
|
-
Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000
|
128
|
+
Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000 ^ Net::NTLM::FLAGS[:OEM]
|
62
129
|
expect(opt[:flags]).to eq(flags)
|
63
130
|
end
|
64
131
|
|
@@ -76,28 +143,280 @@ RSpec.describe RubySMB::Client do
|
|
76
143
|
end
|
77
144
|
end
|
78
145
|
|
146
|
+
describe '#echo' do
|
147
|
+
let(:response) { double('Echo Response') }
|
148
|
+
before :example do
|
149
|
+
allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with SMB1' do
|
153
|
+
it 'calls #smb1_echo with the expected arguments' do
|
154
|
+
allow(smb1_client).to receive(:smb1_echo).and_return(response)
|
155
|
+
count = 3
|
156
|
+
data = 'testing...'
|
157
|
+
smb1_client.echo(count: count, data: data)
|
158
|
+
expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with SMB2' do
|
163
|
+
it 'calls #smb2_echo without arguments' do
|
164
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
165
|
+
smb2_client.echo
|
166
|
+
expect(smb2_client).to have_received(:smb2_echo)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'with SMB3' do
|
171
|
+
it 'calls #smb2_echo without arguments' do
|
172
|
+
allow(smb3_client).to receive(:smb2_echo).and_return(response)
|
173
|
+
smb3_client.echo
|
174
|
+
expect(smb3_client).to have_received(:smb2_echo)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns the expected status code' do
|
179
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
180
|
+
expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
79
184
|
describe '#send_recv' do
|
80
185
|
let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
|
81
186
|
let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
|
82
187
|
|
83
188
|
before(:each) do
|
84
|
-
|
85
|
-
|
189
|
+
allow(client).to receive(:is_status_pending?).and_return(false)
|
190
|
+
allow(dispatcher).to receive(:send_packet).and_return(nil)
|
191
|
+
allow(dispatcher).to receive(:recv_packet).and_return('A')
|
86
192
|
end
|
87
193
|
|
88
|
-
|
89
|
-
|
90
|
-
|
194
|
+
context 'when signing' do
|
195
|
+
it 'calls #smb1_sign if it is an SMB1 packet' do
|
196
|
+
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
197
|
+
client.send_recv(smb1_request)
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'with an SMB2 packet' do
|
201
|
+
it 'does not sign a SessionSetupRequest packet' do
|
202
|
+
expect(smb2_client).to_not receive(:smb2_sign)
|
203
|
+
expect(smb2_client).to_not receive(:smb3_sign)
|
204
|
+
client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'calls #smb2_sign if it is an SMB2 client' do
|
208
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
209
|
+
expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
210
|
+
smb2_client.send_recv(smb2_request)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'calls #smb3_sign if it is an SMB3 client' do
|
214
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
215
|
+
expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
|
216
|
+
smb3_client.send_recv(smb2_request)
|
217
|
+
end
|
218
|
+
end
|
91
219
|
end
|
92
220
|
|
93
|
-
it '
|
94
|
-
expect(
|
221
|
+
it 'sends the expected packet and gets the response' do
|
222
|
+
expect(dispatcher).to receive(:send_packet).with(smb1_request)
|
223
|
+
expect(dispatcher).to receive(:recv_packet)
|
95
224
|
client.send_recv(smb1_request)
|
96
225
|
end
|
97
226
|
|
98
|
-
|
99
|
-
|
100
|
-
|
227
|
+
context 'with SMB1' do
|
228
|
+
it 'does not check if it is a STATUS_PENDING response' do
|
229
|
+
expect(smb1_client).to_not receive(:is_status_pending?)
|
230
|
+
smb1_client.send_recv(smb1_request)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context 'with SMB2' do
|
235
|
+
context 'when receiving a STATUS_PENDING response' do
|
236
|
+
it 'waits 1 second and reads/decrypts again' do
|
237
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
|
238
|
+
expect(smb2_client).to receive(:sleep).with(1)
|
239
|
+
expect(dispatcher).to receive(:recv_packet).twice
|
240
|
+
smb2_client.send_recv(smb2_request)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'with SMB3 and encryption' do
|
246
|
+
before :example do
|
247
|
+
smb3_client.dialect = '0x0300'
|
248
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'with a SessionSetupRequest' do
|
252
|
+
it 'does not encrypt/decrypt' do
|
253
|
+
request = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
254
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
255
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
256
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
257
|
+
expect(dispatcher).to receive(:recv_packet)
|
258
|
+
smb3_client.send_recv(request)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'with a NegotiateRequest' do
|
263
|
+
it 'does not encrypt/decrypt' do
|
264
|
+
request = RubySMB::SMB2::Packet::NegotiateRequest.new
|
265
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
266
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
267
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
268
|
+
expect(dispatcher).to receive(:recv_packet)
|
269
|
+
smb3_client.send_recv(request)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'encrypts and decrypts' do
|
274
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
275
|
+
expect(smb3_client).to receive(:recv_encrypt)
|
276
|
+
smb3_client.send_recv(smb2_request)
|
277
|
+
end
|
278
|
+
|
279
|
+
context 'when receiving a STATUS_PENDING response' do
|
280
|
+
it 'waits 1 second and reads/decrypts again' do
|
281
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
|
282
|
+
expect(smb3_client).to receive(:sleep).with(1)
|
283
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
284
|
+
expect(smb3_client).to receive(:recv_encrypt).twice
|
285
|
+
smb3_client.send_recv(smb2_request)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe '#is_status_pending?' do
|
292
|
+
let(:response) {
|
293
|
+
res = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
294
|
+
res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
|
295
|
+
res.smb2_header.flags.async_command = 1
|
296
|
+
res
|
297
|
+
}
|
298
|
+
|
299
|
+
it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
|
300
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be true
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
304
|
+
response.smb2_header.flags.async_command = 0
|
305
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
309
|
+
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
310
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe '#can_be_encrypted?' do
|
315
|
+
it 'returns true if the packet can be encrypted' do
|
316
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
317
|
+
expect(client.can_be_encrypted?(packet)).to be true
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'returns false if it is an SMB1 packet' do
|
321
|
+
packet = RubySMB::SMB1::Packet::LogoffRequest.new
|
322
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
323
|
+
end
|
324
|
+
|
325
|
+
[RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
|
326
|
+
it "returns false if the packet is a #{klass}" do
|
327
|
+
packet = klass.new
|
328
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe '#encryption_supported?' do
|
334
|
+
['0x0300', '0x0302', '0x0311'].each do |dialect|
|
335
|
+
it "returns true if the dialect is #{dialect}" do
|
336
|
+
client.dialect = dialect
|
337
|
+
expect(client.encryption_supported?).to be true
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
it "returns false if the dialect does not support encryption" do
|
342
|
+
client.dialect = '0x0202'
|
343
|
+
expect(client.encryption_supported?).to be false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe '#send_encrypt' do
|
348
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
349
|
+
before :example do
|
350
|
+
allow(dispatcher).to receive(:send_packet)
|
351
|
+
client.dialect = '0x0300'
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'creates a Transform request' do
|
355
|
+
expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
|
356
|
+
client.send_encrypt(packet)
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'raises an EncryptionError exception if an error occurs while encrypting' do
|
360
|
+
allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
|
361
|
+
expect { client.send_encrypt(packet) }.to raise_error(
|
362
|
+
RubySMB::Error::EncryptionError,
|
363
|
+
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
364
|
+
)
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'sends the encrypted packet' do
|
368
|
+
encrypted_packet = double('Encrypted packet')
|
369
|
+
allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
|
370
|
+
client.send_encrypt(packet)
|
371
|
+
expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe '#recv_encrypt' do
|
376
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
377
|
+
before :example do
|
378
|
+
allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
|
379
|
+
client.dialect = '0x0300'
|
380
|
+
allow(client).to receive(:smb3_decrypt)
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'reads the response packet' do
|
384
|
+
client.recv_encrypt
|
385
|
+
expect(dispatcher).to have_received(:recv_packet)
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'raises an EncryptionError exception if an error occurs while receiving the response' do
|
389
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
390
|
+
expect { client.recv_encrypt }.to raise_error(
|
391
|
+
RubySMB::Error::EncryptionError,
|
392
|
+
'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
|
393
|
+
'The server supports encryption but was not able to handle the encrypted request.'
|
394
|
+
)
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'parses the response as a Transform response packet' do
|
398
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
399
|
+
client.recv_encrypt
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
403
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
404
|
+
expect { client.recv_encrypt }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'decrypts the Transform response packet' do
|
408
|
+
transform = double('Transform header packet')
|
409
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
410
|
+
client.recv_encrypt
|
411
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
415
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
|
416
|
+
expect { client.recv_encrypt }.to raise_error(
|
417
|
+
RubySMB::Error::EncryptionError,
|
418
|
+
'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
|
419
|
+
)
|
101
420
|
end
|
102
421
|
end
|
103
422
|
|
@@ -240,6 +559,44 @@ RSpec.describe RubySMB::Client do
|
|
240
559
|
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
241
560
|
end
|
242
561
|
end
|
562
|
+
|
563
|
+
context 'with SMB3' do
|
564
|
+
let(:raw_response) { double('Raw response') }
|
565
|
+
let(:logoff_response) {
|
566
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
567
|
+
}
|
568
|
+
before :example do
|
569
|
+
allow(smb3_client).to receive(:send_recv).and_return(raw_response)
|
570
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
571
|
+
allow(smb3_client).to receive(:wipe_state!)
|
572
|
+
end
|
573
|
+
|
574
|
+
it 'creates a LogoffRequest packet' do
|
575
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
576
|
+
smb3_client.logoff!
|
577
|
+
end
|
578
|
+
|
579
|
+
it 'calls #send_recv' do
|
580
|
+
expect(smb3_client).to receive(:send_recv)
|
581
|
+
smb3_client.logoff!
|
582
|
+
end
|
583
|
+
|
584
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
585
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
586
|
+
smb3_client.logoff!
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
590
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
591
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
595
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
596
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
597
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
598
|
+
end
|
599
|
+
end
|
243
600
|
end
|
244
601
|
|
245
602
|
context 'NetBIOS Session Service' do
|
@@ -365,7 +722,8 @@ RSpec.describe RubySMB::Client do
|
|
365
722
|
smb1_extended_response.to_binary_s
|
366
723
|
}
|
367
724
|
|
368
|
-
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
725
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
726
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
369
727
|
|
370
728
|
describe '#smb1_negotiate_request' do
|
371
729
|
it 'returns an SMB1 Negotiate Request packet' do
|
@@ -373,33 +731,158 @@ RSpec.describe RubySMB::Client do
|
|
373
731
|
end
|
374
732
|
|
375
733
|
it 'sets the default SMB1 Dialect' do
|
376
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
734
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
735
|
+
buffer_format: 2,
|
736
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
737
|
+
)
|
377
738
|
end
|
378
739
|
|
379
740
|
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
380
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
741
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
742
|
+
buffer_format: 2,
|
743
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
744
|
+
)
|
381
745
|
end
|
382
746
|
|
383
747
|
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
384
|
-
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
748
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
749
|
+
buffer_format: 2,
|
750
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
751
|
+
)
|
385
752
|
end
|
386
753
|
|
387
754
|
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
388
|
-
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
755
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
756
|
+
buffer_format: 2,
|
757
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
758
|
+
)
|
759
|
+
end
|
760
|
+
|
761
|
+
it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
|
762
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
763
|
+
buffer_format: 2,
|
764
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
765
|
+
)
|
766
|
+
end
|
767
|
+
|
768
|
+
it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
|
769
|
+
expect(smb3_client.smb1_negotiate_request.dialects).to include(
|
770
|
+
buffer_format: 2,
|
771
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
772
|
+
)
|
773
|
+
end
|
774
|
+
|
775
|
+
it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
|
776
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
777
|
+
buffer_format: 2,
|
778
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
779
|
+
)
|
389
780
|
end
|
390
781
|
end
|
391
782
|
|
392
|
-
describe '#
|
783
|
+
describe '#smb2_3_negotiate_request' do
|
393
784
|
it 'return an SMB2 Negotiate Request packet' do
|
394
|
-
expect(client.
|
785
|
+
expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
|
789
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
790
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
791
|
+
)
|
395
792
|
end
|
396
793
|
|
397
|
-
it '
|
398
|
-
expect(
|
794
|
+
it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
|
795
|
+
expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
|
796
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
797
|
+
)
|
399
798
|
end
|
400
799
|
|
401
800
|
it 'sets the Message ID to 0' do
|
402
|
-
expect(client.
|
801
|
+
expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
|
802
|
+
end
|
803
|
+
|
804
|
+
it 'adds SMB3 dialects if if SMB3 support is enabled' do
|
805
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
806
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
807
|
+
)
|
808
|
+
end
|
809
|
+
|
810
|
+
it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
|
811
|
+
expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
|
812
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
813
|
+
)
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
describe '#add_smb3_to_negotiate_request' do
|
818
|
+
let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
|
819
|
+
|
820
|
+
it 'adds the default SMB3 dialects' do
|
821
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
|
822
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
823
|
+
)
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'raises the expected exception when the dialects is not an array of strings' do
|
827
|
+
dialects = ['0x0300', 0x0302, '0x0311']
|
828
|
+
expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'sets encryption capability flag' do
|
832
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
|
833
|
+
end
|
834
|
+
|
835
|
+
context 'when the negotiate packet includes the 0x0311 dialect' do
|
836
|
+
before :example do
|
837
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
|
838
|
+
end
|
839
|
+
|
840
|
+
it 'adds 3 Negotiate Contexts' do
|
841
|
+
expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
|
842
|
+
end
|
843
|
+
|
844
|
+
it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
|
845
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
846
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
847
|
+
end
|
848
|
+
expect(nc.length).to eq(1)
|
849
|
+
expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
|
850
|
+
end
|
851
|
+
|
852
|
+
it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
|
853
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
854
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
855
|
+
end
|
856
|
+
expect(nc.length).to eq(1)
|
857
|
+
expect(nc.first.data.ciphers).to eq(
|
858
|
+
[
|
859
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
860
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
861
|
+
]
|
862
|
+
)
|
863
|
+
end
|
864
|
+
|
865
|
+
it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
|
866
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
867
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
868
|
+
end
|
869
|
+
expect(nc.length).to eq(1)
|
870
|
+
expect(nc.first.data.compression_algorithms).to eq(
|
871
|
+
[
|
872
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
873
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77,
|
874
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
|
875
|
+
RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
876
|
+
]
|
877
|
+
)
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
context 'when the negotiate packet does not include the 0x0311 dialect' do
|
882
|
+
it 'does not add any Negotiate Context' do
|
883
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
|
884
|
+
expect(negotiate_request.negotiate_context_list?). to be false
|
885
|
+
end
|
403
886
|
end
|
404
887
|
end
|
405
888
|
|
@@ -414,10 +897,15 @@ RSpec.describe RubySMB::Client do
|
|
414
897
|
client.negotiate_request
|
415
898
|
end
|
416
899
|
|
417
|
-
it 'calls #
|
418
|
-
expect(smb2_client).to receive(:
|
900
|
+
it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
|
901
|
+
expect(smb2_client).to receive(:smb2_3_negotiate_request)
|
419
902
|
smb2_client.negotiate_request
|
420
903
|
end
|
904
|
+
|
905
|
+
it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
|
906
|
+
expect(smb3_client).to receive(:smb2_3_negotiate_request)
|
907
|
+
smb3_client.negotiate_request
|
908
|
+
end
|
421
909
|
end
|
422
910
|
|
423
911
|
describe '#negotiate_response' do
|
@@ -464,12 +952,28 @@ RSpec.describe RubySMB::Client do
|
|
464
952
|
end
|
465
953
|
end
|
466
954
|
|
467
|
-
context 'with
|
955
|
+
context 'with only SMB3' do
|
956
|
+
it 'returns a properly formed packet' do
|
957
|
+
expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
958
|
+
end
|
959
|
+
|
960
|
+
it 'raises an exception if the Response is invalid' do
|
961
|
+
expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
962
|
+
end
|
963
|
+
|
964
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
965
|
+
bogus_response = smb2_response
|
966
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
967
|
+
expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
context 'with SMB1, SMB2 and SMB3 enabled' do
|
468
972
|
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
469
973
|
expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
470
974
|
end
|
471
975
|
|
472
|
-
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
976
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
|
473
977
|
expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
474
978
|
end
|
475
979
|
end
|
@@ -477,9 +981,10 @@ RSpec.describe RubySMB::Client do
|
|
477
981
|
|
478
982
|
describe '#parse_negotiate_response' do
|
479
983
|
context 'when SMB1 was Negotiated' do
|
480
|
-
it 'turns off SMB2 support' do
|
984
|
+
it 'turns off SMB2 and SMB3 support' do
|
481
985
|
client.parse_negotiate_response(smb1_extended_response)
|
482
986
|
expect(client.smb2).to be false
|
987
|
+
expect(client.smb3).to be false
|
483
988
|
end
|
484
989
|
|
485
990
|
it 'sets whether or not signing is required' do
|
@@ -502,12 +1007,18 @@ RSpec.describe RubySMB::Client do
|
|
502
1007
|
it 'returns the string \'SMB1\'' do
|
503
1008
|
expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
|
504
1009
|
end
|
1010
|
+
|
1011
|
+
it 'sets #negotiated_smb_version to 1' do
|
1012
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1013
|
+
expect(client.negotiated_smb_version).to eq(1)
|
1014
|
+
end
|
505
1015
|
end
|
506
1016
|
|
507
1017
|
context 'when SMB2 was negotiated' do
|
508
|
-
it 'turns off SMB1 support' do
|
1018
|
+
it 'turns off SMB1 and SMB3 support' do
|
509
1019
|
client.parse_negotiate_response(smb2_response)
|
510
1020
|
expect(client.smb1).to be false
|
1021
|
+
expect(client.smb3).to be false
|
511
1022
|
end
|
512
1023
|
|
513
1024
|
it 'sets whether or not signing is required' do
|
@@ -526,9 +1037,122 @@ RSpec.describe RubySMB::Client do
|
|
526
1037
|
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
527
1038
|
end
|
528
1039
|
end
|
1040
|
+
|
1041
|
+
context 'when SMB3 was negotiated' do
|
1042
|
+
it 'turns off SMB1 and SMB2 support' do
|
1043
|
+
client.parse_negotiate_response(smb3_response)
|
1044
|
+
expect(client.smb1).to be false
|
1045
|
+
expect(client.smb2).to be false
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
it 'sets whether or not signing is required' do
|
1049
|
+
smb3_response.security_mode.signing_required = 1
|
1050
|
+
client.parse_negotiate_response(smb3_response)
|
1051
|
+
expect(client.signing_required).to be true
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
it 'sets #dialect to the negotiated dialect' do
|
1055
|
+
client.parse_negotiate_response(smb3_response)
|
1056
|
+
expect(client.dialect).to eq '0x0300'
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
it 'returns the string \'SMB2\'' do
|
1060
|
+
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
context 'when the server supports encryption' do
|
1064
|
+
before :example do
|
1065
|
+
smb3_response.capabilities.encryption = 1
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
it 'sets the expected encryption algorithm' do
|
1069
|
+
client.parse_negotiate_response(smb3_response)
|
1070
|
+
expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
it 'keeps session encryption enabled if it was already' do
|
1074
|
+
client.session_encrypt_data = true
|
1075
|
+
client.parse_negotiate_response(smb3_response)
|
1076
|
+
expect(client.session_encrypt_data).to be true
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
it 'keeps session encryption disabled if it was already' do
|
1080
|
+
client.session_encrypt_data = false
|
1081
|
+
client.parse_negotiate_response(smb3_response)
|
1082
|
+
expect(client.session_encrypt_data).to be false
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
context 'when the server does not support encryption' do
|
1087
|
+
before :example do
|
1088
|
+
smb3_response.capabilities.encryption = 0
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
it 'disables session encryption if it was already enabled' do
|
1092
|
+
client.session_encrypt_data = true
|
1093
|
+
client.parse_negotiate_response(smb3_response)
|
1094
|
+
expect(client.session_encrypt_data).to be false
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
it 'keeps session encryption disabled if it was already' do
|
1098
|
+
client.session_encrypt_data = false
|
1099
|
+
client.parse_negotiate_response(smb3_response)
|
1100
|
+
expect(client.session_encrypt_data).to be false
|
1101
|
+
end
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
context 'when the response contains the SMB2 wildcard revision number dialect' do
|
1106
|
+
it 'only turns off SMB1 support' do
|
1107
|
+
smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
|
1108
|
+
client.parse_negotiate_response(smb2_response)
|
1109
|
+
expect(client.smb1).to be false
|
1110
|
+
expect(client.smb2).to be true
|
1111
|
+
expect(client.smb3).to be true
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
context 'when the negotiation failed' do
|
1116
|
+
context 'with a STATUS_NOT_SUPPORTED status code' do
|
1117
|
+
before :example do
|
1118
|
+
error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
it 'raises the expected exception with SMB2' do
|
1122
|
+
expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1123
|
+
RubySMB::Error::NegotiationFailure,
|
1124
|
+
'Unable to negotiate with remote host, SMB2 not supported'
|
1125
|
+
)
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
it 'raises the expected exception with SMB3' do
|
1129
|
+
expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1130
|
+
RubySMB::Error::NegotiationFailure,
|
1131
|
+
'Unable to negotiate with remote host, SMB3 not supported'
|
1132
|
+
)
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
context 'with an unknown status code' do
|
1137
|
+
it 'raises the expected exception' do
|
1138
|
+
expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
|
1139
|
+
RubySMB::Error::NegotiationFailure,
|
1140
|
+
'Unable to negotiate with remote host'
|
1141
|
+
)
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
end
|
529
1145
|
end
|
530
1146
|
|
531
1147
|
describe '#negotiate' do
|
1148
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1149
|
+
before :example do
|
1150
|
+
allow(client).to receive(:negotiate_request)
|
1151
|
+
allow(client).to receive(:send_recv)
|
1152
|
+
allow(client).to receive(:negotiate_response)
|
1153
|
+
allow(client).to receive(:parse_negotiate_response)
|
1154
|
+
end
|
1155
|
+
|
532
1156
|
it 'calls the backing methods' do
|
533
1157
|
expect(client).to receive(:negotiate_request)
|
534
1158
|
expect(client).to receive(:send_recv)
|
@@ -537,18 +1161,247 @@ RSpec.describe RubySMB::Client do
|
|
537
1161
|
client.negotiate
|
538
1162
|
end
|
539
1163
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
1164
|
+
context 'with SMB1' do
|
1165
|
+
it 'sets the response-packet #dialects array with the dialects sent in the request' do
|
1166
|
+
request_packet = client.smb1_negotiate_request
|
1167
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1168
|
+
allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
|
1169
|
+
expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
|
1170
|
+
client.negotiate
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
context "with 0x0311 dialect" do
|
1175
|
+
it 'calls #parse_negotiate_response and updates the preauth hash' do
|
1176
|
+
client.dialect = '0x0311'
|
1177
|
+
request_packet = client.smb2_3_negotiate_request
|
1178
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1179
|
+
allow(client).to receive(:negotiate_response).and_return(smb3_response)
|
1180
|
+
expect(client).to receive(:parse_negotiate_response).with(smb3_response)
|
1181
|
+
expect(client).to receive(:update_preauth_hash).with(request_packet)
|
1182
|
+
expect(client).to receive(:update_preauth_hash).with(smb3_response)
|
1183
|
+
client.negotiate
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
context 'with a wildcard revision number response' do
|
1188
|
+
before :example do
|
1189
|
+
client.dialect = '0x02ff'
|
1190
|
+
allow(client).to receive(:smb2_message_id=) do
|
1191
|
+
client.dialect = '0x0202'
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
it 'increments the message ID' do
|
1196
|
+
expect(client).to receive(:smb2_message_id=).with(1)
|
1197
|
+
client.negotiate
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
it 're-negotiates' do
|
1201
|
+
expect(client).to receive(:negotiate_request).twice
|
1202
|
+
expect(client).to receive(:send_recv).twice
|
1203
|
+
expect(client).to receive(:negotiate_response).twice
|
1204
|
+
expect(client).to receive(:parse_negotiate_response).twice
|
1205
|
+
client.negotiate
|
1206
|
+
end
|
547
1207
|
end
|
548
1208
|
|
549
|
-
|
550
|
-
|
551
|
-
|
1209
|
+
context 'when an error occurs' do
|
1210
|
+
before :example do
|
1211
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1212
|
+
allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
|
1213
|
+
client.smb1 = false
|
1214
|
+
client.smb2 = false
|
1215
|
+
client.smb3 = false
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
context 'with SMB1' do
|
1219
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1220
|
+
|
1221
|
+
it 'raise the expected exception' do
|
1222
|
+
client.smb1 = true
|
1223
|
+
expect { client.negotiate }.to raise_error(
|
1224
|
+
RubySMB::Error::NegotiationFailure,
|
1225
|
+
"Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
|
1226
|
+
)
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
context 'with SMB2' do
|
1231
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1232
|
+
|
1233
|
+
it 'raise the expected exception' do
|
1234
|
+
client.smb2 = true
|
1235
|
+
expect { client.negotiate }.to raise_error(
|
1236
|
+
RubySMB::Error::NegotiationFailure,
|
1237
|
+
"Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
|
1238
|
+
)
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
context 'with SMB3' do
|
1243
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1244
|
+
|
1245
|
+
it 'raise the expected exception' do
|
1246
|
+
client.smb3 = true
|
1247
|
+
expect { client.negotiate }.to raise_error(
|
1248
|
+
RubySMB::Error::NegotiationFailure,
|
1249
|
+
"Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
|
1250
|
+
)
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
describe '#parse_smb3_capabilities' do
|
1256
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1257
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
|
1258
|
+
let(:nc_encryption) do
|
1259
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1260
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1261
|
+
)
|
1262
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
1263
|
+
nc
|
1264
|
+
end
|
1265
|
+
let(:nc_integrity) do
|
1266
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1267
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1268
|
+
)
|
1269
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
1270
|
+
nc
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
before :example do
|
1274
|
+
allow(smb3_client).to receive(:update_preauth_hash)
|
1275
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1276
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
context 'when selecting the integrity hash algorithm' do
|
1280
|
+
context 'with one algorithm' do
|
1281
|
+
it 'selects the expected algorithm' do
|
1282
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1283
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
context 'with multiple algorithms' do
|
1288
|
+
it 'selects the first algorithm' do
|
1289
|
+
nc = smb3_response.find_negotiate_context(
|
1290
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1291
|
+
)
|
1292
|
+
nc.data.hash_algorithms << 3
|
1293
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1294
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1295
|
+
end
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
context 'without integrity negotiate context' do
|
1299
|
+
it 'raises the expected exception' do
|
1300
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1301
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1302
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1303
|
+
RubySMB::Error::EncryptionError,
|
1304
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1305
|
+
)
|
1306
|
+
end
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
context 'with an unknown integrity hash algorithm' do
|
1310
|
+
it 'raises the expected exception' do
|
1311
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1312
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1313
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1314
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1315
|
+
)
|
1316
|
+
nc.data.hash_algorithms << 5
|
1317
|
+
smb3_response.add_negotiate_context(nc)
|
1318
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1319
|
+
RubySMB::Error::EncryptionError,
|
1320
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1321
|
+
)
|
1322
|
+
end
|
1323
|
+
end
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
context 'when selecting the encryption algorithm' do
|
1327
|
+
context 'with one algorithm' do
|
1328
|
+
it 'selects the expected algorithm' do
|
1329
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1330
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1331
|
+
end
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
context 'with multiple algorithms' do
|
1335
|
+
it 'selects the AES-128-GCM algorithm if included' do
|
1336
|
+
nc = smb3_response.find_negotiate_context(
|
1337
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1338
|
+
)
|
1339
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1340
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1341
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
it 'selects the first algorithm if AES-128-GCM is not included' do
|
1345
|
+
nc = smb3_response.find_negotiate_context(
|
1346
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1347
|
+
)
|
1348
|
+
nc.data.ciphers << 3
|
1349
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1350
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
it 'keep tracks of the server supported algorithms' do
|
1354
|
+
nc = smb3_response.find_negotiate_context(
|
1355
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1356
|
+
)
|
1357
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1358
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1359
|
+
expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
context 'without encryption context' do
|
1364
|
+
it 'raises the expected exception' do
|
1365
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1366
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1367
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1368
|
+
RubySMB::Error::EncryptionError,
|
1369
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1370
|
+
)
|
1371
|
+
end
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
context 'with an unknown encryption algorithm' do
|
1375
|
+
it 'raises the expected exception' do
|
1376
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1377
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1378
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1379
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1380
|
+
)
|
1381
|
+
nc.data.ciphers << 14
|
1382
|
+
smb3_response.add_negotiate_context(nc)
|
1383
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1384
|
+
RubySMB::Error::EncryptionError,
|
1385
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1386
|
+
)
|
1387
|
+
end
|
1388
|
+
end
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
context 'when selecting the compression algorithm' do
|
1392
|
+
it 'keep tracks of the server supported algorithms' do
|
1393
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1394
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
1395
|
+
)
|
1396
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
1397
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
1398
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
1399
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
1400
|
+
smb3_response.add_negotiate_context(nc)
|
1401
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1402
|
+
expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
|
1403
|
+
end
|
1404
|
+
end
|
552
1405
|
end
|
553
1406
|
end
|
554
1407
|
end
|
@@ -875,6 +1728,39 @@ RSpec.describe RubySMB::Client do
|
|
875
1728
|
smb2_client.smb2_authenticate
|
876
1729
|
expect(smb2_client.os_version).to eq '6.1.7601'
|
877
1730
|
end
|
1731
|
+
|
1732
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1733
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1734
|
+
smb2_client.dialect = dialect
|
1735
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1736
|
+
smb2_client.smb2_authenticate
|
1737
|
+
end
|
1738
|
+
end
|
1739
|
+
|
1740
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1741
|
+
smb2_client.dialect = '0x0311'
|
1742
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
|
1743
|
+
smb2_client.smb2_authenticate
|
1744
|
+
end
|
1745
|
+
|
1746
|
+
context 'when setting the session_encrypt_data parameter' do
|
1747
|
+
before :example do
|
1748
|
+
smb2_client.smb3 = true
|
1749
|
+
smb2_client.session_encrypt_data = false
|
1750
|
+
end
|
1751
|
+
|
1752
|
+
it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
|
1753
|
+
final_response_packet.session_flags.encrypt_data = 1
|
1754
|
+
smb2_client.smb2_authenticate
|
1755
|
+
expect(smb2_client.session_encrypt_data).to be true
|
1756
|
+
end
|
1757
|
+
|
1758
|
+
it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
|
1759
|
+
final_response_packet.session_flags.encrypt_data = 0
|
1760
|
+
smb2_client.smb2_authenticate
|
1761
|
+
expect(smb2_client.session_encrypt_data).to be false
|
1762
|
+
end
|
1763
|
+
end
|
878
1764
|
end
|
879
1765
|
|
880
1766
|
describe '#smb2_ntlmssp_negotiate_packet' do
|
@@ -890,20 +1776,34 @@ RSpec.describe RubySMB::Client do
|
|
890
1776
|
smb2_client.smb2_ntlmssp_negotiate_packet
|
891
1777
|
end
|
892
1778
|
|
893
|
-
it '
|
894
|
-
expect(smb2_client.smb2_ntlmssp_negotiate_packet.
|
895
|
-
end
|
896
|
-
|
897
|
-
it 'increments client#smb2_message_id' do
|
898
|
-
expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
|
1779
|
+
it 'enables signing' do
|
1780
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
|
899
1781
|
end
|
900
1782
|
end
|
901
1783
|
|
902
1784
|
describe '#smb2_ntlmssp_negotiate' do
|
1785
|
+
before :example do
|
1786
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
1787
|
+
allow(smb2_client).to receive(:send_recv)
|
1788
|
+
end
|
1789
|
+
|
903
1790
|
it 'sends the request packet and receives a response' do
|
904
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
905
|
-
expect(
|
906
|
-
|
1791
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
1792
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1793
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1797
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1798
|
+
smb2_client.dialect = dialect
|
1799
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1800
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1801
|
+
end
|
1802
|
+
end
|
1803
|
+
|
1804
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1805
|
+
smb2_client.dialect = '0x0311'
|
1806
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
907
1807
|
smb2_client.smb2_ntlmssp_negotiate
|
908
1808
|
end
|
909
1809
|
end
|
@@ -961,13 +1861,35 @@ RSpec.describe RubySMB::Client do
|
|
961
1861
|
it 'sets the session ID on the request packet' do
|
962
1862
|
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
|
963
1863
|
end
|
1864
|
+
|
1865
|
+
it 'enables signing' do
|
1866
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
|
1867
|
+
end
|
964
1868
|
end
|
965
1869
|
|
966
1870
|
describe '#smb2_ntlmssp_authenticate' do
|
1871
|
+
before :example do
|
1872
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
1873
|
+
allow(smb2_client).to receive(:send_recv)
|
1874
|
+
end
|
1875
|
+
|
967
1876
|
it 'sends the request packet and receives a response' do
|
968
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
969
|
-
expect(
|
970
|
-
|
1877
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
1878
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1879
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1880
|
+
end
|
1881
|
+
|
1882
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1883
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1884
|
+
smb2_client.dialect = dialect
|
1885
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1886
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1887
|
+
end
|
1888
|
+
end
|
1889
|
+
|
1890
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1891
|
+
smb2_client.dialect = '0x0311'
|
1892
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
971
1893
|
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
972
1894
|
end
|
973
1895
|
end
|
@@ -1103,6 +2025,108 @@ RSpec.describe RubySMB::Client do
|
|
1103
2025
|
end
|
1104
2026
|
end
|
1105
2027
|
end
|
2028
|
+
|
2029
|
+
describe '#smb3_sign' do
|
2030
|
+
context 'if signing is required and we have a session key' do
|
2031
|
+
let(:request) {
|
2032
|
+
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
2033
|
+
packet.smb2_header.flags.signed = 1
|
2034
|
+
packet.smb2_header.signature = "\x00" * 16
|
2035
|
+
packet
|
2036
|
+
}
|
2037
|
+
let(:session_key) { 'Session Key' }
|
2038
|
+
before :example do
|
2039
|
+
smb3_client.session_key = session_key
|
2040
|
+
smb3_client.signing_required = true
|
2041
|
+
end
|
2042
|
+
|
2043
|
+
['0x0300', '0x0302'].each do |dialect|
|
2044
|
+
context "with #{dialect} dialect" do
|
2045
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2046
|
+
smb3_client.dialect = dialect
|
2047
|
+
fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
|
2048
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2049
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2050
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2051
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2052
|
+
end
|
2053
|
+
end
|
2054
|
+
end
|
2055
|
+
|
2056
|
+
context "with 0x0311 dialect" do
|
2057
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2058
|
+
smb3_client.dialect = '0x0311'
|
2059
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2060
|
+
fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
|
2061
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2062
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2063
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2064
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2065
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2066
|
+
end
|
2067
|
+
end
|
2068
|
+
|
2069
|
+
context 'with an incompatible dialect' do
|
2070
|
+
it 'raises the expected exception' do
|
2071
|
+
smb3_client.dialect = '0x0202'
|
2072
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2073
|
+
RubySMB::Error::SigningError,
|
2074
|
+
'Dialect is incompatible with SMBv3 signing'
|
2075
|
+
)
|
2076
|
+
end
|
2077
|
+
end
|
2078
|
+
end
|
2079
|
+
|
2080
|
+
context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
|
2081
|
+
let(:request) {
|
2082
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
2083
|
+
packet.smb2_header.flags.signed = 1
|
2084
|
+
packet.smb2_header.signature = "\x00" * 16
|
2085
|
+
packet
|
2086
|
+
}
|
2087
|
+
let(:session_key) { 'Session Key' }
|
2088
|
+
before :example do
|
2089
|
+
smb3_client.session_key = session_key
|
2090
|
+
smb3_client.signing_required = false
|
2091
|
+
end
|
2092
|
+
|
2093
|
+
['0x0300', '0x0302'].each do |dialect|
|
2094
|
+
context "with #{dialect} dialect" do
|
2095
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2096
|
+
smb3_client.dialect = dialect
|
2097
|
+
fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
|
2098
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2099
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2100
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2101
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2102
|
+
end
|
2103
|
+
end
|
2104
|
+
end
|
2105
|
+
|
2106
|
+
context "with 0x0311 dialect" do
|
2107
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2108
|
+
smb3_client.dialect = '0x0311'
|
2109
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2110
|
+
fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
|
2111
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2112
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2113
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2114
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2115
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2116
|
+
end
|
2117
|
+
end
|
2118
|
+
|
2119
|
+
context 'with an incompatible dialect' do
|
2120
|
+
it 'raises the expected exception' do
|
2121
|
+
smb3_client.dialect = '0x0202'
|
2122
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2123
|
+
RubySMB::Error::SigningError,
|
2124
|
+
'Dialect is incompatible with SMBv3 signing'
|
2125
|
+
)
|
2126
|
+
end
|
2127
|
+
end
|
2128
|
+
end
|
2129
|
+
end
|
1106
2130
|
end
|
1107
2131
|
|
1108
2132
|
context '#increment_smb_message_id' do
|
@@ -1156,7 +2180,10 @@ RSpec.describe RubySMB::Client do
|
|
1156
2180
|
|
1157
2181
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1158
2182
|
response.smb_header.nt_status = 0xc0000015
|
1159
|
-
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2183
|
+
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2184
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2185
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2186
|
+
)
|
1160
2187
|
end
|
1161
2188
|
|
1162
2189
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
@@ -1177,11 +2204,14 @@ RSpec.describe RubySMB::Client do
|
|
1177
2204
|
}
|
1178
2205
|
|
1179
2206
|
describe '#smb2_tree_connect' do
|
1180
|
-
it 'builds and sends
|
2207
|
+
it 'builds and sends the expected TreeconnectRequest for the supplied share' do
|
1181
2208
|
allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
2209
|
+
expect(smb2_client).to receive(:send_recv) do |req|
|
2210
|
+
expect(req).to eq(request)
|
2211
|
+
expect(req.smb2_header.tree_id).to eq(65_535)
|
2212
|
+
expect(req.path).to eq(path.encode('UTF-16LE'))
|
2213
|
+
response.to_binary_s
|
2214
|
+
end
|
1185
2215
|
smb2_client.smb2_tree_connect(path)
|
1186
2216
|
end
|
1187
2217
|
|
@@ -1200,11 +2230,20 @@ RSpec.describe RubySMB::Client do
|
|
1200
2230
|
|
1201
2231
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1202
2232
|
response.smb2_header.nt_status = 0xc0000015
|
1203
|
-
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2233
|
+
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2234
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2235
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2236
|
+
)
|
1204
2237
|
end
|
1205
2238
|
|
1206
2239
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
1207
|
-
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
|
2240
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
|
2241
|
+
smb2_client.smb2_tree_from_response(path, response)
|
2242
|
+
end
|
2243
|
+
|
2244
|
+
it 'creates a new with encryption set if the response requires it' do
|
2245
|
+
response.share_flags.encrypt = 1
|
2246
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
|
1208
2247
|
smb2_client.smb2_tree_from_response(path, response)
|
1209
2248
|
end
|
1210
2249
|
end
|
@@ -1301,7 +2340,7 @@ RSpec.describe RubySMB::Client do
|
|
1301
2340
|
end
|
1302
2341
|
|
1303
2342
|
it 'raise an InvalidPacket exception when the response is not valid' do
|
1304
|
-
echo_response.smb_header.command = RubySMB::SMB1::Commands::
|
2343
|
+
echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
1305
2344
|
allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
1306
2345
|
expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
1307
2346
|
end
|
@@ -1325,5 +2364,346 @@ RSpec.describe RubySMB::Client do
|
|
1325
2364
|
end
|
1326
2365
|
end
|
1327
2366
|
|
2367
|
+
context 'Winreg' do
|
2368
|
+
describe '#connect_to_winreg' do
|
2369
|
+
let(:host) { '1.2.3.4' }
|
2370
|
+
let(:share) { "\\\\#{host}\\IPC$" }
|
2371
|
+
let(:ipc_tree) { double('IPC$ tree') }
|
2372
|
+
let(:named_pipe) { double('Named pipe') }
|
2373
|
+
before :example do
|
2374
|
+
allow(ipc_tree).to receive_messages(
|
2375
|
+
:share => share,
|
2376
|
+
:open_file => named_pipe
|
2377
|
+
)
|
2378
|
+
allow(client).to receive(:tree_connect).and_return(ipc_tree)
|
2379
|
+
end
|
2380
|
+
|
2381
|
+
context 'when the client is already connected to the IPC$ share' do
|
2382
|
+
before :example do
|
2383
|
+
client.tree_connects << ipc_tree
|
2384
|
+
allow(ipc_tree).to receive(:share).and_return(share)
|
2385
|
+
end
|
2386
|
+
|
2387
|
+
it 'does not connect to the already connected tree' do
|
2388
|
+
client.connect_to_winreg(host)
|
2389
|
+
expect(client).to_not have_received(:tree_connect)
|
2390
|
+
end
|
2391
|
+
end
|
2392
|
+
|
2393
|
+
it 'calls #tree_connect' do
|
2394
|
+
client.connect_to_winreg(host)
|
2395
|
+
expect(client).to have_received(:tree_connect).with(share)
|
2396
|
+
end
|
2397
|
+
|
2398
|
+
it 'open \'winreg\' file on the IPC$ Tree' do
|
2399
|
+
client.connect_to_winreg(host)
|
2400
|
+
expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
|
2401
|
+
end
|
2402
|
+
|
2403
|
+
it 'returns the expected opened named pipe' do
|
2404
|
+
expect(client.connect_to_winreg(host)).to eq(named_pipe)
|
2405
|
+
end
|
2406
|
+
|
2407
|
+
context 'when a block is given' do
|
2408
|
+
before :example do
|
2409
|
+
allow(named_pipe).to receive(:close)
|
2410
|
+
end
|
2411
|
+
|
2412
|
+
it 'yields the expected named_pipe' do
|
2413
|
+
client.connect_to_winreg(host) do |np|
|
2414
|
+
expect(np).to eq(named_pipe)
|
2415
|
+
end
|
2416
|
+
end
|
2417
|
+
|
2418
|
+
it 'closes the named pipe' do
|
2419
|
+
client.connect_to_winreg(host) { |np| }
|
2420
|
+
expect(named_pipe).to have_received(:close)
|
2421
|
+
end
|
2422
|
+
|
2423
|
+
it 'returns the block return value' do
|
2424
|
+
result = double('Result')
|
2425
|
+
expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
|
2426
|
+
end
|
2427
|
+
end
|
2428
|
+
end
|
2429
|
+
|
2430
|
+
describe '#has_registry_key?' do
|
2431
|
+
let(:host) { '1.2.3.4' }
|
2432
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2433
|
+
let(:named_pipe) { double('Named pipe') }
|
2434
|
+
let(:result) { double('Result') }
|
2435
|
+
before :example do
|
2436
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2437
|
+
allow(named_pipe).to receive(:has_registry_key?).and_return(result)
|
2438
|
+
end
|
2439
|
+
|
2440
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2441
|
+
client.has_registry_key?(host, key)
|
2442
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2443
|
+
end
|
2444
|
+
|
2445
|
+
it 'calls Pipe #has_registry_key?' do
|
2446
|
+
client.has_registry_key?(host, key)
|
2447
|
+
expect(named_pipe).to have_received(:has_registry_key?).with(key)
|
2448
|
+
end
|
2449
|
+
end
|
2450
|
+
|
2451
|
+
describe '#read_registry_key_value' do
|
2452
|
+
let(:host) { '1.2.3.4' }
|
2453
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2454
|
+
let(:value_name) { 'Value' }
|
2455
|
+
let(:named_pipe) { double('Named pipe') }
|
2456
|
+
let(:result) { double('Result') }
|
2457
|
+
before :example do
|
2458
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2459
|
+
allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
|
2460
|
+
end
|
2461
|
+
|
2462
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2463
|
+
client.read_registry_key_value(host, key, value_name)
|
2464
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2465
|
+
end
|
2466
|
+
|
2467
|
+
it 'calls Pipe #read_registry_key_value' do
|
2468
|
+
client.read_registry_key_value(host, key, value_name)
|
2469
|
+
expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
|
2470
|
+
end
|
2471
|
+
end
|
2472
|
+
|
2473
|
+
describe '#enum_registry_key' do
|
2474
|
+
let(:host) { '1.2.3.4' }
|
2475
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2476
|
+
let(:named_pipe) { double('Named pipe') }
|
2477
|
+
let(:result) { double('Result') }
|
2478
|
+
before :example do
|
2479
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2480
|
+
allow(named_pipe).to receive(:enum_registry_key).and_return(result)
|
2481
|
+
end
|
2482
|
+
|
2483
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2484
|
+
client.enum_registry_key(host, key)
|
2485
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2486
|
+
end
|
2487
|
+
|
2488
|
+
it 'calls Pipe #enum_registry_key' do
|
2489
|
+
client.enum_registry_key(host, key)
|
2490
|
+
expect(named_pipe).to have_received(:enum_registry_key).with(key)
|
2491
|
+
end
|
2492
|
+
end
|
2493
|
+
|
2494
|
+
describe '#enum_registry_values' do
|
2495
|
+
let(:host) { '1.2.3.4' }
|
2496
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2497
|
+
let(:named_pipe) { double('Named pipe') }
|
2498
|
+
let(:result) { double('Result') }
|
2499
|
+
before :example do
|
2500
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2501
|
+
allow(named_pipe).to receive(:enum_registry_values).and_return(result)
|
2502
|
+
end
|
2503
|
+
|
2504
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2505
|
+
client.enum_registry_values(host, key)
|
2506
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2507
|
+
end
|
2508
|
+
|
2509
|
+
it 'calls Pipe #enum_registry_values' do
|
2510
|
+
client.enum_registry_values(host, key)
|
2511
|
+
expect(named_pipe).to have_received(:enum_registry_values).with(key)
|
2512
|
+
end
|
2513
|
+
end
|
2514
|
+
end
|
2515
|
+
|
2516
|
+
describe '#update_preauth_hash' do
|
2517
|
+
it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
|
2518
|
+
expect { client.update_preauth_hash('Test') }.to raise_error(
|
2519
|
+
RubySMB::Error::EncryptionError,
|
2520
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
2521
|
+
)
|
2522
|
+
end
|
2523
|
+
|
2524
|
+
it 'computes the hash value' do
|
2525
|
+
packet = RubySMB::SMB2::Packet::EchoRequest.new
|
2526
|
+
data = 'Previous hash'
|
2527
|
+
algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
|
2528
|
+
RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
2529
|
+
]
|
2530
|
+
client.preauth_integrity_hash_algorithm = algo
|
2531
|
+
client.preauth_integrity_hash_value = data
|
2532
|
+
hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
|
2533
|
+
client.update_preauth_hash(packet)
|
2534
|
+
expect(client.preauth_integrity_hash_value).to eq(hash)
|
2535
|
+
end
|
2536
|
+
end
|
2537
|
+
|
2538
|
+
context 'Encryption' do
|
2539
|
+
describe '#smb3_encrypt' do
|
2540
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2541
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2542
|
+
let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
|
2543
|
+
|
2544
|
+
before :example do
|
2545
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
|
2546
|
+
allow(transform_packet).to receive(:encrypt)
|
2547
|
+
client.session_key = session_key
|
2548
|
+
end
|
2549
|
+
|
2550
|
+
it 'does not generate a new client encryption key if it already exists' do
|
2551
|
+
client.client_encryption_key = 'key'
|
2552
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2553
|
+
expect(client.client_encryption_key).to eq('key')
|
2554
|
+
client.smb3_encrypt(data)
|
2555
|
+
end
|
2556
|
+
|
2557
|
+
['0x0300', '0x0302'].each do |dialect|
|
2558
|
+
context "with #{dialect} dialect" do
|
2559
|
+
before :example do
|
2560
|
+
client.dialect = dialect
|
2561
|
+
end
|
2562
|
+
|
2563
|
+
it 'generates the client encryption key with the expected parameters' do
|
2564
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2565
|
+
session_key,
|
2566
|
+
"SMB2AESCCM\x00",
|
2567
|
+
"ServerIn \x00"
|
2568
|
+
).and_call_original
|
2569
|
+
client.smb3_encrypt(data)
|
2570
|
+
end
|
2571
|
+
end
|
2572
|
+
end
|
2573
|
+
|
2574
|
+
context 'with 0x0311 dialect' do
|
2575
|
+
it 'generates the client encryption key with the expected parameters' do
|
2576
|
+
client.preauth_integrity_hash_value = ''
|
2577
|
+
client.dialect = '0x0311'
|
2578
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2579
|
+
session_key,
|
2580
|
+
"SMBC2SCipherKey\x00",
|
2581
|
+
''
|
2582
|
+
).and_call_original
|
2583
|
+
client.smb3_encrypt(data)
|
2584
|
+
end
|
2585
|
+
end
|
2586
|
+
|
2587
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2588
|
+
client.dialect = '0x0202'
|
2589
|
+
expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
|
2590
|
+
end
|
2591
|
+
|
2592
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2593
|
+
client.dialect = '0x0300'
|
2594
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2595
|
+
client.session_id = 123
|
2596
|
+
client.smb3_encrypt(data)
|
2597
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
|
2598
|
+
expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
|
2599
|
+
end
|
2600
|
+
|
2601
|
+
it 'generates the expected client encryption key with 0x0302 dialect' do
|
2602
|
+
client.dialect = '0x0302'
|
2603
|
+
expected_enc_key =
|
2604
|
+
"\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
|
2605
|
+
client.smb3_encrypt(data)
|
2606
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2607
|
+
end
|
2608
|
+
|
2609
|
+
it 'generates the expected client encryption key with 0x0311 dialect' do
|
2610
|
+
client.dialect = '0x0311'
|
2611
|
+
client.session_key =
|
2612
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2613
|
+
client.preauth_integrity_hash_value =
|
2614
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2615
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2616
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2617
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2618
|
+
expected_enc_key =
|
2619
|
+
"\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
|
2620
|
+
client.smb3_encrypt(data)
|
2621
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2622
|
+
end
|
2623
|
+
end
|
2624
|
+
|
2625
|
+
describe '#smb3_decrypt' do
|
2626
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2627
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2628
|
+
|
2629
|
+
before :example do
|
2630
|
+
allow(transform_packet).to receive(:decrypt)
|
2631
|
+
client.session_key = session_key
|
2632
|
+
end
|
2633
|
+
|
2634
|
+
it 'does not generate a new server encryption key if it already exists' do
|
2635
|
+
client.server_encryption_key = 'key'
|
2636
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2637
|
+
expect(client.server_encryption_key).to eq('key')
|
2638
|
+
client.smb3_decrypt(transform_packet)
|
2639
|
+
end
|
2640
|
+
|
2641
|
+
['0x0300', '0x0302'].each do |dialect|
|
2642
|
+
context "with #{dialect} dialect" do
|
2643
|
+
before :example do
|
2644
|
+
client.dialect = dialect
|
2645
|
+
end
|
2646
|
+
|
2647
|
+
it 'generates the client encryption key with the expected parameters' do
|
2648
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2649
|
+
session_key,
|
2650
|
+
"SMB2AESCCM\x00",
|
2651
|
+
"ServerOut\x00"
|
2652
|
+
).and_call_original
|
2653
|
+
client.smb3_decrypt(transform_packet)
|
2654
|
+
end
|
2655
|
+
end
|
2656
|
+
end
|
2657
|
+
|
2658
|
+
context 'with 0x0311 dialect' do
|
2659
|
+
it 'generates the client encryption key with the expected parameters' do
|
2660
|
+
client.preauth_integrity_hash_value = ''
|
2661
|
+
client.dialect = '0x0311'
|
2662
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2663
|
+
session_key,
|
2664
|
+
"SMBS2CCipherKey\x00",
|
2665
|
+
''
|
2666
|
+
).and_call_original
|
2667
|
+
client.smb3_decrypt(transform_packet)
|
2668
|
+
end
|
2669
|
+
end
|
2670
|
+
|
2671
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2672
|
+
client.dialect = '0x0202'
|
2673
|
+
expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
|
2674
|
+
end
|
2675
|
+
|
2676
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2677
|
+
client.dialect = '0x0300'
|
2678
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2679
|
+
client.session_id = 123
|
2680
|
+
client.smb3_decrypt(transform_packet)
|
2681
|
+
expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
|
2682
|
+
end
|
2683
|
+
|
2684
|
+
it 'generates the expected server encryption key with 0x0302 dialect' do
|
2685
|
+
client.dialect = '0x0302'
|
2686
|
+
expected_enc_key =
|
2687
|
+
"\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
|
2688
|
+
client.smb3_decrypt(transform_packet)
|
2689
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2690
|
+
end
|
2691
|
+
|
2692
|
+
it 'generates the expected server encryption key with 0x0311 dialect' do
|
2693
|
+
client.dialect = '0x0311'
|
2694
|
+
client.session_key =
|
2695
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2696
|
+
client.preauth_integrity_hash_value =
|
2697
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2698
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2699
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2700
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2701
|
+
expected_enc_key =
|
2702
|
+
"\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
|
2703
|
+
client.smb3_decrypt(transform_packet)
|
2704
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2705
|
+
end
|
2706
|
+
end
|
2707
|
+
end
|
1328
2708
|
end
|
1329
2709
|
|