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/acceptance/mssql_spec.rb
Views: 11766
1
require 'acceptance_spec_helper'
2
3
RSpec.describe 'MSSQL sessions and MSSQL modules' do
4
include_context 'wait_for_expect'
5
6
tests = {
7
mssql: {
8
target: {
9
session_module: "auxiliary/scanner/mssql/mssql_login",
10
type: 'MSSQL',
11
platforms: [:linux, :osx, :windows],
12
datastore: {
13
global: {},
14
module: {
15
username: ENV.fetch('MSSQL_USER', 'sa'),
16
password: ENV.fetch('MSSQL_PASSWORD', 'yourStrong(!)Password'),
17
rhost: ENV.fetch('MSSQL_RHOST', '127.0.0.1'),
18
rport: ENV.fetch('MSSQL_RPORT', '1433'),
19
database: 'master'
20
}
21
}
22
},
23
module_tests: [
24
{
25
name: "post/test/mssql",
26
platforms: [:linux, :osx, :windows],
27
targets: [:session],
28
skipped: false,
29
},
30
{
31
name: "auxiliary/scanner/mssql/mssql_hashdump",
32
platforms: [:linux, :osx, :windows],
33
targets: [:session, :rhost],
34
skipped: false,
35
lines: {
36
all: {
37
required: [
38
/Instance Name: "\w+"/,
39
]
40
},
41
}
42
},
43
{
44
name: "auxiliary/scanner/mssql/mssql_version",
45
platforms: [:linux, :osx, :windows],
46
targets: [:session, :rhost],
47
skipped: false,
48
lines: {
49
all: {
50
required: [
51
/Version: \d+.\d+.\d+/,
52
/Encryption: (?:on|off|unsupported|required|unknown)/
53
]
54
},
55
}
56
},
57
{
58
name: "auxiliary/admin/mssql/mssql_enum",
59
platforms: [:linux, :osx, :windows],
60
targets: [:session, :rhost],
61
skipped: false,
62
lines: {
63
all: {
64
required: [
65
'Version:',
66
/Microsoft SQL Server \d+.\d+/,
67
'Databases on the server:',
68
'System Logins on this Server:'
69
]
70
},
71
}
72
},
73
{
74
name: "auxiliary/scanner/mssql/mssql_schemadump",
75
platforms: [:linux, :osx, :windows],
76
targets: [:session, :rhost],
77
skipped: false,
78
lines: {
79
all: {
80
required: [
81
/Instance Name: "\w+"/,
82
'Microsoft SQL Server Schema',
83
'Host:',
84
'Port:',
85
'Instance:',
86
'Version:'
87
]
88
},
89
}
90
},
91
{
92
name: "auxiliary/admin/mssql/mssql_sql",
93
platforms: [:linux, :osx, :windows],
94
targets: [:session, :rhost],
95
skipped: false,
96
lines: {
97
all: {
98
required: [
99
"Response",
100
"Microsoft SQL Server",
101
]
102
},
103
}
104
}
105
]
106
}
107
}
108
109
allure_test_environment = AllureRspec.configuration.environment_properties
110
111
let_it_be(:current_platform) { Acceptance::Session::current_platform }
112
113
# Driver instance, keeps track of all open processes/payloads/etc, so they can be closed cleanly
114
let_it_be(:driver) do
115
driver = Acceptance::ConsoleDriver.new
116
driver
117
end
118
119
# Opens a test console with the test loadpath specified
120
# @!attribute [r] console
121
# @return [Acceptance::Console]
122
let_it_be(:console) do
123
console = driver.open_console
124
125
# Load the test modules
126
console.sendline('loadpath test/modules')
127
console.recvuntil(/Loaded \d+ modules:[^\n]*\n/)
128
console.recvuntil(/\d+ auxiliary modules[^\n]*\n/)
129
console.recvuntil(/\d+ exploit modules[^\n]*\n/)
130
console.recvuntil(/\d+ post modules[^\n]*\n/)
131
console.recvuntil(Acceptance::Console.prompt)
132
133
# Read the remaining console
134
# console.sendline "quit -y"
135
# console.recv_available
136
137
features = %w[
138
mssql_session_type
139
]
140
141
features.each do |feature|
142
console.sendline("features set #{feature} true")
143
console.recvuntil(Acceptance::Console.prompt)
144
end
145
146
console
147
end
148
149
# Run the given block in a 'test harness' which will handle all of the boilerplate for asserting module results, cleanup, and artifact tracking
150
# This doesn't happen in a before/after block to ensure that allure's report generation is correctly attached to the correct test scope
151
def with_test_harness(module_test)
152
begin
153
replication_commands = []
154
155
known_failures = module_test.dig(:lines, :all, :known_failures) || []
156
known_failures += module_test.dig(:lines, current_platform, :known_failures) || []
157
known_failures = known_failures.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }
158
159
required_lines = module_test.dig(:lines, :all, :required) || []
160
required_lines += module_test.dig(:lines, current_platform, :required) || []
161
required_lines = required_lines.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }
162
163
yield replication_commands
164
165
# XXX: When debugging failed tests, you can enter into an interactive msfconsole prompt with:
166
# console.interact
167
168
# Expect the test module to complete
169
module_type = module_test[:name].split('/').first
170
test_result = console.recvuntil("#{module_type.capitalize} module execution completed")
171
172
# Ensure there are no failures, and assert tests are complete
173
aggregate_failures("#{target.type} target and passes the #{module_test[:name].inspect} tests") do
174
# Skip any ignored lines from the validation input
175
validated_lines = test_result.lines.reject do |line|
176
is_acceptable = known_failures.any? do |acceptable_failure|
177
is_matching_line = is_matching_line.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value)
178
is_matching_line &&
179
acceptable_failure.if?(test_environment)
180
end || line.match?(/Passed: \d+; Failed: \d+/)
181
182
is_acceptable
183
end
184
185
validated_lines.each do |test_line|
186
test_line = Acceptance::Session.uncolorize(test_line)
187
expect(test_line).to_not include('FAILED', '[-] FAILED', '[-] Exception', '[-] '), "Unexpected error: #{test_line}"
188
end
189
190
# Assert all expected lines are present
191
required_lines.each do |required|
192
next unless required.if?(test_environment)
193
if required.value.is_a?(Regexp)
194
expect(test_result).to match(required.value)
195
else
196
expect(test_result).to include(required.value)
197
end
198
end
199
200
# Assert all ignored lines are present, if they are not present - they should be removed from
201
# the calling config
202
known_failures.each do |acceptable_failure|
203
next if acceptable_failure.flaky?(test_environment)
204
next unless acceptable_failure.if?(test_environment)
205
206
expect(test_result).to include(acceptable_failure.value)
207
end
208
end
209
rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e
210
test_run_error = e
211
end
212
213
# Test cleanup. We intentionally omit cleanup from an `after(:each)` to ensure the allure attachments are
214
# still generated if the session dies in a weird way etc
215
216
console_reset_error = nil
217
current_console_data = console.all_data
218
begin
219
console.reset
220
rescue => e
221
console_reset_error = e
222
Allure.add_attachment(
223
name: 'console.reset failure information',
224
source: "Error: #{e.class} - #{e.message}\n#{(e.backtrace || []).join("\n")}",
225
type: Allure::ContentType::TXT
226
)
227
end
228
229
target_configuration_details = target.as_readable_text(
230
default_global_datastore: default_global_datastore,
231
default_module_datastore: default_module_datastore
232
)
233
234
replication_steps = <<~EOF
235
## Load test modules
236
loadpath test/modules
237
238
#{target_configuration_details}
239
240
## Replication commands
241
#{replication_commands.empty? ? '# no additional commands run' : replication_commands.join("\n")}
242
EOF
243
244
Allure.add_attachment(
245
name: 'payload configuration and replication',
246
source: replication_steps,
247
type: Allure::ContentType::TXT
248
)
249
250
Allure.add_attachment(
251
name: 'console data',
252
source: current_console_data,
253
type: Allure::ContentType::TXT
254
)
255
256
test_assertions = JSON.pretty_generate(
257
{
258
required_lines: required_lines.map(&:to_h),
259
known_failures: known_failures.map(&:to_h),
260
}
261
)
262
Allure.add_attachment(
263
name: 'test assertions',
264
source: test_assertions,
265
type: Allure::ContentType::TXT
266
)
267
268
raise test_run_error if test_run_error
269
raise console_reset_error if console_reset_error
270
end
271
272
tests.each do |runtime_name, test_config|
273
runtime_name = "#{runtime_name}#{ENV.fetch('RUNTIME_VERSION', '')}"
274
275
describe "#{Acceptance::Session.current_platform}/#{runtime_name}", focus: test_config[:focus] do
276
test_config[:module_tests].each do |module_test|
277
describe(
278
module_test[:name],
279
if: (
280
Acceptance::Session.supported_platform?(module_test)
281
)
282
) do
283
let(:target) { Acceptance::Target.new(test_config[:target]) }
284
285
let(:default_global_datastore) do
286
{
287
}
288
end
289
290
let(:test_environment) { allure_test_environment }
291
292
let(:default_module_datastore) do
293
{
294
lhost: '127.0.0.1'
295
}
296
end
297
298
# The shared session id that will be reused across the test run
299
let(:session_id) do
300
console.sendline "use #{target.session_module}"
301
console.recvuntil(Acceptance::Console.prompt)
302
303
# Set global options
304
console.sendline target.setg_commands(default_global_datastore: default_global_datastore)
305
console.recvuntil(Acceptance::Console.prompt)
306
307
console.sendline target.run_command(default_module_datastore: { PASS_FILE: nil, USER_FILE: nil, CreateSession: true })
308
309
session_id = nil
310
# Wait for the session to open, or break early if the payload is detected as dead
311
wait_for_expect do
312
session_opened_matcher = /#{target.type} session (\d+) opened[^\n]*\n/
313
session_message = ''
314
begin
315
session_message = console.recvuntil(session_opened_matcher, timeout: 1)
316
rescue Acceptance::ChildProcessRecvError
317
# noop
318
end
319
320
session_id = session_message[session_opened_matcher, 1]
321
expect(session_id).to_not be_nil
322
end
323
324
session_id
325
end
326
327
before :each do |example|
328
next unless example.respond_to?(:parameter)
329
330
# Add the test environment metadata to the rspec example instance - so it appears in the final allure report UI
331
test_environment.each do |key, value|
332
example.parameter(key, value)
333
end
334
end
335
336
after :all do
337
driver.close_payloads
338
console.reset
339
end
340
341
context "when targeting a session", if: module_test[:targets].include?(:session) do
342
it(
343
"#{Acceptance::Session.current_platform}/#{runtime_name} session opens and passes the #{module_test[:name].inspect} tests"
344
) do
345
with_test_harness(module_test) do |replication_commands|
346
# Ensure we have a valid session id; We intentionally omit this from a `before(:each)` to ensure the allure attachments are generated if the session dies
347
expect(session_id).to_not(be_nil, proc do
348
"There should be a session present"
349
end)
350
351
use_module = "use #{module_test[:name]}"
352
run_module = "run session=#{session_id} Verbose=true"
353
354
replication_commands << use_module
355
console.sendline(use_module)
356
console.recvuntil(Acceptance::Console.prompt)
357
358
replication_commands << run_module
359
console.sendline(run_module)
360
361
# Assertions will happen after this block ends
362
end
363
end
364
end
365
366
context "when targeting an rhost", if: module_test[:targets].include?(:rhost) do
367
it(
368
"#{Acceptance::Session.current_platform}/#{runtime_name} rhost opens and passes the #{module_test[:name].inspect} tests"
369
) do
370
with_test_harness(module_test) do |replication_commands|
371
use_module = "use #{module_test[:name]}"
372
run_module = "run #{target.datastore_options(default_module_datastore: default_module_datastore)} Verbose=true"
373
374
replication_commands << use_module
375
console.sendline(use_module)
376
console.recvuntil(Acceptance::Console.prompt)
377
378
replication_commands << run_module
379
console.sendline(run_module)
380
381
# Assertions will happen after this block ends
382
end
383
end
384
end
385
end
386
end
387
end
388
end
389
end
390
391