CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/msf/ui/console/module_argument_parsing_spec.rb
Views: 11655
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 'parses the option str directly into its components' do
69
expected_result = {
70
datastore_options: {
71
'RHOSTS' => '192.168.172.1',
72
'RPORT' => '1337'
73
}
74
}
75
expect(subject.send(opts[:method_name], ['-o', 'RHOSTS=192.168.172.1,RPORT=1337'])).to include(expected_result)
76
end
77
78
it 'handles arguments containing spaces' do
79
args = ['-o', 'RHOSTS=http://user:this is a [email protected]']
80
expected_result = {
81
datastore_options: {
82
'RHOSTS' => '"http://user:this is a [email protected]"'
83
}
84
}
85
expect(subject.send(opts[:method_name], args)).to include(expected_result)
86
end
87
88
RHOST_EXAMPLES.each do |value|
89
it "parses the option str correctly for rhost #{value.inspect}" do
90
expected_result = {
91
datastore_options: {
92
'RHOSTS' => value,
93
'RPORT' => '1337'
94
}
95
}
96
expect(subject.send(opts[:method_name], ['-o', "RHOSTS=#{value},RPORT=1337"])).to include(expected_result)
97
end
98
end
99
100
it 'correctly handles combinations of inline options, arguments, and option str being provided' do
101
args = [
102
'-o', 'RHOSTS=192.168.172.1,RPORT=1337',
103
'192.168.172.2',
104
'LPORT=5555'
105
]
106
expected_result = {
107
datastore_options: {
108
'RHOSTS' => '192.168.172.1 192.168.172.2',
109
'RPORT' => '1337',
110
'LPORT' => '5555'
111
}
112
}
113
expect(subject.send(opts[:method_name], args)).to include(expected_result)
114
end
115
end
116
117
context 'when arbitrary datastore key value pairs are provided' do
118
it 'allows setting one value' do
119
expected_result = {
120
datastore_options: {
121
'RHOSTS' => '192.168.172.1'
122
}
123
}
124
expect(subject.send(opts[:method_name], ['RHOSTS=192.168.172.1'])).to include(expected_result)
125
end
126
127
it 'allows setting multiple options individually' do
128
expected_result = {
129
datastore_options: {
130
'RHOSTS' => '192.168.172.1',
131
'RPORT' => '1337'
132
}
133
}
134
expect(subject.send(opts[:method_name], ['RHOSTS=192.168.172.1', 'RPORT=1337'])).to include(expected_result)
135
end
136
137
it 'correctly handles a missing value' do
138
expected_result = {
139
datastore_options: {
140
'RPORT' => ''
141
}
142
}
143
expect(subject.send(opts[:method_name], ['RPORT='])).to include(expected_result)
144
end
145
146
it 'handles multiple values' do
147
args = ['RHOSTS=192.168.172.1', 'rhosts=192.168.172.2', 'rhost=smb://user:a b [email protected]']
148
expected_result = {
149
datastore_options: {
150
'RHOSTS' => '192.168.172.1 192.168.172.2 "smb://user:a b [email protected]"'
151
}
152
}
153
expect(subject.send(opts[:method_name], args)).to include(expected_result)
154
end
155
156
it 'handles whitespaces' do
157
args = ['rhosts=http://user:this is a [email protected]', 'http://user:[email protected]']
158
expected_result = {
159
datastore_options: {
160
'RHOSTS' => '"http://user:this is a [email protected]" http://user:[email protected]'
161
}
162
}
163
expect(subject.send(opts[:method_name], args)).to include(expected_result)
164
end
165
end
166
167
context 'when arguments that resemble an RHOST value are used' do
168
it 'handles arguments containing spaces' do
169
args = ['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
178
RHOST_EXAMPLES.each do |value|
179
it "works with a single value of #{value}" do
180
expected_result = {
181
datastore_options: {
182
'RHOSTS' => value
183
}
184
}
185
expect(subject.send(opts[:method_name], [value])).to include(expected_result)
186
end
187
188
it 'works with multiple values' do
189
expected_result = {
190
datastore_options: {
191
'RHOSTS' => "#{value} #{value} #{value}"
192
}
193
}
194
expect(subject.send(opts[:method_name], [value, value, value])).to include(expected_result)
195
end
196
197
it 'works with arbitrary option values' do
198
expected_result = {
199
datastore_options: {
200
'RHOSTS' => "#{value} #{value}",
201
'RPORT' => '2000',
202
'LPORT' => '5555'
203
}
204
}
205
expect(subject.send(opts[:method_name], ['-o', "RHOSTS=#{value}", '-o', 'RPORT=2000', value, 'LPORT=5555'])).to include(expected_result)
206
end
207
end
208
end
209
end
210
211
RSpec.shared_examples_for 'a command which shows help menus' do |opts|
212
it 'shows the help menu with the -h flag' do
213
expect(subject.send(opts[:method_name], ['-h'])).to be_nil
214
expect(subject).to have_received(opts[:expected_help_cmd])
215
end
216
217
it 'shows the help menu with --help flag' do
218
expect(subject.send(opts[:method_name], ['--help'])).to be_nil
219
expect(subject).to have_received(opts[:expected_help_cmd])
220
end
221
222
[
223
['--foo'],
224
['--foo', 'bar'],
225
].each do |args|
226
it "shows the help menu with unknown flags #{args.inspect}" do
227
expect(subject.send(opts[:method_name], args)).to be_nil
228
expect(subject).to have_received(opts[:expected_help_cmd])
229
end
230
end
231
end
232
233
RSpec.describe Msf::Ui::Console::ModuleArgumentParsing do
234
include_context 'Msf::UIDriver'
235
236
let(:framework) { nil }
237
let(:subject) do
238
described_class = self.described_class
239
dummy_class = Class.new do
240
include Msf::Ui::Console::ModuleCommandDispatcher
241
include described_class
242
243
# Method not provided by the mixin, needs to be implemented by class that mixes in described_class
244
def cmd_run_help
245
# noop
246
end
247
248
# Method not provided by the mixin, needs to be implemented by class that mixes in described_class
249
def cmd_exploit_help
250
# noop
251
end
252
end
253
instance = dummy_class.new(driver)
254
instance
255
end
256
257
before do
258
allow(subject).to receive(:cmd_run_help)
259
allow(subject).to receive(:cmd_exploit_help)
260
allow(subject).to receive(:cmd_check_help)
261
end
262
263
describe '#parse_check_opts' do
264
let(:current_mod) { instance_double Msf::Auxiliary, datastore: {} }
265
266
before do
267
allow(subject).to receive(:mod).and_return(current_mod)
268
end
269
270
it_behaves_like 'a command which parses datastore values',
271
method_name: 'parse_check_opts',
272
expected_help_cmd: 'cmd_check_help'
273
274
it_behaves_like 'a command which shows help menus',
275
method_name: 'parse_check_opts',
276
expected_help_cmd: 'cmd_check_help'
277
end
278
279
describe '#parse_run_opts' do
280
let(:current_mod) { instance_double Msf::Auxiliary, datastore: {} }
281
282
before do
283
allow(subject).to receive(:mod).and_return(current_mod)
284
end
285
286
it_behaves_like 'a command which parses datastore values',
287
method_name: 'parse_run_opts',
288
expected_help_cmd: 'cmd_run_help'
289
290
it_behaves_like 'a command which shows help menus',
291
method_name: 'parse_run_opts',
292
expected_help_cmd: 'cmd_run_help'
293
294
it 'handles an action being supplied' do
295
args = []
296
expected_result = {
297
jobify: false,
298
quiet: false,
299
action: 'action-name',
300
datastore_options: {}
301
}
302
expect(subject.parse_run_opts(args, action: 'action-name')).to eq(expected_result)
303
end
304
305
it 'handles an action being specified from the original datastore value' do
306
current_mod.datastore['action'] = 'datastore-action-name'
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 nil' do
318
args = []
319
expected_result = {
320
jobify: false,
321
quiet: false,
322
action: nil,
323
datastore_options: {}
324
}
325
expect(subject.parse_run_opts(args)).to eq(expected_result)
326
end
327
end
328
329
describe '#parse_exploit_opts' do
330
let(:current_mod) { instance_double Msf::Exploit, datastore: {} }
331
332
before do
333
allow(subject).to receive(:mod).and_return(current_mod)
334
end
335
336
it_behaves_like 'a command which parses datastore values',
337
method_name: 'parse_exploit_opts',
338
expected_help_cmd: 'cmd_exploit_help'
339
340
it_behaves_like 'a command which shows help menus',
341
method_name: 'parse_exploit_opts',
342
expected_help_cmd: 'cmd_exploit_help'
343
344
it 'handles no arguments being supplied' do
345
args = []
346
expected_result = {
347
jobify: false,
348
quiet: false,
349
datastore_options: {}
350
}
351
expect(subject.parse_exploit_opts(args)).to eq(expected_result)
352
end
353
354
it 'allows multiple exploit options to be set' do
355
args = [
356
# encoder
357
'-e', 'encoder_value',
358
# force
359
'-f',
360
# quiet
361
'-q',
362
# nop
363
'-n', 'nop_value',
364
# option str
365
'-o', 'RPORT=9001',
366
# payload
367
'-p', 'payload_value',
368
# target
369
'-t', '5',
370
# run in the background
371
'-z',
372
# inline option
373
'LPORT=5555',
374
# rhosts
375
'192.168.172.1',
376
'192.168.172.2',
377
'example.com'
378
]
379
expected_result = {
380
jobify: false,
381
quiet: true,
382
datastore_options: {
383
'RHOSTS' => '192.168.172.1 192.168.172.2 example.com',
384
'RPORT' => '9001',
385
'LPORT' => '5555'
386
},
387
encoder: 'encoder_value',
388
force: true,
389
nop: 'nop_value',
390
payload: 'payload_value',
391
target: 5,
392
background: true
393
}
394
expect(subject.parse_exploit_opts(args)).to eq(expected_result)
395
end
396
end
397
end
398
399