Skip to content

Commit 0df07d7

Browse files
authored
Land rapid7#19390, Adds support to test custom payload branches
2 parents 2dd06c3 + 63ac229 commit 0df07d7

File tree

9 files changed

+225
-5
lines changed

9 files changed

+225
-5
lines changed

.github/workflows/acceptance.yml

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ permissions:
2222
statuses: none
2323

2424
on:
25+
workflow_dispatch:
26+
inputs:
27+
metasploitPayloadsCommit:
28+
description: 'metasploit-payloads branch would like to test'
29+
required: true
30+
default: 'master'
31+
mettleCommit:
32+
description: 'mettle branch you would like to test'
33+
required: true
34+
default: 'master'
2535
push:
2636
branches-ignore:
2737
- gh-pages
@@ -81,10 +91,12 @@ jobs:
8191

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

84-
timeout-minutes: 25
94+
timeout-minutes: 50
8595

8696
env:
8797
RAILS_ENV: test
98+
metasploitPayloadsCommit: ${{ github.event.inputs.metasploitPayloadsCommit || 'master' }}
99+
mettleCommit: ${{ github.event.inputs.mettleCommit|| 'master' }}
88100
HOST_RUNNER_IMAGE: ${{ matrix.os }}
89101
METERPRETER: ${{ matrix.meterpreter.name }}
90102
METERPRETER_RUNTIME_VERSION: ${{ matrix.meterpreter.runtime_version }}
@@ -129,8 +141,58 @@ jobs:
129141
dir %WINDIR%
130142
type %WINDIR%\\system32\\drivers\\etc\\hosts
131143
132-
- name: Checkout code
144+
# The job checkout structure is:
145+
# .
146+
# ├── metasploit-framework
147+
# └── metasploit-payloads (Only if the "payload-testing-branch" GitHub label is applied)
148+
# └── mettle (Only if the "payload-testing-mettle-branch" GitHub label is applied)
149+
150+
- name: Install Docker - macOS
151+
if: ${{ ( matrix.meterpreter.name == 'java') && (runner.os == 'macos' ) && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
152+
run: |
153+
brew install docker
154+
colima delete
155+
colima start --arch x86_64
156+
157+
- name: Checkout mettle
158+
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
159+
uses: actions/checkout@v4
160+
with:
161+
repository: rapid7/mettle
162+
path: mettle
163+
ref: ${{ env.mettleCommit }}
164+
165+
- name: Get mettle version
166+
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
167+
run: |
168+
echo "METTLE_VERSION=$(grep -oh '[0-9].[0-9].[0-9]*' lib/metasploit_payloads/mettle/version.rb)" | tee -a $GITHUB_ENV
169+
working-directory: mettle
170+
171+
- name: Prerequisite mettle gem setup
172+
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
173+
run: |
174+
set -x
175+
ruby -pi.bak -e "gsub(/${{ env.METTLE_VERSION }}/, '${{ env.METTLE_VERSION }}-dev')" lib/metasploit_payloads/mettle/version.rb
176+
working-directory: mettle
177+
178+
- name: Compile mettle payloads
179+
if: ${{ matrix.meterpreter.name == 'mettle' && runner.os != 'macos' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
180+
run: |
181+
docker run --rm=true --tty --volume=$(pwd):/mettle --workdir=/mettle rapid7/build:mettle rake mettle:build mettle:check
182+
rake build
183+
working-directory: mettle
184+
185+
- name: Compile mettle payloads - macOS
186+
if: ${{ matrix.meterpreter.name == 'mettle' && runner.os == 'macos' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
187+
run: |
188+
make TARGET=x86_64-apple-darwin
189+
rake build
190+
working-directory: mettle
191+
192+
- name: Checkout metasploit-framework code
133193
uses: actions/checkout@v4
194+
with:
195+
path: metasploit-framework
134196

135197
- name: Setup Ruby
136198
env:
@@ -140,11 +202,66 @@ jobs:
140202
ruby-version: ${{ matrix.ruby }}
141203
bundler-cache: true
142204
cache-version: 4
205+
working-directory: metasploit-framework
143206
# Github actions with Ruby requires Bundler 2.2.18+
144207
# https://github.com/ruby/setup-ruby/tree/d2b39ad0b52eca07d23f3aa14fdf2a3fcc1f411c#windows
145208
bundler: 2.2.33
146209

147-
- name: acceptance
210+
- name: Move mettle gem into framework
211+
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }}
212+
run: |
213+
cp ./mettle/pkg/metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem ./metasploit-framework
214+
working-directory: metasploit-framework
215+
216+
- name: Install mettle gem
217+
if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'payload-testing-mettle-branch')) }}
218+
run: |
219+
set -x
220+
bundle exec gem install metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem
221+
ruby -pi.bak -e "gsub(/'metasploit_payloads-mettle', '${{ env.METTLE_VERSION }}'/, '\'metasploit_payloads-mettle\', \'${{ env.METTLE_VERSION }}.pre.dev\'')" metasploit-framework.gemspec
222+
bundle config unset deployment
223+
bundle update metasploit_payloads-mettle
224+
bundle install
225+
working-directory: metasploit-framework
226+
227+
- name: Checkout metasploit-payloads
228+
if: contains(github.event.issue.labels.*.name, 'payload-testing-branch')
229+
uses: actions/checkout@v4
230+
with:
231+
repository: rapid7/metasploit-payloads
232+
path: metasploit-payloads
233+
ref: ${{ env.metasploitPayloadsCommit }}
234+
235+
- name: Build Java and Android payloads
236+
if: ${{ (matrix.meterpreter.name == 'java') && (runner.os != 'Windows') && (contains(github.event.issue.labels.*.name, 'payload-testing-branch')) }}
237+
run: |
238+
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"
239+
240+
- name: Build Windows payloads via Visual Studio 2019 Build (Windows)
241+
shell: cmd
242+
if: ${{ (runner.os == 'Windows') && (matrix.os == 'windows-2019') && (contains(github.event.issue.labels.*.name, 'payload-testing-branch')) }}
243+
run: |
244+
cd c/meterpreter
245+
git submodule init && git submodule update
246+
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" && make.bat
247+
working-directory: metasploit-payloads
248+
249+
- name: Build Windows payloads via Visual Studio 2022 Build (Windows)
250+
shell: cmd
251+
if: ${{ (runner.os == 'Windows') && (matrix.os == 'windows-2022') && (contains(github.event.issue.labels.*.name, 'payload-testing-branch'))}}
252+
run: |
253+
cd c/meterpreter
254+
git submodule init && git submodule update
255+
make.bat
256+
working-directory: metasploit-payloads
257+
258+
- name: Build PHP, Python and Windows payloads
259+
if: ${{ ((matrix.meterpreter.name == 'php') || (matrix.meterpreter.name == 'python') || (runner.os == 'Windows')) && (contains(github.event.issue.labels.*.name, 'payload-testing-branch'))}}
260+
run: |
261+
make install-php install-python install-windows
262+
working-directory: metasploit-payloads
263+
264+
- name: Acceptance
148265
env:
149266
SPEC_HELPER_LOAD_METASPLOIT: false
150267
SPEC_OPTS: "--tag acceptance --require acceptance_spec_helper.rb --color --format documentation --format AllureRspec::RSpecFormatter"
@@ -157,14 +274,15 @@ jobs:
157274
# Additionally - flakey tests should be fixed or marked as flakey instead of silently retried
158275
run: |
159276
bundle exec rspec spec/acceptance/meterpreter_spec.rb
277+
working-directory: metasploit-framework
160278

161279
- name: Archive results
162280
if: always()
163281
uses: actions/upload-artifact@v4
164282
with:
165283
# Provide a unique artifact for each matrix os, otherwise race conditions can lead to corrupt zips
166284
name: raw-data-${{ matrix.meterpreter.name }}-${{ matrix.meterpreter.runtime_version }}-${{ matrix.os }}
167-
path: tmp/allure-raw-data
285+
path: metasploit-framework/tmp/allure-raw-data
168286

169287
# Generate a final report from the previous test results
170288
report:

data/cmd_exec/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## Setup
2+
3+
This contains setup steps used for acceptance testing of the `cmd_exec` API. We will make use of the gcc docker image to
4+
build out the C binaries to then be uploaded to the host machine, so they can be used as part of the `cmd_exec`
5+
create process API.
6+
7+
This directory contains:
8+
- C executable `show_args.c`
9+
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
10+
those args back to the user.
11+
12+
- Makefile to build the binaries `makefile.mk`
13+
This file is used to create the binaries for both Windows and Linux that the docker command below will make use of.
14+
15+
- Precompiled binaries for Windows
16+
- `show_args.exe`
17+
18+
- Precompiled binaries for Linux and Mettle
19+
- `show_args`
20+
21+
- Precompiled binaries for macOS
22+
- `show_args_macos`
23+
24+
## Compile binaries locally
25+
26+
We make use of gcc for this: https://hub.docker.com/_/gcc
27+
28+
- Run:
29+
```shell
30+
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"
31+
```
32+
33+
You will need to compile the OSX payload separately on an OSX machine, Docker is not supported.

data/cmd_exec/makefile.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
all: show_args_linux show_args_windows
2+
show_args_linux: show_args.c
3+
cc show_args.c -o show_args_linux
4+
show_args_windows: show_args.c
5+
x86_64-w64-mingw32-gcc show_args.c -o show_args.exe

data/cmd_exec/show_args.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
int printf(const char *format, ...);
2+
3+
int main(int argc, char *argv[]) {
4+
for (int i = 0; i < argc; i++) {
5+
printf("%s\n", argv[i]);
6+
}
7+
}

data/cmd_exec/show_args.exe

119 KB
Binary file not shown.

data/cmd_exec/show_args_linux

16 KB
Binary file not shown.

data/cmd_exec/show_args_osx

8.25 KB
Binary file not shown.

spec/acceptance/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ SPEC_OPTS='--tag acceptance' SPEC_HELPER_LOAD_METASPLOIT=false bundle exec rspec
2727
```
2828

2929
Run a specific Meterpreter/module test Unix / Windows:
30+
31+
Bash command:
32+
```
33+
SPEC_OPTS='--tag acceptance' METERPRETER=php METERPRETER_MODULE_TEST=post/test/unix bundle exec rspec './spec/acceptance/meterpreter_spec.rb'
3034
```
31-
SPEC_OPTS='--tag acceptance' METERPRETER=php METERPRETER_MODULE_TEST=test/unix bundle exec rspec './spec/acceptance/meterpreter_spec.rb'
3235

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

test/modules/post/test/cmd_exec.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,47 @@ def initialize(info = {})
2121
)
2222
end
2323

24+
def upload_precompiled_binaries
25+
print_status 'Uploading precompiled binaries'
26+
upload_file(show_args_binary[:path], "data/cmd_exec/#{show_args_binary[:path]}")
27+
if session.platform.eql?('linux') || session.platform.eql?('osx')
28+
chmod(show_args_binary[:path])
29+
end
30+
end
31+
32+
def show_args_binary
33+
if session.platform == 'linux'
34+
{ path: 'show_args_linux', cmd: './show_args_linux' }
35+
elsif session.platform == 'osx'
36+
{ path: 'show_args_osx', cmd: './show_args_osx' }
37+
elsif session.platform == 'windows'
38+
{ path: 'show_args.exe', cmd: 'show_args.exe' }
39+
else
40+
raise "unknown platform #{session.platform}"
41+
end
42+
end
43+
44+
def valid_show_args_response?(output, expected:)
45+
# Handle both unix new lines `\n` and windows `\r\n`
46+
output_lines = output.lines(chomp: true)
47+
# extract the program name and remainder args
48+
output_binary, *output_args = output_lines
49+
50+
# Match the binary name, to support the binary name containig relative or absolute paths, i.e.
51+
# "show_args.exe\r\none\r\ntwo",
52+
match = output_binary.match?(expected[0]) && output_args == expected[1..]
53+
if !match
54+
vprint_status("#{__method__}: expected: #{expected.inspect} - actual: #{output_lines.inspect}")
55+
end
56+
57+
match
58+
end
59+
2460
def test_cmd_exec
2561
# we are inconsistent reporting windows session types
2662
windows_strings = ['windows', 'win']
2763
vprint_status("Starting cmd_exec tests")
64+
upload_precompiled_binaries
2865

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

77+
it 'should execute the show_args binary a single string' do
78+
# TODO: Fix this functionality
79+
if session.type.eql?('meterpreter') && session.arch.eql?('python')
80+
vprint_status("test skipped for Python Meterpreter - functionality not correct")
81+
next true
82+
end
83+
output = cmd_exec("#{show_args_binary[:cmd]} one two")
84+
valid_show_args_response?(output, expected: [show_args_binary[:path], 'one', 'two'])
85+
end
86+
87+
it 'should execute the show_args binary with the binary name and args provided separately' do
88+
output = cmd_exec(show_args_binary[:cmd], "one two")
89+
valid_show_args_response?(output, expected: [show_args_binary[:path], 'one', 'two'])
90+
end
91+
4092
# Powershell supports this, but not windows meterpreter (unsure about windows shell)
4193
if not windows_strings.include? session.platform or session.type.eql? 'powershell'
4294
it "should return the full response after sleeping" do

0 commit comments

Comments
 (0)