Skip to content

Commit

Permalink
Adds support to test custom payload branches
Browse files Browse the repository at this point in the history
  • Loading branch information
cgranleese-r7 committed Aug 21, 2024
1 parent 8fa437e commit 90ac288
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 5 deletions.
132 changes: 128 additions & 4 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ permissions:
statuses: none

on:
workflow_dispatch:
inputs:
metasploitPayloadsCommit:
description: 'metasploit-payloads branch would like to test'
required: true
default: 'master'
mettleCommit:
description: 'mettle branch you would like to test'
required: true
default: 'master'
push:
branches-ignore:
- gh-pages
Expand Down Expand Up @@ -81,10 +91,12 @@ jobs:

runs-on: ${{ matrix.os }}

timeout-minutes: 25
timeout-minutes: 50

env:
RAILS_ENV: test
metasploitPayloadsCommit: ${{ github.event.inputs.metasploitPayloadsCommit || 'master' }}
mettleCommit: ${{ github.event.inputs.mettleCommit|| 'master' }}
HOST_RUNNER_IMAGE: ${{ matrix.os }}
METERPRETER: ${{ matrix.meterpreter.name }}
METERPRETER_RUNTIME_VERSION: ${{ matrix.meterpreter.runtime_version }}
Expand Down Expand Up @@ -129,8 +141,58 @@ jobs:
dir %WINDIR%
type %WINDIR%\\system32\\drivers\\etc\\hosts
- name: Checkout code
# The job checkout structure is:
# .
# ├── metasploit-framework
# └── metasploit-payloads (Only if the "payload-testing-branch" GitHub label is applied)
# └── mettle (Only if the "payload-testing-mettle-branch" GitHub label is applied)

- name: Install Docker - macOS
if: ${{ ( matrix.meterpreter.name == 'java') && (runner.os == 'macos' ) && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
brew install docker
colima delete
colima start --arch x86_64
- name: Checkout mettle
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
uses: actions/checkout@v4
with:
repository: rapid7/mettle
path: mettle
ref: ${{ env.mettleCommit }}

- name: Get mettle version
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
echo "METTLE_VERSION=$(grep -oh '[0-9].[0-9].[0-9]*' lib/metasploit_payloads/mettle/version.rb)" | tee -a $GITHUB_ENV
working-directory: mettle

- name: Prerequisite mettle gem setup
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
set -x
ruby -pi.bak -e "gsub(/${{ env.METTLE_VERSION }}/, '${{ env.METTLE_VERSION }}-dev')" lib/metasploit_payloads/mettle/version.rb
working-directory: mettle

- name: Compile mettle payloads
if: ${{ matrix.meterpreter.name == 'mettle' && runner.os != 'macos' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
docker run --rm=true --tty --volume=$(pwd):/mettle --workdir=/mettle rapid7/build:mettle rake mettle:build mettle:check
rake build
working-directory: mettle

- name: Compile mettle payloads - macOS
if: ${{ matrix.meterpreter.name == 'mettle' && runner.os == 'macos' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
make TARGET=x86_64-apple-darwin
rake build
working-directory: mettle

- name: Checkout metasploit-framework code
uses: actions/checkout@v4
with:
path: metasploit-framework

- name: Setup Ruby
env:
Expand All @@ -140,11 +202,72 @@ jobs:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
cache-version: 4
working-directory: metasploit-framework
# Github actions with Ruby requires Bundler 2.2.18+
# https://github.com/ruby/setup-ruby/tree/d2b39ad0b52eca07d23f3aa14fdf2a3fcc1f411c#windows
bundler: 2.2.33

- name: acceptance
- name: Move mettle gem into framework
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }}
run: |
cp ./mettle/pkg/metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem ./metasploit-framework
working-directory: metasploit-framework

- name: Move mettle gem into framework - macOS
if: ${{ matrix.meterpreter.name == 'mettle' && runner.os != 'macos' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
cp /home/runner/work/metasploit-framework/metasploit-framework/mettle/pkg/metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem /home/runner/work/metasploit-framework/metasploit-framework/metasploit-framework
working-directory: metasploit-framework

- name: Install mettle gem
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
run: |
set -x
bundle exec gem install metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem
ruby -pi.bak -e "gsub(/'metasploit_payloads-mettle', '${{ env.METTLE_VERSION }}'/, '\'metasploit_payloads-mettle\', \'${{ env.METTLE_VERSION }}.pre.dev\'')" metasploit-framework.gemspec
bundle config unset deployment
bundle update metasploit_payloads-mettle
bundle install
working-directory: metasploit-framework

- name: Checkout metasploit-payloads
if: contains(github.event.issue.labels.*.name, 'payload-testing-branch')
uses: actions/checkout@v4
with:
repository: rapid7/metasploit-payloads
path: metasploit-payloads
ref: ${{ env.metasploitPayloadsCommit }}

- name: Build Java and Android payloads
if: ${{ (matrix.meterpreter.name == 'java') && (runner.os != 'Windows') && (contains(github.event.issue.labels.*.name, 'payload-testing-branch')) }}
run: |
docker run --rm -w "$(pwd)" -v "$(pwd):$(pwd)" rapid7/msf-ubuntu-x64-meterpreter:latest /bin/bash -c "cd metasploit-payloads/java && make clean && make android && mvn -P deploy package"
- name: Build Windows payloads via Visual Studio 2019 Build (Windows)
shell: cmd
if: ${{ (runner.os == 'Windows') && (matrix.os == 'windows-2019') && (contains(github.event.issue.labels.*.name, 'payload-testing-branch')) }}
run: |
cd c/meterpreter
git submodule init && git submodule update
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" && make.bat
working-directory: metasploit-payloads

- name: Build Windows payloads via Visual Studio 2022 Build (Windows)
shell: cmd
if: ${{ (runner.os == 'Windows') && (matrix.os == 'windows-2022') && (contains(github.event.issue.labels.*.name, 'payload-testing-branch'))}}
run: |
cd c/meterpreter
git submodule init && git submodule update
make.bat
working-directory: metasploit-payloads

- name: Build PHP, Python and Windows payloads
if: ${{ ((matrix.meterpreter.name == 'php') || (matrix.meterpreter.name == 'python') || (runner.os == 'Windows')) && (contains(github.event.issue.labels.*.name, 'payload-testing-branch'))}}
run: |
make install-php install-python install-windows
working-directory: metasploit-payloads

- name: Acceptance
env:
SPEC_HELPER_LOAD_METASPLOIT: false
SPEC_OPTS: "--tag acceptance --require acceptance_spec_helper.rb --color --format documentation --format AllureRspec::RSpecFormatter"
Expand All @@ -157,14 +280,15 @@ jobs:
# Additionally - flakey tests should be fixed or marked as flakey instead of silently retried
run: |
bundle exec rspec spec/acceptance/meterpreter_spec.rb
working-directory: metasploit-framework

- 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: raw-data-${{ matrix.meterpreter.name }}-${{ matrix.meterpreter.runtime_version }}-${{ matrix.os }}
path: tmp/allure-raw-data
path: metasploit-framework/tmp/allure-raw-data

# Generate a final report from the previous test results
report:
Expand Down
33 changes: 33 additions & 0 deletions data/cmd_exec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Setup

This contains setup steps used for acceptance testing of the `cmd_exec` API. We will make use of the gcc docker image to
build out the C binaries to then be uploaded to the host machine, so they can be used as part of the `cmd_exec`
create process API.

This directory contains:
- C executable `show_args.c`
This file is used as part of the `cmd_exec` testing as it requires a file to take args, then loop over them and output
those args back to the user.

- Makefile to build the binaries `makefile.mk`
This file is used to create the binaries for both Windows and Linux that the docker command below will make use of.

- Precompiled binaries for Windows
- `show_args.exe`

- Precompiled binaries for Linux and Mettle
- `show_args`

- Precompiled binaries for macOS
- `show_args_macos`

## Compile binaries locally

We make use of gcc for this: https://hub.docker.com/_/gcc

- Run:
```shell
docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp gcc:11.4.0 /bin/bash -c "apt update && apt install -y gcc-mingw-w64 && make all -f makefile.mk"
```

You will need to compile the OSX payload separately on an OSX machine, Docker is not supported.
5 changes: 5 additions & 0 deletions data/cmd_exec/makefile.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
all: show_args_linux show_args_windows
show_args_linux: show_args.c
cc show_args.c -o show_args_linux
show_args_windows: show_args.c
x86_64-w64-mingw32-gcc show_args.c -o show_args.exe
7 changes: 7 additions & 0 deletions data/cmd_exec/show_args.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
int printf(const char *format, ...);

int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
printf("%s\n", argv[i]);
}
}
Binary file added data/cmd_exec/show_args.exe
Binary file not shown.
Binary file added data/cmd_exec/show_args_linux
Binary file not shown.
Binary file added data/cmd_exec/show_args_osx
Binary file not shown.
7 changes: 6 additions & 1 deletion spec/acceptance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ SPEC_OPTS='--tag acceptance' SPEC_HELPER_LOAD_METASPLOIT=false bundle exec rspec
```

Run a specific Meterpreter/module test Unix / Windows:

Bash command:
```
SPEC_OPTS='--tag acceptance' METERPRETER=php METERPRETER_MODULE_TEST=post/test/unix bundle exec rspec './spec/acceptance/meterpreter_spec.rb'
```
SPEC_OPTS='--tag acceptance' METERPRETER=php METERPRETER_MODULE_TEST=test/unix bundle exec rspec './spec/acceptance/meterpreter_spec.rb'

Powershell command:
```
$env:SPEC_OPTS='--tag acceptance'; $env:SPEC_HELPER_LOAD_METASPLOIT=$false; $env:METERPRETER = 'php'; bundle exec rspec './spec/acceptance/meterpreter_spec.rb'
```

Expand Down
52 changes: 52 additions & 0 deletions test/modules/post/test/cmd_exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,47 @@ def initialize(info = {})
)
end

def upload_precompiled_binaries
print_status 'Uploading precompiled binaries'
upload_file(show_args_binary[:path], "data/cmd_exec/#{show_args_binary[:path]}")
if session.platform.eql?('linux') || session.platform.eql?('osx')
chmod(show_args_binary[:path])
end
end

def show_args_binary
if session.platform == 'linux'
{ path: 'show_args_linux', cmd: './show_args_linux' }
elsif session.platform == 'osx'
{ path: 'show_args_osx', cmd: './show_args_osx' }
elsif session.platform == 'windows'
{ path: 'show_args.exe', cmd: 'show_args.exe' }
else
raise "unknown platform #{session.platform}"
end
end

def valid_show_args_response?(output, expected:)
# Handle both unix new lines `\n` and windows `\r\n`
output_lines = output.lines(chomp: true)
# extract the program name and remainder args
output_binary, *output_args = output_lines

# Match the binary name, to support the binary name containig relative or absolute paths, i.e.
# "show_args.exe\r\none\r\ntwo",
match = output_binary.match?(expected[0]) && output_args == expected[1..]
if !match
vprint_status("#{__method__}: expected: #{expected.inspect} - actual: #{output_lines.inspect}")
end

match
end

def test_cmd_exec
# we are inconsistent reporting windows session types
windows_strings = ['windows', 'win']
vprint_status("Starting cmd_exec tests")
upload_precompiled_binaries

it "should return the result of echo" do
test_string = Rex::Text.rand_text_alpha(4)
Expand All @@ -37,6 +74,21 @@ def test_cmd_exec
output == test_string
end

it 'should execute the show_args binary a single string' do
# TODO: Fix this functionality
if session.type.eql?('meterpreter') && session.arch.eql?('python')
vprint_status("test skipped for Python Meterpreter - functionality not correct")
next true
end
output = cmd_exec("#{show_args_binary[:cmd]} one two")
valid_show_args_response?(output, expected: [show_args_binary[:path], 'one', 'two'])
end

it 'should execute the show_args binary with the binary name and args provided separately' do
output = cmd_exec(show_args_binary[:cmd], "one two")
valid_show_args_response?(output, expected: [show_args_binary[:path], 'one', 'two'])
end

# Powershell supports this, but not windows meterpreter (unsure about windows shell)
if not windows_strings.include? session.platform or session.type.eql? 'powershell'
it "should return the full response after sleeping" do
Expand Down

0 comments on commit 90ac288

Please sign in to comment.