Add postgres acceptance tests

This commit is contained in:
adfoster-r7 2024-03-05 16:34:18 +00:00
parent 97ef5cf827
commit e7bc17cab3
15 changed files with 774 additions and 61 deletions

View File

@ -155,7 +155,7 @@ jobs:
# Note: rspec retry is intentionally not used, as it can cause issues with allure's reporting # Note: rspec retry is intentionally not used, as it can cause issues with allure's reporting
# Additionally - flakey tests should be fixed or marked as flakey instead of silently retried # Additionally - flakey tests should be fixed or marked as flakey instead of silently retried
run: | run: |
bundle exec rspec spec/acceptance/ bundle exec rspec spec/acceptance/meterpreter_spec.rb
- name: Archive results - name: Archive results
if: always() if: always()
@ -188,7 +188,7 @@ jobs:
BUNDLE_FORCE_RUBY_PLATFORM: true BUNDLE_FORCE_RUBY_PLATFORM: true
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 3.0.2 ruby-version: '${{ matrix.ruby }}'
bundler-cache: true bundler-cache: true
cache-version: 4 cache-version: 4
# Github actions with Ruby requires Bundler 2.2.18+ # Github actions with Ruby requires Bundler 2.2.18+

View File

@ -0,0 +1,182 @@
name: Acceptance
# Optional, enabling concurrency limits: https://docs.github.com/en/actions/using-jobs/using-concurrency
#concurrency:
# group: ${{ github.ref }}-${{ github.workflow }}
# cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
actions: none
checks: none
contents: none
deployments: none
id-token: none
issues: none
discussions: none
packages: none
pages: none
pull-requests: none
repository-projects: none
security-events: none
statuses: none
on:
push:
branches-ignore:
- gh-pages
- metakitty
pull_request:
branches:
- '*'
paths:
- 'metsploit-framework.gemspec'
- 'Gemfile.lock'
- '**/**postgres**'
- 'spec/acceptance/**'
- 'spec/support/acceptance/**'
- 'spec/acceptance_spec_helper.rb'
# Example of running as a cron, to weed out flaky tests
# schedule:
# - cron: '*/15 * * * *'
jobs:
postgres:
runs-on: ${{ matrix.os }}
timeout-minutes: 40
services:
postgres:
image: ${{ matrix.docker_image }}
ports: ["5432:5432"]
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
fail-fast: true
matrix:
ruby:
- '3.2'
os:
- ubuntu-latest
docker_image:
- postgres:9.4
- postgres:16.2
env:
RAILS_ENV: test
name: ${{ matrix.docker_image }} - ${{ matrix.os }} - Ruby ${{ matrix.ruby }}
steps:
- name: Install system dependencies
run: sudo apt-get install -y --no-install-recommends libpcap-dev graphviz
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Ruby
env:
BUNDLE_WITHOUT: "coverage development pcap"
# Nokogiri doesn't release pre-compiled binaries for preview versions of Ruby; So force compilation with BUNDLE_FORCE_RUBY_PLATFORM
BUNDLE_FORCE_RUBY_PLATFORM: "${{ contains(matrix.ruby, 'preview') && 'true' || 'false' }}"
uses: ruby/setup-ruby@v1
with:
ruby-version: '${{ matrix.ruby }}'
bundler-cache: true
- name: Extract runtime version
run: |
echo "RUNTIME_VERSION=$(echo $DOCKER_IMAGE | awk -F: '{ print $2 }')" >> $GITHUB_ENV
echo "DOCKER_IMAGE_FILENAME=$(echo $DOCKER_IMAGE | tr -d ':')" >> $GITHUB_ENV
env:
DOCKER_IMAGE: ${{ matrix.docker_image }}
OS: ${{ matrix.os }}
- name: acceptance
env:
SPEC_HELPER_LOAD_METASPLOIT: false
SPEC_OPTS: "--tag acceptance --require acceptance_spec_helper.rb --color --format documentation --format AllureRspec::RSpecFormatter"
RUNTIME_VERSION: ${{ env.RUNTIME_VERSION }}
# Unix run command:
# SPEC_HELPER_LOAD_METASPLOIT=false bundle exec ./spec/acceptance
# Windows cmd command:
# set SPEC_HELPER_LOAD_METASPLOIT=false
# bundle exec rspec .\spec\acceptance
# Note: rspec retry is intentionally not used, as it can cause issues with allure's reporting
# Additionally - flakey tests should be fixed or marked as flakey instead of silently retried
run: |
bundle exec rspec spec/acceptance/postgres_spec.rb
- name: Archive results
if: always()
uses: actions/upload-artifact@v4
with:
# Provide a unique artifact for each matrix os, otherwise race conditions can lead to corrupt zips
name: ${{ env.DOCKER_IMAGE_FILENAME }}-${{ matrix.os }}
path: tmp/allure-raw-data
# Generate a final report from the previous test results
report:
name: Generate report
needs:
- postgres
runs-on: ubuntu-latest
if: always()
steps:
- name: Checkout code
uses: actions/checkout@v4
if: always()
- name: Install system dependencies (Linux)
if: always()
run: sudo apt-get -y --no-install-recommends install libpcap-dev graphviz
- name: Setup Ruby
if: always()
env:
BUNDLE_WITHOUT: "coverage development"
BUNDLE_FORCE_RUBY_PLATFORM: true
uses: ruby/setup-ruby@v1
with:
ruby-version: '${{ matrix.ruby }}'
bundler-cache: true
cache-version: 4
# Github actions with Ruby requires Bundler 2.2.18+
# https://github.com/ruby/setup-ruby/tree/d2b39ad0b52eca07d23f3aa14fdf2a3fcc1f411c#windows
bundler: 2.2.33
- uses: actions/download-artifact@v4
id: download
if: always()
with:
# Note: Not specifying a name will download all artifacts from the previous workflow jobs
path: raw-data
- name: allure generate
if: always()
run: |
export VERSION=2.22.1
curl -o allure-$VERSION.tgz -Ls https://github.com/allure-framework/allure2/releases/download/$VERSION/allure-$VERSION.tgz
tar -zxvf allure-$VERSION.tgz -C .
ls -la ${{steps.download.outputs.download-path}}
./allure-$VERSION/bin/allure generate ${{steps.download.outputs.download-path}}/* -o ./allure-report
find ${{steps.download.outputs.download-path}}
bundle exec ruby tools/dev/report_generation/support_matrix/generate.rb --allure-data ${{steps.download.outputs.download-path}} > ./allure-report/support_matrix.html
- name: archive results
if: always()
uses: actions/upload-artifact@v4
with:
name: final-report-${{ github.run_id }}
path: |
./allure-report

View File

@ -212,7 +212,7 @@ module Msf
if s.platform.blank? if s.platform.blank?
issues << "Unknown session platform. This module works with: #{platform.names.join(', ')}." issues << "Unknown session platform. This module works with: #{platform.names.join(', ')}."
elsif !platform.supports?(Msf::Module::PlatformList.transform(s.platform)) elsif !platform.supports?(Msf::Module::PlatformList.transform(s.platform))
issues << "incompatible session platform: #{s.platform}. This module works with: #{platform.names.join(', ')}." issues << "incompatible session platform: #{s.platform}. This module works with: #{platform ? platform.names.join(', ') : platform.inspect}."
end end
end end

View File

@ -7,7 +7,7 @@ create handlers, etc.
The test suite runs on the current host, so the Meterpreter runtimes should be available. The test suite runs on the current host, so the Meterpreter runtimes should be available.
There is no remote host support currently. There is no remote host support currently.
### Examples ### Meterpreter
Useful environment variables: Useful environment variables:
- `METERPRETER` - Filter the test suite for specific Meterpreter instances, example: `METERPRETER=java` - `METERPRETER` - Filter the test suite for specific Meterpreter instances, example: `METERPRETER=java`
@ -33,6 +33,20 @@ SPEC_OPTS='--tag acceptance' METERPRETER=php METERPRETER_MODULE_TEST=test/unix b
$env:SPEC_OPTS='--tag acceptance'; $env:SPEC_HELPER_LOAD_METASPLOIT=$false; $env:METERPRETER = 'php'; bundle exec rspec './spec/acceptance/meterpreter_spec.rb' $env:SPEC_OPTS='--tag acceptance'; $env:SPEC_HELPER_LOAD_METASPLOIT=$false; $env:METERPRETER = 'php'; bundle exec rspec './spec/acceptance/meterpreter_spec.rb'
``` ```
### Postgres
Run a target:
```
docker run -it --rm --publish 127.0.0.1:9000:5432 -e POSTGRES_PASSWORD=password postgres:14
```
Run the test suite:
```
POSTGRES_RPORT=9000 SPEC_HELPER_LOAD_METASPLOIT=false bundle exec rspec ./spec/acceptance/postgres_spec.rb
```
#### Allure reports #### Allure reports
Generate allure reports locally: Generate allure reports locally:

View File

@ -0,0 +1,373 @@
require 'acceptance_spec_helper'
RSpec.describe 'Postgres sessions and postgres modules' do
include_context 'wait_for_expect'
TESTS = {
postgres: {
target: {
session_module: "auxiliary/scanner/postgres/postgres_login",
type: 'PostgreSQL',
platforms: [:linux, :osx, :windows],
datastore: {
global: {},
module: {
username: ENV.fetch('POSTGRES_USERNAME', 'postgres'),
password: ENV.fetch('POSTGRES_PASSWORD', 'password'),
rhost: ENV.fetch('POSTGRES_RHOST', '127.0.0.1'),
rport: ENV.fetch('POSTGRES_RPORT', '5432'),
}
}
},
module_tests: [
{
name: "post/test/postgres",
platforms: [:linux, :osx, :windows],
targets: [:session],
skipped: false,
},
{
name: "auxiliary/scanner/postgres/postgres_hashdump",
platforms: [:linux, :osx, :windows],
targets: [:session, :rhost],
skipped: false,
lines: {
all: {
required: [
" Username Hash",
" -------- ----",
# postgres SCRAM-SHA-256$4096:UfTJGaMUW+DtXay1UUD+zA==$0C01mPHaruGTqKJFt5qdITvM+nwLsCgxukO3MIbKugU=:iNBXVE5Vqnoa+dGhmEGMQ0cy+nNXDOzg0F3YNcrtRyE=
/ postgres \w+/
]
},
}
},
{
name: "auxiliary/scanner/postgres/postgres_version",
platforms: [:linux, :osx, :windows],
targets: [:session, :rhost],
skipped: false,
lines: {
all: {
required: [
/Version PostgreSQL \d+.\d+/
]
},
}
},
{
name: "auxiliary/admin/postgres/postgres_readfile",
platforms: [:linux],
targets: [:session, :rhost],
skipped: false,
lines: {
all: {
# Module reads /etc/passwd by default:
required: [
/root:x:\d+:\d+:root:/,
/postgres:x:\d+:\d+::/
]
},
}
},
{
name: "auxiliary/admin/postgres/postgres_sql",
platforms: [:linux, :osx, :windows],
targets: [:session, :rhost],
skipped: false,
lines: {
all: {
required: [
# Default module query
"Query Text: 'select version()'",
# Result
/PostgreSQL \d+.\d+/,
]
},
}
}
]
}
}
TEST_ENVIRONMENT = AllureRspec.configuration.environment_properties
let_it_be(:current_platform) { Acceptance::Meterpreter::current_platform }
# Driver instance, keeps track of all open processes/payloads/etc, so they can be closed cleanly
let_it_be(:driver) do
driver = Acceptance::ConsoleDriver.new
driver
end
# Opens a test console with the test loadpath specified
# @!attribute [r] console
# @return [Acceptance::Console]
let_it_be(:console) do
console = driver.open_console
# Load the test modules
console.sendline('loadpath test/modules')
console.recvuntil(/Loaded \d+ modules:[^\n]*\n/)
console.recvuntil(/\d+ auxiliary modules[^\n]*\n/)
console.recvuntil(/\d+ exploit modules[^\n]*\n/)
console.recvuntil(/\d+ post modules[^\n]*\n/)
console.recvuntil(Acceptance::Console.prompt)
# Read the remaining console
# console.sendline "quit -y"
# console.recv_available
features = %w[
postgresql_session_type
]
features.each do |feature|
console.sendline("features set #{feature} true")
console.recvuntil(Acceptance::Console.prompt)
end
console
end
# Run the given block in a 'test harness' which will handle all of the boilerplate for asserting module results, cleanup, and artifact tracking
# This doesn't happen in a before/after block to ensure that allure's report generation is correctly attached to the correct test scope
def with_test_harness(module_test)
begin
replication_commands = []
known_failures = module_test.dig(:lines, :all, :known_failures) || []
known_failures += module_test.dig(:lines, current_platform, :known_failures) || []
known_failures = known_failures.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }
required_lines = module_test.dig(:lines, :all, :required) || []
required_lines += module_test.dig(:lines, current_platform, :required) || []
required_lines = required_lines.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }
yield replication_commands
# XXX: When debugging failed tests, you can enter into an interactive msfconsole prompt with:
# console.interact
# Expect the test module to complete
module_type = module_test[:name].split('/').first
test_result = console.recvuntil("#{module_type.capitalize} module execution completed")
# Ensure there are no failures, and assert tests are complete
aggregate_failures("#{target.type} target and passes the #{module_test[:name].inspect} tests") do
# Skip any ignored lines from the validation input
validated_lines = test_result.lines.reject do |line|
is_acceptable = known_failures.any? do |acceptable_failure|
is_matching_line = is_matching_line.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value)
is_matching_line &&
acceptable_failure.if?(test_environment)
end || line.match?(/Passed: \d+; Failed: \d+/)
is_acceptable
end
validated_lines.each do |test_line|
test_line = Acceptance::Meterpreter.uncolorize(test_line)
expect(test_line).to_not include('FAILED', '[-] FAILED', '[-] Exception', '[-] '), "Unexpected error: #{test_line}"
end
# Assert all expected lines are present
required_lines.each do |required|
next unless required.if?(test_environment)
if required.value.is_a?(Regexp)
expect(test_result).to match(required.value)
else
expect(test_result).to include(required.value)
end
end
# Assert all ignored lines are present, if they are not present - they should be removed from
# the calling config
known_failures.each do |acceptable_failure|
next if acceptable_failure.flaky?(test_environment)
next unless acceptable_failure.if?(test_environment)
expect(test_result).to include(acceptable_failure.value)
end
end
rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e
test_run_error = e
end
# Test cleanup. We intentionally omit cleanup from an `after(:each)` to ensure the allure attachments are
# still generated if the session dies in a weird way etc
console_reset_error = nil
current_console_data = console.all_data
begin
console.reset
rescue => e
console_reset_error = e
Allure.add_attachment(
name: 'console.reset failure information',
source: "Error: #{e.class} - #{e.message}\n#{(e.backtrace || []).join("\n")}",
type: Allure::ContentType::TXT
)
end
target_configuration_details = target.as_readable_text(
default_global_datastore: default_global_datastore,
default_module_datastore: default_module_datastore
)
replication_steps = <<~EOF
## Load test modules
loadpath test/modules
#{target_configuration_details}
## Replication commands
#{replication_commands.empty? ? 'no additional commands run' : replication_commands.join("\n")}
EOF
Allure.add_attachment(
name: 'payload configuration and replication',
source: replication_steps,
type: Allure::ContentType::TXT
)
Allure.add_attachment(
name: 'console data',
source: current_console_data,
type: Allure::ContentType::TXT
)
test_assertions = JSON.pretty_generate(
{
required_lines: required_lines.map(&:to_h),
known_failures: known_failures.map(&:to_h),
}
)
Allure.add_attachment(
name: 'test assertions',
source: test_assertions,
type: Allure::ContentType::TXT
)
raise test_run_error if test_run_error
raise console_reset_error if console_reset_error
end
TESTS.each do |runtime_name, test_config|
runtime_name = "#{runtime_name}#{ENV.fetch('RUNTIME_VERSION', '')}"
describe "#{Acceptance::Meterpreter.current_platform}/#{runtime_name}", focus: test_config[:focus] do
test_config[:module_tests].each do |module_test|
describe(
module_test[:name],
if: (
Acceptance::Meterpreter.supported_platform?(module_test)
)
) do
let(:target) { Acceptance::Target.new(test_config[:target]) }
let(:default_global_datastore) do
{
}
end
let(:test_environment) { TEST_ENVIRONMENT }
let(:default_module_datastore) do
{
lhost: '127.0.0.1'
}
end
# The shared session id that will be reused across the test run
let(:session_id) do
console.sendline "use #{target.session_module}"
console.recvuntil(Acceptance::Console.prompt)
# Set global options
console.sendline target.setg_commands(default_global_datastore: default_global_datastore)
console.recvuntil(Acceptance::Console.prompt)
console.sendline target.run_command(default_module_datastore: { PASS_FILE: nil, USER_FILE: nil, CreateSession: true })
session_id = nil
# Wait for the session to open, or break early if the payload is detected as dead
wait_for_expect do
session_opened_matcher = /#{target.type} session (\d+) opened[^\n]*\n/
session_message = ''
begin
session_message = console.recvuntil(session_opened_matcher, timeout: 1)
rescue Acceptance::ChildProcessRecvError
# noop
end
session_id = session_message[session_opened_matcher, 1]
expect(session_id).to_not be_nil
end
session_id
end
before :each do |example|
next unless example.respond_to?(:parameter)
# Add the test environment metadata to the rspec example instance - so it appears in the final allure report UI
test_environment.each do |key, value|
example.parameter(key, value)
end
end
after :all do
driver.close_payloads
console.reset
end
context "when targeting a session", if: module_test[:targets].include?(:session) do
it(
"#{Acceptance::Meterpreter.current_platform}/#{runtime_name} session opens and passes the #{module_test[:name].inspect} tests"
) do
with_test_harness(module_test) do |replication_commands|
# 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
expect(session_id).to_not(be_nil, proc do
"There should be a session present"
end)
use_module = "use #{module_test[:name]}"
run_module = "run session=#{session_id} Verbose=true"
replication_commands << use_module
console.sendline(use_module)
console.recvuntil(Acceptance::Console.prompt)
replication_commands << run_module
console.sendline(run_module)
# Assertions will happen after this block ends
end
end
end
context "when targeting an rhost", if: module_test[:targets].include?(:rhost) do
it(
"#{Acceptance::Meterpreter.current_platform}/#{runtime_name} rhost opens and passes the #{module_test[:name].inspect} tests"
) do
with_test_harness(module_test) do |replication_commands|
use_module = "use #{module_test[:name]}"
run_module = "run #{target.datastore_options(default_module_datastore: default_module_datastore)} Verbose=true"
replication_commands << use_module
console.sendline(use_module)
console.recvuntil(Acceptance::Console.prompt)
replication_commands << run_module
console.sendline(run_module)
# Assertions will happen after this block ends
end
end
end
end
end
end
end
end

View File

@ -21,6 +21,7 @@ AllureRspec.configure do |config|
environment_properties[:meterpreter_runtime_version] = "#{meterpreter_name}#{meterpreter_runtime_version}" environment_properties[:meterpreter_runtime_version] = "#{meterpreter_name}#{meterpreter_runtime_version}"
end end
end end
environment_properties[:runtime_version] = ENV['RUNTIME_VERSION']
config.environment_properties = environment_properties.compact config.environment_properties = environment_properties.compact
end end

View File

@ -499,7 +499,7 @@ module Acceptance
end end
def close def close
close_processes(@payload_processes + [console]) close_processes(@payload_processes + [@console])
end end
def self.finalizer_proc_for(instance) def self.finalizer_proc_for(instance)

View File

@ -19,7 +19,7 @@ module Acceptance::Meterpreter
], ],
module_tests: [ module_tests: [
{ {
name: "test/services", name: "post/test/services",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -74,7 +74,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/cmd_exec", name: "post/test/cmd_exec",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -90,7 +90,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/extapi", name: "post/test/extapi",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -106,7 +106,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/file", name: "post/test/file",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -124,7 +124,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/get_env", name: "post/test/get_env",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -140,7 +140,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/meterpreter", name: "post/test/meterpreter",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -156,7 +156,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun", name: "post/test/railgun",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -172,7 +172,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun_reverse_lookups", name: "post/test/railgun_reverse_lookups",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -188,7 +188,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/registry", name: "post/test/registry",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -223,7 +223,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/search", name: "post/test/search",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -239,7 +239,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/unix", name: "post/test/unix",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,

View File

@ -38,7 +38,7 @@ module Acceptance::Meterpreter
], ],
module_tests: [ module_tests: [
{ {
name: "test/services", name: "post/test/services",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -70,7 +70,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/cmd_exec", name: "post/test/cmd_exec",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -96,7 +96,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/extapi", name: "post/test/extapi",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -122,7 +122,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/file", name: "post/test/file",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -148,7 +148,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/get_env", name: "post/test/get_env",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -174,7 +174,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/meterpreter", name: "post/test/meterpreter",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -203,7 +203,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun", name: "post/test/railgun",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -229,7 +229,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun_reverse_lookups", name: "post/test/railgun_reverse_lookups",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,
@ -255,7 +255,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/registry", name: "post/test/registry",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -287,7 +287,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/search", name: "post/test/search",
platforms: [ platforms: [
:linux, :linux,
[ [
@ -319,7 +319,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/unix", name: "post/test/unix",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,

View File

@ -19,7 +19,7 @@ module Acceptance::Meterpreter
], ],
module_tests: [ module_tests: [
{ {
name: "test/services", name: "post/test/services",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -61,7 +61,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/cmd_exec", name: "post/test/cmd_exec",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -79,7 +79,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/extapi", name: "post/test/extapi",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -95,7 +95,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/file", name: "post/test/file",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -119,7 +119,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/get_env", name: "post/test/get_env",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -135,7 +135,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/meterpreter", name: "post/test/meterpreter",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -153,7 +153,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun", name: "post/test/railgun",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -169,7 +169,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun_reverse_lookups", name: "post/test/railgun_reverse_lookups",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -185,7 +185,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/registry", name: "post/test/registry",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -227,7 +227,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/search", name: "post/test/search",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -243,7 +243,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/unix", name: "post/test/unix",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,

View File

@ -20,7 +20,7 @@ module Acceptance::Meterpreter
], ],
module_tests: [ module_tests: [
{ {
name: "test/services", name: "post/test/services",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -73,7 +73,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/cmd_exec", name: "post/test/cmd_exec",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -89,7 +89,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/extapi", name: "post/test/extapi",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -110,7 +110,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/file", name: "post/test/file",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -126,7 +126,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/get_env", name: "post/test/get_env",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -142,7 +142,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/meterpreter", name: "post/test/meterpreter",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -160,7 +160,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun", name: "post/test/railgun",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -176,7 +176,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun_reverse_lookups", name: "post/test/railgun_reverse_lookups",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -192,7 +192,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/registry", name: "post/test/registry",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -224,7 +224,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/search", name: "post/test/search",
platforms: [:linux, :osx, :windows], platforms: [:linux, :osx, :windows],
skipped: false, skipped: false,
lines: { lines: {
@ -240,7 +240,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/unix", name: "post/test/unix",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,

View File

@ -22,7 +22,7 @@ module Acceptance::Meterpreter
], ],
module_tests: [ module_tests: [
{ {
name: "test/services", name: "post/test/services",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -54,7 +54,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/cmd_exec", name: "post/test/cmd_exec",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -86,7 +86,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/extapi", name: "post/test/extapi",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -118,7 +118,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/file", name: "post/test/file",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -150,7 +150,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/get_env", name: "post/test/get_env",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -182,7 +182,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/meterpreter", name: "post/test/meterpreter",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -214,7 +214,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun", name: "post/test/railgun",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -246,7 +246,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/railgun_reverse_lookups", name: "post/test/railgun_reverse_lookups",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -278,7 +278,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/registry", name: "post/test/registry",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -310,7 +310,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/search", name: "post/test/search",
platforms: [ platforms: [
[ [
:linux, :linux,
@ -342,7 +342,7 @@ module Acceptance::Meterpreter
} }
}, },
{ {
name: "test/unix", name: "post/test/unix",
platforms: [ platforms: [
:linux, :linux,
:osx, :osx,

View File

@ -0,0 +1,65 @@
module Acceptance
###
# Stores the data for a target. These credentials can be used to create a sesion, or run a module against
###
class Target
attr_reader :session_module, :type, :datastore
def initialize(options)
@type = options.fetch(:type)
@session_module = options.fetch(:session_module)
@datastore = options.fetch(:datastore)
end
def [](k)
options[k]
end
# @param [Hash] default_global_datastore
# @return [String] The setg commands for setting the global datastore
def setg_commands(default_global_datastore: {})
commands = []
# Ensure the global framework datastore is always clear
commands << "irb -e '(self.respond_to?(:framework) ? framework : self).datastore.user_defined.clear'"
# Call setg
global_datastore = default_global_datastore.merge(@datastore[:global])
global_datastore.each do |key, value|
commands << "setg #{key} #{value}"
end
commands.join("\n")
end
# @param [Hash] default_module_datastore
# @return [String] The datastore options string
def datastore_options(default_module_datastore: {})
module_datastore = default_module_datastore.merge(@datastore[:module])
module_options = module_datastore.map do |key, value|
"#{key}=#{value}"
end
module_options.join(' ')
end
# @param [Hash] default_module_datastore
# @return [String] The command which can be used on msfconsole to generate the payload
def run_command(default_module_datastore: {})
"run #{datastore_options(default_module_datastore: default_module_datastore)}"
end
# @param [Hash] default_global_datastore
# @param [Hash] default_module_datastore
# @return [String] A human readable representation of the payload configuration object
def as_readable_text(default_global_datastore: {}, default_module_datastore: {})
<<~EOF
## Session module
use #{session_module}
## Set global datastore
#{setg_commands(default_global_datastore: default_global_datastore)}
## Run command
#{run_command(default_module_datastore: default_module_datastore)}
EOF
end
end
end

View File

@ -36,7 +36,7 @@ class MetasploitModule < Msf::Post
end end
def select_available_modules def select_available_modules
session_platform = Msf::Module::Platform.find_platform(session.platform) session_platform = session.platform ? Msf::Module::Platform.find_platform(session.platform) : nil
session_type = session.type session_type = session.type
module_results = [] module_results = []

View File

@ -0,0 +1,78 @@
require 'rex/post/meterpreter/extensions/stdapi/command_ids'
require 'rex'
lib = File.join(Msf::Config.install_root, "test", "lib")
$LOAD_PATH.push(lib) unless $LOAD_PATH.include?(lib)
require 'module_test'
class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
include Msf::ModuleTest::PostTestFileSystem
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Testing Postgres sessions work',
'Description' => %q{ This module will test the postgres sessions work },
'License' => MSF_LICENSE,
'Author' => [ 'alanfoster'],
'Platform' => all_platforms,
'SessionTypes' => [ 'postgresql' ]
)
)
end
def setup
super
end
def cleanup
super
end
def test_console_query
it "should return a version" do
stdout = with_mocked_console(session) { |console| console.run_single("query 'select version();'") }
ret = true
ret &&= stdout.buf.match?(/PostgreSQL \d+.\d+/)
ret
end
end
def test_console_help
it "should support the help command" do
stdout = with_mocked_console(session) { |console| console.run_single("help") }
ret = true
ret &&= stdout.buf.include?('Core Commands')
ret &&= stdout.buf.include?('PostgreSQL Client Commands')
ret
end
end
private
def all_platforms
Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase }
end
# Wrap the console with a mocked stdin/stdout for testing purposes. This ensures the console
# will not write the real stdout, and the contents can be verified in the test
# @param [Session] session
# @return [Rex::Ui::Text::Output::Buffer] the stdout buffer
def with_mocked_console(session)
old_input = session.console.input
old_output = session.console.output
mock_input = Rex::Ui::Text::Input.new
mock_output = Rex::Ui::Text::Output::Buffer.new
session.console.init_ui(mock_input, mock_output)
yield session.console
mock_output
ensure
session.console.init_ui(old_input, old_output)
end
end