Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/msf/ui/console/module_argument_parsing_spec.rb
19715 views
1
require 'rspec'
2
3
RHOST_EXAMPLES = [
4
'192.168.172.1',
5
'192.168.172.1/32',
6
'file:foo.txt',
7
'example',
8
'localhost',
9
'example.com',
10
'http://example.com',
11
'https://example.com:443',
12
'https://example.com:443/foo/bar?baz=qux&a=b',
13
'cidr:/30:http://multiple_ips.example.com/foo',
14
'http://[::ffff:7f00:1]:8000/',
15
'smb://example.com/',
16
'smb://[email protected]/',
17
'smb://user:[email protected]',
18
'smb://:@example.com',
19
'smb://domain;user:[email protected]/'
20
].freeze
21
22
# Shared examples to ensure that all command parsing supports the same ways of
23
# supplying inline datastore values
24
RSpec.shared_examples_for 'a command which parses datastore values' do |opts|
25
context 'when the -o option flag is supplied' do
26
it 'shows the help menu when no value is supplied' do
27
expect(subject.send(opts[:method_name], ['-o'])).to be_nil
28
expect(subject).to have_received(opts[:expected_help_cmd])
29
end
30
31
it 'allows setting one value' do
32
expected_result = {
33
datastore_options: {
34
'RHOSTS' => '192.168.172.1'
35
}
36
}
37
expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1'])).to include(expected_result)
38
end
39
40
it 'allows setting namespaced datastore options' do
41
expected_result = {
42
datastore_options: {
43
'SMB::PROTOCOLVERSION' => '1,2'
44
}
45
}
46
expect(subject.send(opts[:method_name], ['SMB::ProtocolVersion=1,2'])).to include(expected_result)
47
end
48
49
it 'allows setting datastore options with underscores' do
50
expected_result = {
51
datastore_options: {
52
'USER_FILE' => './example.txt'
53
}
54
}
55
expect(subject.send(opts[:method_name], ['user_file=./example.txt'])).to include(expected_result)
56
end
57
58
it 'allows setting multiple options individually' do
59
expected_result = {
60
datastore_options: {
61
'RHOSTS' => '192.168.172.1 192.168.172.2',
62
'RPORT' => '1337'
63
}
64
}
65
expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1', '-o', 'RPORT=1337', '-o', 'rhosts=192.168.172.2'])).to include(expected_result)
66
end
67
68
it 'allows setting action inline' do
69
expected_result = {
70
datastore_options: {
71
'RHOSTS' => '192.168.172.1',
72
'RPORT' => '1337',
73
}
74
}
75
expected_result[:action] = 'action-name' unless opts[:method_name] == 'parse_check_opts'
76
result = subject.send(opts[:method_name], ['RHOSTS=192.168.172.1', 'RPORT=1337', 'action=action-name'])
77
expect(result).to include(expected_result)
78
end
79
80
it 'parses the option str directly into its components' do
81
expected_result = {
82
datastore_options: {
83
'RHOSTS' => '192.168.172.1',
84
'RPORT' => '1337'
85
}
86
}
87
expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1,RPORT=1337'])).to include(expected_result)
88
end
89
90
it 'handles arguments containing spaces' do
91
args = ['-o', 'RHOSTS=http://user:this is a [email protected]']
92
expected_result = {
93
datastore_options: {
94
'RHOSTS' => '"http://user:this is a [email protected]"'
95
}
96
}
97
expect(subject.send(opts[:method_name], args)).to include(expected_result)
98
end
99
100
RHOST_EXAMPLES.each do |value|
101
it "parses the option str correctly for rhost #{value.inspect}" do
102
expected_result = {
103
datastore_options: {
104
'RHOSTS' => value,
105
'RPORT' => '1337'
106
}
107
}
108
expect(subject.send(opts[:method_name], ['-o', "RHOSTS=#{value},RPORT=1337"])).to include(expected_result)
109
end
110
end
111
112
it 'correctly handles combinations of inline options, arguments, and option str being provided' do
113
args = [
114
'-o', 'RHOSTS=192.168.172.1,RPORT=1337',
115
'192.168.172.2',
116
'LPORT=5555'
117
]
118
expected_result = {
119
datastore_options: {
120
'RHOSTS' => '192.168.172.1 192.168.172.2',
121
'RPORT' => '1337',
122
'LPORT' => '5555'
123
}
124
}
125
expect(subject.send(opts[:method_name], args)).to include(expected_result)
126
end
127
end
128
129
context 'when arbitrary datastore key value pairs are provided' do
130
it 'allows setting one value' do
131
expected_result = {
132
datastore_options: {
133
'RHOSTS' => '192.168.172.1'
134
}
135
}
136
expect(subject.send(opts[:method_name], ['RHOSTS=192.168.172.1'])).to include(expected_result)
137
end
138
139
it 'allows setting multiple options individually' do
140
expected_result = {
141
datastore_options: {
142
'RHOSTS' => '192.168.172.1',
143
'RPORT' => '1337'
144
}
145
}
146
expect(subject.send(opts[:method_name], ['RHOSTS=192.168.172.1', 'RPORT=1337'])).to include(expected_result)
147
end
148
149
it 'correctly handles a missing value' do
150
expected_result = {
151
datastore_options: {
152
'RPORT' => ''
153
}
154
}
155
expect(subject.send(opts[:method_name], ['RPORT='])).to include(expected_result)
156
end
157
158
it 'handles multiple values' do
159
args = ['RHOSTS=192.168.172.1', 'rhosts=192.168.172.2', 'rhost=smb://user:a b [email protected]']
160
expected_result = {
161
datastore_options: {
162
'RHOSTS' => '192.168.172.1 192.168.172.2 "smb://user:a b [email protected]"'
163
}
164
}
165
expect(subject.send(opts[:method_name], args)).to include(expected_result)
166
end
167
168
it 'handles whitespaces' do
169
args = ['rhosts=http://user:this is a [email protected]', 'http://user:[email protected]']
170
expected_result = {
171
datastore_options: {
172
'RHOSTS' => '"http://user:this is a [email protected]" http://user:[email protected]'
173
}
174
}
175
expect(subject.send(opts[:method_name], args)).to include(expected_result)
176
end
177
end
178
179
context 'when arguments that resemble an RHOST value are used' do
180
it 'handles arguments containing spaces' do
181
args = ['http://user:this is a [email protected]', 'http://user:[email protected]']
182
expected_result = {
183
datastore_options: {
184
'RHOSTS' => '"http://user:this is a [email protected]" http://user:[email protected]'
185
}
186
}
187
expect(subject.send(opts[:method_name], args)).to include(expected_result)
188
end
189
190
RHOST_EXAMPLES.each do |value|
191
it "works with a single value of #{value}" do
192
expected_result = {
193
datastore_options: {
194
'RHOSTS' => value
195
}
196
}
197
expect(subject.send(opts[:method_name], [value])).to include(expected_result)
198
end
199
200
it 'works with multiple values' do
201
expected_result = {
202
datastore_options: {
203
'RHOSTS' => "#{value} #{value} #{value}"
204
}
205
}
206
expect(subject.send(opts[:method_name], [value, value, value])).to include(expected_result)
207
end
208
209
it 'works with arbitrary option values' do
210
expected_result = {
211
datastore_options: {
212
'RHOSTS' => "#{value} #{value}",
213
'RPORT' => '2000',
214
'LPORT' => '5555'
215
}
216
}
217
expect(subject.send(opts[:method_name], ['-o', "RHOSTS=#{value}", '-o', 'RPORT=2000', value, 'LPORT=5555'])).to include(expected_result)
218
end
219
end
220
end
221
end
222
223
RSpec.shared_examples_for 'a command which shows help menus' do |opts|
224
it 'shows the help menu with the -h flag' do
225
expect(subject.send(opts[:method_name], ['-h'])).to be_nil
226
expect(subject).to have_received(opts[:expected_help_cmd])
227
end
228
229
it 'shows the help menu with --help flag' do
230
expect(subject.send(opts[:method_name], ['--help'])).to be_nil
231
expect(subject).to have_received(opts[:expected_help_cmd])
232
end
233
234
[
235
['--foo'],
236
['--foo', 'bar'],
237
].each do |args|
238
it "shows the help menu with unknown flags #{args.inspect}" do
239
expect(subject.send(opts[:method_name], args)).to be_nil
240
expect(subject).to have_received(opts[:expected_help_cmd])
241
end
242
end
243
end
244
245
RSpec.describe Msf::Ui::Console::ModuleArgumentParsing do
246
include_context 'Msf::UIDriver'
247
248
let(:framework) { nil }
249
let(:subject) do
250
described_class = self.described_class
251
dummy_class = Class.new do
252
include Msf::Ui::Console::ModuleCommandDispatcher
253
include described_class
254
255
# Method not provided by the mixin, needs to be implemented by class that mixes in described_class
256
def cmd_run_help
257
# noop
258
end
259
260
# Method not provided by the mixin, needs to be implemented by class that mixes in described_class
261
def cmd_exploit_help
262
# noop
263
end
264
end
265
instance = dummy_class.new(driver)
266
instance
267
end
268
269
before do
270
allow(subject).to receive(:cmd_run_help)
271
allow(subject).to receive(:cmd_exploit_help)
272
allow(subject).to receive(:cmd_check_help)
273
end
274
275
describe '#parse_check_opts' do
276
let(:current_mod) { instance_double Msf::Auxiliary, datastore: {} }
277
278
before do
279
allow(subject).to receive(:mod).and_return(current_mod)
280
end
281
282
it_behaves_like 'a command which parses datastore values',
283
method_name: 'parse_check_opts',
284
expected_help_cmd: 'cmd_check_help'
285
286
it_behaves_like 'a command which shows help menus',
287
method_name: 'parse_check_opts',
288
expected_help_cmd: 'cmd_check_help'
289
end
290
291
describe '#parse_run_opts' do
292
let(:current_mod) { instance_double Msf::Auxiliary, datastore: {} }
293
294
before do
295
allow(subject).to receive(:mod).and_return(current_mod)
296
end
297
298
it_behaves_like 'a command which parses datastore values',
299
method_name: 'parse_run_opts',
300
expected_help_cmd: 'cmd_run_help'
301
302
it_behaves_like 'a command which shows help menus',
303
method_name: 'parse_run_opts',
304
expected_help_cmd: 'cmd_run_help'
305
306
it 'handles an action being supplied' do
307
args = []
308
expected_result = {
309
jobify: false,
310
quiet: false,
311
action: 'action-name',
312
datastore_options: {}
313
}
314
expect(subject.parse_run_opts(args, action: 'action-name')).to eq(expected_result)
315
end
316
317
it 'handles an action being specified from the original datastore value' do
318
current_mod.datastore['action'] = 'datastore-action-name'
319
args = []
320
expected_result = {
321
jobify: false,
322
quiet: false,
323
action: 'action-name',
324
datastore_options: {}
325
}
326
expect(subject.parse_run_opts(args, action: 'action-name')).to eq(expected_result)
327
end
328
329
it 'handles an action being nil' do
330
args = []
331
expected_result = {
332
jobify: false,
333
quiet: false,
334
action: nil,
335
datastore_options: {}
336
}
337
expect(subject.parse_run_opts(args)).to eq(expected_result)
338
end
339
end
340
341
describe '#parse_exploit_opts' do
342
let(:current_mod) { instance_double Msf::Exploit, datastore: {} }
343
344
before do
345
allow(subject).to receive(:mod).and_return(current_mod)
346
end
347
348
it_behaves_like 'a command which parses datastore values',
349
method_name: 'parse_exploit_opts',
350
expected_help_cmd: 'cmd_exploit_help'
351
352
it_behaves_like 'a command which shows help menus',
353
method_name: 'parse_exploit_opts',
354
expected_help_cmd: 'cmd_exploit_help'
355
356
it 'handles no arguments being supplied' do
357
args = []
358
expected_result = {
359
action: nil,
360
jobify: false,
361
quiet: false,
362
datastore_options: {}
363
}
364
expect(subject.parse_exploit_opts(args)).to eq(expected_result)
365
end
366
367
it 'allows multiple exploit options to be set' do
368
args = [
369
# encoder
370
'-e', 'encoder_value',
371
# force
372
'-f',
373
# quiet
374
'-q',
375
# nop
376
'-n', 'nop_value',
377
# option str
378
'-o', 'RPORT=9001',
379
# payload
380
'-p', 'payload_value',
381
# target
382
'-t', '5',
383
# run in the background
384
'-z',
385
# inline option
386
'LPORT=5555',
387
# rhosts
388
'192.168.172.1',
389
'192.168.172.2',
390
'example.com'
391
]
392
expected_result = {
393
action: nil,
394
jobify: false,
395
quiet: true,
396
datastore_options: {
397
'RHOSTS' => '192.168.172.1 192.168.172.2 example.com',
398
'RPORT' => '9001',
399
'LPORT' => '5555'
400
},
401
encoder: 'encoder_value',
402
force: true,
403
nop: 'nop_value',
404
payload: 'payload_value',
405
target: 5,
406
background: true
407
}
408
expect(subject.parse_exploit_opts(args)).to eq(expected_result)
409
end
410
end
411
end
412
413