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