Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/plugins/mcp/option_validator_spec.rb
74512 views
1
# frozen_string_literal: true
2
3
require 'spec_helper'
4
require 'rex/text'
5
require Metasploit::Framework.root.join('plugins/mcp.rb').to_path
6
7
RSpec.describe Msf::Plugin::MCP do
8
include_context 'Msf::UIDriver'
9
10
let(:framework) { instance_double(Msf::Framework) }
11
let(:output) { driver_output }
12
let(:base_opts) { { 'LocalOutput' => output } }
13
14
let(:plugins_collection) do
15
instance_double('Msf::PluginManager').tap do |pm|
16
allow(pm).to receive(:find).and_return(nil)
17
allow(pm).to receive(:load).and_return(true)
18
end
19
end
20
21
let(:threads_manager) do
22
instance_double('Msf::Framework::ThreadManager').tap do |tm|
23
allow(tm).to receive(:spawn).and_return(Thread.new {})
24
end
25
end
26
27
let(:mock_client) do
28
instance_double('Msf::MCP::Metasploit::Client').tap do |c|
29
allow(c).to receive(:authenticate).and_return('token')
30
allow(c).to receive(:shutdown)
31
end
32
end
33
34
before do
35
allow(framework).to receive(:plugins).and_return(plugins_collection)
36
allow(framework).to receive(:threads).and_return(threads_manager)
37
stub_const('Msf::MCP::Metasploit::Client', Class.new do
38
def initialize(**_args); end
39
def authenticate(*_args); 'token'; end
40
def shutdown; end
41
end)
42
stub_const('Msf::MCP::Security::RateLimiter', Class.new do
43
def initialize(**_args); end
44
end)
45
stub_const('Msf::MCP::Server', Class.new do
46
def initialize(**_args); end
47
def start(**_args); end
48
def shutdown; end
49
end)
50
allow(Rex::Text).to receive(:rand_text_alphanumeric).with(12).and_return('abcdefghijkl')
51
52
mock_dispatcher = instance_double(described_class::McpCommandDispatcher)
53
allow(mock_dispatcher).to receive(:plugin=)
54
allow_any_instance_of(described_class).to receive(:add_console_dispatcher).and_return(mock_dispatcher)
55
allow_any_instance_of(described_class).to receive(:remove_console_dispatcher)
56
end
57
58
subject(:plugin) { described_class.new(framework, base_opts) }
59
60
describe '#validate_options!' do
61
describe 'ServerPort' do
62
it 'accepts port 1' do
63
expect { plugin.send(:validate_options!, { 'ServerPort' => '1' }) }.not_to raise_error
64
end
65
66
it 'accepts port 3000' do
67
expect { plugin.send(:validate_options!, { 'ServerPort' => '3000' }) }.not_to raise_error
68
end
69
70
it 'accepts port 65535' do
71
expect { plugin.send(:validate_options!, { 'ServerPort' => '65535' }) }.not_to raise_error
72
end
73
74
it 'rejects port 0' do
75
expect { plugin.send(:validate_options!, { 'ServerPort' => '0' }) }.to raise_error(Msf::MCP::Config::ValidationError, /ServerPort/)
76
end
77
78
it 'rejects port 65536' do
79
expect { plugin.send(:validate_options!, { 'ServerPort' => '65536' }) }.to raise_error(Msf::MCP::Config::ValidationError, /ServerPort/)
80
end
81
82
it 'rejects non-numeric value "abc"' do
83
expect { plugin.send(:validate_options!, { 'ServerPort' => 'abc' }) }.to raise_error(Msf::MCP::Config::ValidationError, /ServerPort/)
84
end
85
86
it 'is optional (nil is accepted)' do
87
expect { plugin.send(:validate_options!, {}) }.not_to raise_error
88
end
89
end
90
91
describe 'RpcPort' do
92
it 'accepts port 1' do
93
expect { plugin.send(:validate_options!, { 'RpcPort' => '1' }) }.not_to raise_error
94
end
95
96
it 'accepts port 55552' do
97
expect { plugin.send(:validate_options!, { 'RpcPort' => '55552' }) }.not_to raise_error
98
end
99
100
it 'accepts port 65535' do
101
expect { plugin.send(:validate_options!, { 'RpcPort' => '65535' }) }.not_to raise_error
102
end
103
104
it 'rejects port 0' do
105
expect { plugin.send(:validate_options!, { 'RpcPort' => '0' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RpcPort/)
106
end
107
108
it 'rejects port 65536' do
109
expect { plugin.send(:validate_options!, { 'RpcPort' => '65536' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RpcPort/)
110
end
111
112
it 'rejects non-numeric value "abc"' do
113
expect { plugin.send(:validate_options!, { 'RpcPort' => 'abc' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RpcPort/)
114
end
115
end
116
117
describe 'RpcSSL' do
118
it 'accepts "true"' do
119
expect { plugin.send(:validate_options!, { 'RpcSSL' => 'true' }) }.not_to raise_error
120
end
121
122
it 'accepts "false"' do
123
expect { plugin.send(:validate_options!, { 'RpcSSL' => 'false' }) }.not_to raise_error
124
end
125
126
it 'rejects "yes"' do
127
expect { plugin.send(:validate_options!, { 'RpcSSL' => 'yes' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RpcSSL/)
128
end
129
130
it 'rejects "1"' do
131
expect { plugin.send(:validate_options!, { 'RpcSSL' => '1' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RpcSSL/)
132
end
133
134
it 'rejects empty string' do
135
expect { plugin.send(:validate_options!, { 'RpcSSL' => '' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RpcSSL/)
136
end
137
end
138
139
describe 'RateLimit' do
140
it 'accepts value 1' do
141
expect { plugin.send(:validate_options!, { 'RateLimit' => '1' }) }.not_to raise_error
142
end
143
144
it 'accepts value 60' do
145
expect { plugin.send(:validate_options!, { 'RateLimit' => '60' }) }.not_to raise_error
146
end
147
148
it 'accepts value 10000' do
149
expect { plugin.send(:validate_options!, { 'RateLimit' => '10000' }) }.not_to raise_error
150
end
151
152
it 'rejects value 0' do
153
expect { plugin.send(:validate_options!, { 'RateLimit' => '0' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RateLimit/)
154
end
155
156
it 'rejects value 10001' do
157
expect { plugin.send(:validate_options!, { 'RateLimit' => '10001' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RateLimit/)
158
end
159
160
it 'rejects non-numeric value "fast"' do
161
expect { plugin.send(:validate_options!, { 'RateLimit' => 'fast' }) }.to raise_error(Msf::MCP::Config::ValidationError, /RateLimit/)
162
end
163
end
164
165
describe 'RPC credential pairing' do
166
it 'raises an error when RpcUser is provided without RpcPass' do
167
expect {
168
plugin.send(:validate_options!, { 'RpcUser' => 'msf', 'RpcPass' => '' })
169
}.to raise_error(Msf::MCP::Config::ValidationError, /RpcPass/)
170
end
171
172
it 'raises an error when RpcUser is provided and RpcPass is nil' do
173
expect {
174
plugin.send(:validate_options!, { 'RpcUser' => 'msf' })
175
}.to raise_error(Msf::MCP::Config::ValidationError, /RpcPass/)
176
end
177
178
it 'raises an error when RpcPass is provided without RpcUser' do
179
expect {
180
plugin.send(:validate_options!, { 'RpcPass' => 'secret', 'RpcUser' => '' })
181
}.to raise_error(Msf::MCP::Config::ValidationError, /RpcUser/)
182
end
183
184
it 'raises an error when RpcPass is provided and RpcUser is nil' do
185
expect {
186
plugin.send(:validate_options!, { 'RpcPass' => 'secret' })
187
}.to raise_error(Msf::MCP::Config::ValidationError, /RpcUser/)
188
end
189
190
it 'accepts when both RpcUser and RpcPass are provided' do
191
expect {
192
plugin.send(:validate_options!, { 'RpcUser' => 'msf', 'RpcPass' => 'secret' })
193
}.not_to raise_error
194
end
195
196
it 'accepts when neither RpcUser nor RpcPass is provided' do
197
expect { plugin.send(:validate_options!, {}) }.not_to raise_error
198
end
199
end
200
201
describe 'error messages' do
202
it 'names the offending option in the error for ServerPort' do
203
expect {
204
plugin.send(:validate_options!, { 'ServerPort' => 'bad' })
205
}.to raise_error(Msf::MCP::Config::ValidationError, /Invalid value for ServerPort/)
206
end
207
208
it 'includes the expected format for ServerPort' do
209
expect {
210
plugin.send(:validate_options!, { 'ServerPort' => 'bad' })
211
}.to raise_error(Msf::MCP::Config::ValidationError, /integer between 1 and 65535/)
212
end
213
214
it 'names the offending option in the error for RpcSSL' do
215
expect {
216
plugin.send(:validate_options!, { 'RpcSSL' => 'maybe' })
217
}.to raise_error(Msf::MCP::Config::ValidationError, /Invalid value for RpcSSL/)
218
end
219
220
it 'includes the expected format for RpcSSL' do
221
expect {
222
plugin.send(:validate_options!, { 'RpcSSL' => 'maybe' })
223
}.to raise_error(Msf::MCP::Config::ValidationError, /\"true\" or \"false\"/)
224
end
225
226
it 'names the offending option in the error for RateLimit' do
227
expect {
228
plugin.send(:validate_options!, { 'RateLimit' => '-1' })
229
}.to raise_error(Msf::MCP::Config::ValidationError, /Invalid value for RateLimit/)
230
end
231
232
it 'includes the expected format for RateLimit' do
233
expect {
234
plugin.send(:validate_options!, { 'RateLimit' => '-1' })
235
}.to raise_error(Msf::MCP::Config::ValidationError, /integer between 1 and 10000/)
236
end
237
end
238
end
239
240
describe '#resolve_config' do
241
describe 'provided options appear in the resolved config' do
242
it 'maps ServerHost to mcp[:host]' do
243
config = plugin.send(:resolve_config, { 'ServerHost' => '0.0.0.0' })
244
expect(config[:mcp][:host]).to eq('0.0.0.0')
245
end
246
247
it 'maps ServerPort to mcp[:port]' do
248
config = plugin.send(:resolve_config, { 'ServerPort' => '8080' })
249
expect(config[:mcp][:port]).to eq(8080)
250
end
251
252
it 'maps RateLimit to rate_limit[:requests_per_minute]' do
253
config = plugin.send(:resolve_config, { 'RateLimit' => '120' })
254
expect(config[:rate_limit][:requests_per_minute]).to eq(120)
255
end
256
257
it 'sets rate_limit[:burst_size] equal to requests_per_minute' do
258
config = plugin.send(:resolve_config, { 'RateLimit' => '120' })
259
expect(config[:rate_limit][:burst_size]).to eq(120)
260
end
261
end
262
263
describe 'default values when options are omitted' do
264
let(:config) { plugin.send(:resolve_config, {}) }
265
266
it 'defaults mcp[:transport] to "http"' do
267
expect(config[:mcp][:transport]).to eq('http')
268
end
269
270
it 'defaults mcp[:host] to "localhost"' do
271
expect(config[:mcp][:host]).to eq('localhost')
272
end
273
274
it 'defaults mcp[:port] to 3000' do
275
expect(config[:mcp][:port]).to eq(3000)
276
end
277
278
it 'defaults rate_limit[:requests_per_minute] to 60' do
279
expect(config[:rate_limit][:requests_per_minute]).to eq(60)
280
end
281
282
it 'defaults rate_limit[:burst_size] to 60' do
283
expect(config[:rate_limit][:burst_size]).to eq(60)
284
end
285
end
286
287
end
288
end
289
290