ruby_smb 0.0.8
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.simplecov +42 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +119 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +18 -0
- data/README.md +64 -0
- data/Rakefile +22 -0
- data/examples/authenticate.rb +30 -0
- data/examples/negotiate.rb +25 -0
- data/lib/ruby_smb/client/authentication.rb +236 -0
- data/lib/ruby_smb/client/negotiation.rb +126 -0
- data/lib/ruby_smb/client/signing.rb +48 -0
- data/lib/ruby_smb/client.rb +164 -0
- data/lib/ruby_smb/dispatcher/base.rb +18 -0
- data/lib/ruby_smb/dispatcher/socket.rb +53 -0
- data/lib/ruby_smb/dispatcher.rb +4 -0
- data/lib/ruby_smb/error.rb +17 -0
- data/lib/ruby_smb/field/file_time.rb +62 -0
- data/lib/ruby_smb/field/nt_status.rb +16 -0
- data/lib/ruby_smb/field/stringz16.rb +55 -0
- data/lib/ruby_smb/field.rb +7 -0
- data/lib/ruby_smb/generic_packet.rb +179 -0
- data/lib/ruby_smb/gss.rb +109 -0
- data/lib/ruby_smb/smb1/andx_block.rb +13 -0
- data/lib/ruby_smb/smb1/bit_field/capabilities.rb +39 -0
- data/lib/ruby_smb/smb1/bit_field/header_flags.rb +19 -0
- data/lib/ruby_smb/smb1/bit_field/header_flags2.rb +27 -0
- data/lib/ruby_smb/smb1/bit_field/security_mode.rb +16 -0
- data/lib/ruby_smb/smb1/bit_field.rb +10 -0
- data/lib/ruby_smb/smb1/commands.rb +9 -0
- data/lib/ruby_smb/smb1/data_block.rb +42 -0
- data/lib/ruby_smb/smb1/dialect.rb +11 -0
- data/lib/ruby_smb/smb1/packet/error_packet.rb +14 -0
- data/lib/ruby_smb/smb1/packet/negotiate_request.rb +52 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +46 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +47 -0
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +71 -0
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +48 -0
- data/lib/ruby_smb/smb1/packet.rb +12 -0
- data/lib/ruby_smb/smb1/parameter_block.rb +42 -0
- data/lib/ruby_smb/smb1/smb_header.rb +21 -0
- data/lib/ruby_smb/smb1.rb +16 -0
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +17 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_capabailities.rb +23 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_header_flags.rb +23 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_security_mode.rb +15 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_security_mode_single.rb +14 -0
- data/lib/ruby_smb/smb2/bit_field.rb +11 -0
- data/lib/ruby_smb/smb2/commands.rb +25 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +50 -0
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +33 -0
- data/lib/ruby_smb/smb2/packet/session_setup_request.rb +53 -0
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +38 -0
- data/lib/ruby_smb/smb2/packet.rb +10 -0
- data/lib/ruby_smb/smb2/smb2_header.rb +22 -0
- data/lib/ruby_smb/smb2.rb +12 -0
- data/lib/ruby_smb/version.rb +3 -0
- data/lib/ruby_smb.rb +22 -0
- data/ruby_smb.gemspec +38 -0
- data/spec/lib/ruby_smb/client_spec.rb +638 -0
- data/spec/lib/ruby_smb/dispatcher/dispatcher_base_spec.rb +22 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +60 -0
- data/spec/lib/ruby_smb/field/file_time_spec.rb +59 -0
- data/spec/lib/ruby_smb/field/nt_status_spec.rb +19 -0
- data/spec/lib/ruby_smb/field/stringz16_spec.rb +50 -0
- data/spec/lib/ruby_smb/generic_packet_spec.rb +58 -0
- data/spec/lib/ruby_smb/smb1/andx_block_spec.rb +41 -0
- data/spec/lib/ruby_smb/smb1/bit_field/capabilities_spec.rb +245 -0
- data/spec/lib/ruby_smb/smb1/bit_field/header_flags2_spec.rb +146 -0
- data/spec/lib/ruby_smb/smb1/bit_field/header_flags_spec.rb +102 -0
- data/spec/lib/ruby_smb/smb1/bit_field/security_mode_spec.rb +44 -0
- data/spec/lib/ruby_smb/smb1/data_block_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/dialect_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +39 -0
- data/spec/lib/ruby_smb/smb1/packet/negotiate_request_spec.rb +77 -0
- data/spec/lib/ruby_smb/smb1/packet/negotiate_response_extended_spec.rb +149 -0
- data/spec/lib/ruby_smb/smb1/packet/negotiate_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +100 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +72 -0
- data/spec/lib/ruby_smb/smb1/parameter_block_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/smb_header_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb2/bit_field/header_flags_spec.rb +81 -0
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +28 -0
- data/spec/lib/ruby_smb/smb2/bit_field/smb2_capabilities_spec.rb +72 -0
- data/spec/lib/ruby_smb/smb2/bit_field/smb_secruity_mode_spec.rb +22 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +122 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +147 -0
- data/spec/lib/ruby_smb/smb2/packet/session_setup_request_spec.rb +79 -0
- data/spec/lib/ruby_smb/smb2/packet/session_setup_response_spec.rb +54 -0
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +127 -0
- data/spec/lib/ruby_smb_spec.rb +2 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/support/mock_socket_dispatcher.rb +8 -0
- data/spec/support/shared/examples/bit_field_single_flag.rb +14 -0
- data.tar.gz.sig +0 -0
- metadata +384 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RubySMB::Client do
|
|
4
|
+
|
|
5
|
+
let(:dispatcher) { RubySMB::Dispatcher::Socket.new(nil) }
|
|
6
|
+
let(:username) { 'msfadmin' }
|
|
7
|
+
let(:password) { 'msfadmin' }
|
|
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) }
|
|
11
|
+
|
|
12
|
+
describe '#initialize' do
|
|
13
|
+
it 'should raise an ArgumentError without a valid dispatcher' do
|
|
14
|
+
expect{ described_class.new(nil) }.to raise_error(ArgumentError)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'defaults to true for SMB1 support' do
|
|
18
|
+
expect(client.smb1).to be true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'defaults to true for SMB2 support' do
|
|
22
|
+
expect(client.smb1).to be true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'accepts an argument to disable smb1 support' do
|
|
26
|
+
smb_client = described_class.new(dispatcher, smb1:false, username: username, password: password)
|
|
27
|
+
expect(smb_client.smb1).to be false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'accepts an argument to disable smb2 support' do
|
|
31
|
+
expect(smb1_client.smb2).to be false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'raises an exception if both SMB1 and SMB2 are disabled' do
|
|
35
|
+
expect{described_class.new(dispatcher, smb1:false, smb2:false, username: username, password: password)}.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'sets the username attribute' do
|
|
39
|
+
expect(client.username).to eq username
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'sets the password attribute' do
|
|
43
|
+
expect(client.password).to eq password
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'crates an NTLM client' do
|
|
47
|
+
expect(client.ntlm_client).to be_a Net::NTLM::Client
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe '#send_recv' do
|
|
52
|
+
let(:smb1_request) { RubySMB::SMB1::Packet::SessionSetupRequest.new }
|
|
53
|
+
let(:smb2_request) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
|
54
|
+
|
|
55
|
+
before(:each) do
|
|
56
|
+
expect(dispatcher).to receive(:send_packet).and_return(nil)
|
|
57
|
+
expect(dispatcher).to receive(:recv_packet).and_return("A")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'checks the packet version' do
|
|
61
|
+
expect(smb1_request).to receive(:packet_smb_version).and_call_original
|
|
62
|
+
client.send_recv(smb1_request)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'calls #smb1_sign if it is an SMB1 packet' do
|
|
66
|
+
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
|
67
|
+
client.send_recv(smb1_request)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'calls #smb2_sign if it is an SMB2 packet' do
|
|
71
|
+
expect(client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
|
72
|
+
client.send_recv(smb2_request)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#login' do
|
|
78
|
+
before(:each) do
|
|
79
|
+
allow(client).to receive(:negotiate)
|
|
80
|
+
allow(client).to receive(:authenticate)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'defaults username to what was in the initializer' do
|
|
84
|
+
expect{ client.login }.to_not change(client, :username)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'overrides username if it is passed as a parameter' do
|
|
88
|
+
expect{ client.login(username:'test') }.to change(client, :username).to('test')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'defaults password to what was in the initializer' do
|
|
92
|
+
expect{ client.login }.to_not change(client, :password)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'overrides password if it is passed as a parameter' do
|
|
96
|
+
expect{ client.login(password:'test') }.to change(client, :password).to('test')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'defaults domain to what was in the initializer' do
|
|
100
|
+
expect{ client.login }.to_not change(client, :domain)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'overrides domain if it is passed as a parameter' do
|
|
104
|
+
expect{ client.login(domain:'test') }.to change(client, :domain).to('test')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'defaults local_workstation to what was in the initializer' do
|
|
108
|
+
expect{ client.login }.to_not change(client, :local_workstation)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'overrides local_workstation if it is passed as a parameter' do
|
|
112
|
+
expect{ client.login(local_workstation:'test') }.to change(client, :local_workstation).to('test')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'initialises a new NTLM Client' do
|
|
116
|
+
expect{ client.login }.to change(client, :ntlm_client)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'calls negotiate after the setup' do
|
|
120
|
+
expect(client).to receive(:negotiate)
|
|
121
|
+
client.login
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'calls authenticate after negotiate' do
|
|
125
|
+
expect(client).to receive(:authenticate)
|
|
126
|
+
client.login
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context 'Protocol Negotiation' do
|
|
132
|
+
let(:random_junk) { "fgrgrwgawrtw4t4tg4gahgn" }
|
|
133
|
+
let(:smb1_capabilities) {
|
|
134
|
+
{:level_2_oplocks=>1,
|
|
135
|
+
:nt_status=>1,
|
|
136
|
+
:rpc_remote_apis=>1,
|
|
137
|
+
:nt_smbs=>1,
|
|
138
|
+
:large_files=>1,
|
|
139
|
+
:unicode=>1,
|
|
140
|
+
:mpx_mode=>0,
|
|
141
|
+
:raw_mode=>0,
|
|
142
|
+
:large_writex=>1,
|
|
143
|
+
:large_readx=>1,
|
|
144
|
+
:info_level_passthru=>1,
|
|
145
|
+
:dfs=>0,
|
|
146
|
+
:reserved1=>0,
|
|
147
|
+
:bulk_transfer=>0,
|
|
148
|
+
:nt_find=>1,
|
|
149
|
+
:lock_and_read=>1,
|
|
150
|
+
:unix=>0,
|
|
151
|
+
:reserved2=>0,
|
|
152
|
+
:lwio=>1,
|
|
153
|
+
:extended_security=>1,
|
|
154
|
+
:reserved3=>0,
|
|
155
|
+
:dynamic_reauth=>0,
|
|
156
|
+
:reserved4=>0,
|
|
157
|
+
:compressed_data=>0,
|
|
158
|
+
:reserved5=>0}
|
|
159
|
+
}
|
|
160
|
+
let(:smb1_extended_response) {
|
|
161
|
+
packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.new
|
|
162
|
+
packet.parameter_block.capabilities = smb1_capabilities
|
|
163
|
+
packet
|
|
164
|
+
}
|
|
165
|
+
let(:smb1_extended_response_raw) {
|
|
166
|
+
smb1_extended_response.to_binary_s
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
|
170
|
+
|
|
171
|
+
describe '#smb1_negotiate_request' do
|
|
172
|
+
it 'returns an SMB1 Negotiate Request packet' do
|
|
173
|
+
expect(client.smb1_negotiate_request).to be_a(RubySMB::SMB1::Packet::NegotiateRequest)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'sets the default SMB1 Dialect' do
|
|
177
|
+
expect(client.smb1_negotiate_request.dialects).to include({:buffer_format=>2, :dialect_string=> RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT})
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
|
181
|
+
expect(client.smb1_negotiate_request.dialects).to include({:buffer_format=>2, :dialect_string=> RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT})
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
|
185
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include({:buffer_format=>2, :dialect_string=> RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT})
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
|
189
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include({:buffer_format=>2, :dialect_string=> RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT})
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
describe '#smb2_negotiate_request' do
|
|
194
|
+
it 'return an SMB2 Negotiate Request packet' do
|
|
195
|
+
expect(client.smb2_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'sets the default SMB2 Dialect' do
|
|
199
|
+
expect(client.smb2_negotiate_request.dialects).to include(RubySMB::Client::SMB2_DIALECT_DEFAULT)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'sets the Message ID to 0' do
|
|
203
|
+
expect(client.smb2_negotiate_request.smb2_header.message_id).to eq 0
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
describe '#negotiate_request' do
|
|
208
|
+
it 'calls #smb1_negotiate_request if SMB1 is enabled' do
|
|
209
|
+
expect(smb1_client).to receive(:smb1_negotiate_request)
|
|
210
|
+
expect(smb1_client).to receive(:send_recv)
|
|
211
|
+
smb1_client.negotiate_request
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'calls #smb1_negotiate_request if both protocols are enabled' do
|
|
215
|
+
expect(client).to receive(:smb1_negotiate_request)
|
|
216
|
+
expect(client).to receive(:send_recv)
|
|
217
|
+
client.negotiate_request
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'calls #smb2_negotiate_request if SMB2 is enabled' do
|
|
221
|
+
expect(smb2_client).to receive(:smb2_negotiate_request)
|
|
222
|
+
expect(smb2_client).to receive(:send_recv)
|
|
223
|
+
smb2_client.negotiate_request
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'returns the raw response string from the server' do
|
|
227
|
+
expect(client).to receive(:send_recv).and_return('A')
|
|
228
|
+
expect(client.negotiate_request).to eq "A"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
describe '#negotiate_response' do
|
|
233
|
+
context 'with only SMB1' do
|
|
234
|
+
it 'returns a properly formed packet' do
|
|
235
|
+
expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'raises an exception if the Response is invalid' do
|
|
239
|
+
expect{ smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
|
243
|
+
bogus_response = smb1_extended_response
|
|
244
|
+
bogus_response.smb_header.command = 0xff
|
|
245
|
+
expect{ smb1_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'considers the response invalid if Extended Security is not enabled' do
|
|
249
|
+
bogus_response = smb1_extended_response
|
|
250
|
+
bogus_response.parameter_block.capabilities.extended_security = 0
|
|
251
|
+
expect{ smb1_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
context 'with only SMB2' do
|
|
256
|
+
it 'returns a properly formed packet' do
|
|
257
|
+
expect( smb2_client.negotiate_response(smb2_response.to_binary_s) ).to eq smb2_response
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'raises an exception if the Response is invalid' do
|
|
261
|
+
expect{ smb2_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
context 'with SMB1 and SMB2 enabled' do
|
|
266
|
+
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
|
267
|
+
expect( client.negotiate_response(smb1_extended_response_raw) ).to eq smb1_extended_response
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
|
271
|
+
expect( client.negotiate_response(smb2_response.to_binary_s) ).to eq smb2_response
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
describe '#parse_negotiate_response' do
|
|
277
|
+
context 'when SMB1 was Negotiated' do
|
|
278
|
+
it 'turns off SMB2 support' do
|
|
279
|
+
client.parse_negotiate_response(smb1_extended_response)
|
|
280
|
+
expect( client.smb2 ).to be false
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'sets whether or not signing is required' do
|
|
284
|
+
smb1_extended_response.parameter_block.security_mode.security_signatures_required = 1
|
|
285
|
+
client.parse_negotiate_response(smb1_extended_response)
|
|
286
|
+
expect(client.signing_required).to be true
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
context 'when SMB2 was negotiated' do
|
|
291
|
+
it 'turns off SMB1 support' do
|
|
292
|
+
client.parse_negotiate_response(smb2_response)
|
|
293
|
+
expect( client.smb1 ).to be false
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'sets whether or not signing is required' do
|
|
297
|
+
smb2_response.security_mode.signing_required = 1
|
|
298
|
+
client.parse_negotiate_response(smb2_response)
|
|
299
|
+
expect(client.signing_required).to be true
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
describe '#negotiate' do
|
|
305
|
+
it 'calls the backing methods' do
|
|
306
|
+
expect(client).to receive(:negotiate_request)
|
|
307
|
+
expect(client).to receive(:negotiate_response)
|
|
308
|
+
expect(client).to receive(:parse_negotiate_response)
|
|
309
|
+
client.negotiate
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
context 'Authentication' do
|
|
315
|
+
let(:type2_string) {
|
|
316
|
+
"TlRMTVNTUAACAAAAHgAeADgAAAA1goriwmZ8HEHtFHAAAAAAAAAAAJgAmABW\nAAAABgGxHQAAAA" +
|
|
317
|
+
"9XAEkATgAtAFMATgBKAEQARwAwAFUAQQA5ADAARgACAB4A\nVwBJAE4ALQBTAE4ASgBEAEcAMABV" +
|
|
318
|
+
"AEEAOQAwAEYAAQAeAFcASQBOAC0AUwBO\nAEoARABHADAAVQBBADkAMABGAAQAHgBXAEkATgAtAF" +
|
|
319
|
+
"MATgBKAEQARwAwAFUA\nQQA5ADAARgADAB4AVwBJAE4ALQBTAE4ASgBEAEcAMABVAEEAOQAwAEYABw" +
|
|
320
|
+
"AI\nADxThZ4nnNIBAAAAAA==\n"
|
|
321
|
+
}
|
|
322
|
+
context 'for SMB1' do
|
|
323
|
+
let(:ntlm_client) { smb1_client.ntlm_client }
|
|
324
|
+
let(:type1_message) { ntlm_client.init_context }
|
|
325
|
+
let(:negotiate_packet) { RubySMB::SMB1::Packet::SessionSetupRequest.new }
|
|
326
|
+
let(:type3_message) { ntlm_client.init_context(type2_string) }
|
|
327
|
+
let(:user_id) { 2041 }
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
describe '#smb1_ntlmssp_auth_packet' do
|
|
331
|
+
it 'creates a new SessionSetupRequest packet' do
|
|
332
|
+
expect(RubySMB::SMB1::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
333
|
+
smb1_client.smb1_ntlmssp_auth_packet(type2_string, user_id)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it 'builds the security blob with an NTLM Type 3 Message' do
|
|
337
|
+
expect(RubySMB::SMB1::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
338
|
+
expect(ntlm_client).to receive(:init_context).with(type2_string).and_return(type3_message)
|
|
339
|
+
expect(negotiate_packet).to receive(:set_type3_blob).with(type3_message.serialize)
|
|
340
|
+
smb1_client.smb1_ntlmssp_auth_packet(type2_string, user_id)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'enables extended security on the packet' do
|
|
344
|
+
expect(smb1_client.smb1_ntlmssp_auth_packet(type2_string, user_id).smb_header.flags2.extended_security).to eq 1
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
describe '#smb1_ntlmssp_negotiate_packet' do
|
|
349
|
+
it 'creates a new SessionSetupRequest packet' do
|
|
350
|
+
expect(RubySMB::SMB1::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
351
|
+
smb1_client.smb1_ntlmssp_negotiate_packet
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it 'builds the security blob with an NTLM Type 1 Message' do
|
|
355
|
+
expect(RubySMB::SMB1::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
356
|
+
expect(ntlm_client).to receive(:init_context).and_return(type1_message)
|
|
357
|
+
expect(negotiate_packet).to receive(:set_type1_blob).with(type1_message.serialize)
|
|
358
|
+
smb1_client.smb1_ntlmssp_negotiate_packet
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it 'enables extended security on the packet' do
|
|
362
|
+
expect(smb1_client.smb1_ntlmssp_negotiate_packet.smb_header.flags2.extended_security).to eq 1
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
describe '#smb1_ntlmssp_authenticate' do
|
|
367
|
+
it 'sends the request packet and receives a response' do
|
|
368
|
+
expect(smb1_client).to receive(:smb1_ntlmssp_auth_packet).and_return(negotiate_packet)
|
|
369
|
+
expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
|
|
370
|
+
expect(dispatcher).to receive(:recv_packet)
|
|
371
|
+
smb1_client.smb1_ntlmssp_authenticate(type2_string, user_id)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
describe '#smb1_ntlmssp_negotiate' do
|
|
376
|
+
it 'sends the request packet and receives a response' do
|
|
377
|
+
expect(smb1_client).to receive(:smb1_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
|
378
|
+
expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
|
|
379
|
+
expect(dispatcher).to receive(:recv_packet)
|
|
380
|
+
smb1_client.smb1_ntlmssp_negotiate
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
describe '#smb1_ntlmssp_challenge_packet' do
|
|
385
|
+
let(:response) {
|
|
386
|
+
packet = RubySMB::SMB1::Packet::SessionSetupResponse.new
|
|
387
|
+
packet.smb_header.nt_status = 0xc0000016
|
|
388
|
+
packet
|
|
389
|
+
}
|
|
390
|
+
let(:wrong_command) {
|
|
391
|
+
packet = RubySMB::SMB1::Packet::SessionSetupResponse.new
|
|
392
|
+
packet.smb_header.nt_status = 0xc0000016
|
|
393
|
+
packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_NEGOTIATE
|
|
394
|
+
packet
|
|
395
|
+
}
|
|
396
|
+
it 'returns the packet object' do
|
|
397
|
+
expect(smb1_client.smb1_ntlmssp_challenge_packet(response.to_binary_s)).to eq response
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it 'raises an UnexpectedStatusCode if the status code is not correct' do
|
|
401
|
+
response.smb_header.nt_status = 0xc0000015
|
|
402
|
+
expect{ smb1_client.smb1_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error( RubySMB::Error::UnexpectedStatusCode)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it 'raises an InvalidPacket if the Command field is wrong' do
|
|
406
|
+
expect{ smb1_client.smb1_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
describe '#smb1_ntlmssp_final_packet' do
|
|
411
|
+
let(:response) {
|
|
412
|
+
packet = RubySMB::SMB1::Packet::SessionSetupResponse.new
|
|
413
|
+
packet.smb_header.nt_status = 0x00000000
|
|
414
|
+
packet
|
|
415
|
+
}
|
|
416
|
+
let(:wrong_command) {
|
|
417
|
+
packet = RubySMB::SMB1::Packet::SessionSetupResponse.new
|
|
418
|
+
packet.smb_header.nt_status = 0x00000000
|
|
419
|
+
packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_NEGOTIATE
|
|
420
|
+
packet
|
|
421
|
+
}
|
|
422
|
+
it 'returns the packet object' do
|
|
423
|
+
expect(smb1_client.smb1_ntlmssp_final_packet(response.to_binary_s)).to eq response
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
it 'raises an InvalidPacket if the Command field is wrong' do
|
|
427
|
+
expect{ smb1_client.smb1_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
describe '#smb1_type2_message' do
|
|
432
|
+
let(:fake_type2) { "NTLMSSP FOO" }
|
|
433
|
+
let(:response_packet) {
|
|
434
|
+
packet = RubySMB::SMB1::Packet::SessionSetupResponse.new
|
|
435
|
+
packet.set_type2_blob(fake_type2)
|
|
436
|
+
packet
|
|
437
|
+
}
|
|
438
|
+
it 'returns a base64 encoded copy of the Type 2 NTLM message' do
|
|
439
|
+
expect(smb1_client.smb1_type2_message(response_packet)).to eq [fake_type2].pack('m')
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
context 'for SMB2' do
|
|
445
|
+
let(:ntlm_client) { smb2_client.ntlm_client }
|
|
446
|
+
let(:type1_message) { ntlm_client.init_context }
|
|
447
|
+
let(:negotiate_packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
|
448
|
+
let(:type3_message) { ntlm_client.init_context(type2_string) }
|
|
449
|
+
let(:session_id) { 0x0000040000000005 }
|
|
450
|
+
|
|
451
|
+
describe '#smb2_ntlmssp_negotiate_packet' do
|
|
452
|
+
it 'creates a new SessionSetupRequest packet' do
|
|
453
|
+
expect(RubySMB::SMB2::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
454
|
+
smb2_client.smb2_ntlmssp_negotiate_packet
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it 'builds the security blob with an NTLM Type 1 Message' do
|
|
458
|
+
expect(RubySMB::SMB2::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
459
|
+
expect(ntlm_client).to receive(:init_context).and_return(type1_message)
|
|
460
|
+
expect(negotiate_packet).to receive(:set_type1_blob).with(type1_message.serialize)
|
|
461
|
+
smb2_client.smb2_ntlmssp_negotiate_packet
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
it 'sets the message ID in the packet header to 1' do
|
|
465
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.smb2_header.message_id).to eq 1
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
it 'increments client#smb2_message_id' do
|
|
469
|
+
expect{ smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
describe '#smb2_ntlmssp_negotiate' do
|
|
474
|
+
it 'sends the request packet and receives a response' do
|
|
475
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
|
476
|
+
expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
|
|
477
|
+
expect(dispatcher).to receive(:recv_packet)
|
|
478
|
+
smb2_client.smb2_ntlmssp_negotiate
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
describe '#smb2_ntlmssp_challenge_packet' do
|
|
483
|
+
let(:response) {
|
|
484
|
+
packet = RubySMB::SMB2::Packet::SessionSetupResponse.new
|
|
485
|
+
packet.smb2_header.nt_status = 0xc0000016
|
|
486
|
+
packet
|
|
487
|
+
}
|
|
488
|
+
let(:wrong_command) {
|
|
489
|
+
packet = RubySMB::SMB2::Packet::SessionSetupResponse.new
|
|
490
|
+
packet.smb2_header.nt_status = 0xc0000016
|
|
491
|
+
packet.smb2_header.command = RubySMB::SMB2::Commands::NEGOTIATE
|
|
492
|
+
packet
|
|
493
|
+
}
|
|
494
|
+
it 'returns the packet object' do
|
|
495
|
+
expect(smb2_client.smb2_ntlmssp_challenge_packet(response.to_binary_s)).to eq response
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
it 'raises an UnexpectedStatusCode if the status code is not correct' do
|
|
499
|
+
response.smb2_header.nt_status = 0xc0000015
|
|
500
|
+
expect{ smb2_client.smb2_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error( RubySMB::Error::UnexpectedStatusCode)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
it 'raises an InvalidPacket if the Command field is wrong' do
|
|
504
|
+
expect{ smb2_client.smb2_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
describe '#smb2_type2_message' do
|
|
509
|
+
let(:fake_type2) { "NTLMSSP FOO" }
|
|
510
|
+
let(:response_packet) {
|
|
511
|
+
packet = RubySMB::SMB2::Packet::SessionSetupResponse.new
|
|
512
|
+
packet.set_type2_blob(fake_type2)
|
|
513
|
+
packet
|
|
514
|
+
}
|
|
515
|
+
it 'returns a base64 encoded copy of the Type 2 NTLM message' do
|
|
516
|
+
expect(smb2_client.smb2_type2_message(response_packet)).to eq [fake_type2].pack('m')
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
describe '#smb1_ntlmssp_auth_packet' do
|
|
521
|
+
it 'creates a new SessionSetupRequest packet' do
|
|
522
|
+
expect(RubySMB::SMB2::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
523
|
+
smb2_client.smb2_ntlmssp_auth_packet(type2_string, session_id)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
it 'builds the security blob with an NTLM Type 3 Message' do
|
|
527
|
+
expect(RubySMB::SMB2::Packet::SessionSetupRequest).to receive(:new).and_return(negotiate_packet)
|
|
528
|
+
expect(ntlm_client).to receive(:init_context).with(type2_string).and_return(type3_message)
|
|
529
|
+
expect(negotiate_packet).to receive(:set_type3_blob).with(type3_message.serialize)
|
|
530
|
+
smb2_client.smb2_ntlmssp_auth_packet(type2_string, session_id)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
it 'sets the session ID on the request packet' do
|
|
534
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type2_string, session_id).smb2_header.session_id).to eq session_id
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
describe '#smb2_ntlmssp_authenticate' do
|
|
540
|
+
it 'sends the request packet and receives a response' do
|
|
541
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
|
542
|
+
expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
|
|
543
|
+
expect(dispatcher).to receive(:recv_packet)
|
|
544
|
+
smb2_client.smb2_ntlmssp_authenticate(type2_string, session_id)
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
describe '#smb2_ntlmssp_final_packet' do
|
|
549
|
+
let(:response) {
|
|
550
|
+
packet = RubySMB::SMB2::Packet::SessionSetupResponse.new
|
|
551
|
+
packet.smb2_header.nt_status = 0x00000000
|
|
552
|
+
packet
|
|
553
|
+
}
|
|
554
|
+
let(:wrong_command) {
|
|
555
|
+
packet = RubySMB::SMB2::Packet::SessionSetupResponse.new
|
|
556
|
+
packet.smb2_header.nt_status = 0x00000000
|
|
557
|
+
packet.smb2_header.command = RubySMB::SMB2::Commands::NEGOTIATE
|
|
558
|
+
packet
|
|
559
|
+
}
|
|
560
|
+
it 'returns the packet object' do
|
|
561
|
+
expect(smb2_client.smb2_ntlmssp_final_packet(response.to_binary_s)).to eq response
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
it 'raises an InvalidPacket if the Command field is wrong' do
|
|
565
|
+
expect{ smb2_client.smb2_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
context 'Signing' do
|
|
572
|
+
describe '#smb2_sign' do
|
|
573
|
+
let(:request1) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
|
574
|
+
let(:fake_hmac) { "\x31\x07\x78\x3e\x35\xd7\x0e\x89\x08\x43\x8a\x18\xcd\x78\x52\x39".force_encoding("ASCII-8BIT") }
|
|
575
|
+
|
|
576
|
+
context 'if signing is required and we have a session key' do
|
|
577
|
+
it 'generates the HMAC based on the packet and the NTLM session key and signs the packet with it' do
|
|
578
|
+
smb2_client.session_key = 'foo'
|
|
579
|
+
smb2_client.signing_required = true
|
|
580
|
+
expect(OpenSSL::HMAC).to receive(:digest).with(instance_of(OpenSSL::Digest::SHA256),smb2_client.session_key,request1.to_binary_s).and_return(fake_hmac)
|
|
581
|
+
expect(smb2_client.smb2_sign(request1).smb2_header.signature).to eq fake_hmac
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
context 'when signing is not required' do
|
|
586
|
+
it 'returns the packet exactly as it was given' do
|
|
587
|
+
smb2_client.session_key = 'foo'
|
|
588
|
+
smb2_client.signing_required = false
|
|
589
|
+
expect(smb2_client.smb2_sign(request1)).to eq request1
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
context 'when there is no session_key' do
|
|
594
|
+
it 'returns the packet exactly as it was given' do
|
|
595
|
+
smb2_client.session_key = ''
|
|
596
|
+
smb2_client.signing_required = true
|
|
597
|
+
expect(smb2_client.smb2_sign(request1)).to eq request1
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
describe '#smb1_sign' do
|
|
604
|
+
let(:request1) { RubySMB::SMB1::Packet::SessionSetupRequest.new }
|
|
605
|
+
let(:fake_sig) { "\x9f\x62\xcf\x08\xd9\xc2\x83\x21".force_encoding("ASCII-8BIT") }
|
|
606
|
+
|
|
607
|
+
context 'if signing is required and we have a session key' do
|
|
608
|
+
it 'generates the signature based on the packet, the sequence counter and the NTLM session key and signs the packet with it' do
|
|
609
|
+
smb1_client.session_key = 'foo'
|
|
610
|
+
smb1_client.signing_required = true
|
|
611
|
+
raw = request1.to_binary_s
|
|
612
|
+
adjusted_request = RubySMB::SMB1::Packet::SessionSetupRequest.read(raw)
|
|
613
|
+
adjusted_request.smb_header.security_features = smb1_client.sequence_counter
|
|
614
|
+
expect(OpenSSL::Digest::MD5).to receive(:digest).with(smb1_client.session_key + adjusted_request.to_binary_s).and_return(fake_sig)
|
|
615
|
+
expect(smb1_client.smb1_sign(request1).smb_header.security_features).to eq fake_sig
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
context 'when signing is not required' do
|
|
620
|
+
it 'returns the packet exactly as it was given' do
|
|
621
|
+
smb1_client.session_key = 'foo'
|
|
622
|
+
smb1_client.signing_required = false
|
|
623
|
+
expect(smb1_client.smb1_sign(request1)).to eq request1
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
context 'when there is no session_key' do
|
|
628
|
+
it 'returns the packet exactly as it was given' do
|
|
629
|
+
smb1_client.session_key = ''
|
|
630
|
+
smb1_client.signing_required = true
|
|
631
|
+
expect(smb1_client.smb1_sign(request1)).to eq request1
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RubySMB::Dispatcher::Base do
|
|
4
|
+
|
|
5
|
+
subject(:dispatcher) { described_class.new }
|
|
6
|
+
|
|
7
|
+
describe '#nbss' do
|
|
8
|
+
it 'returns the size of the packet to the packet in 4 bytes' do
|
|
9
|
+
packet = RubySMB::SMB1::Packet::NegotiateRequest.new
|
|
10
|
+
packet_size = packet.do_num_bytes
|
|
11
|
+
expect( dispatcher.nbss(packet) ).to eq "\x00\x00\x00\x23"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'raises NotImplementedError on #send_packet' do
|
|
16
|
+
expect{ dispatcher.send_packet("foo") }.to raise_error(NotImplementedError)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'raises NotImplementedError on #recv_packet' do
|
|
20
|
+
expect{ dispatcher.recv_packet }.to raise_error(NotImplementedError)
|
|
21
|
+
end
|
|
22
|
+
end
|