diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index cebd411e0c966..970fb2f4f40bc 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -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 @@ -63,17 +73,17 @@ jobs: - { name: python, runtime_version: 3.11 } # Java - - { name: java, runtime_version: 8 } - - { name: java, runtime_version: 21 } +# - { name: java, runtime_version: 8 } +# - { name: java, runtime_version: 21 } # PHP - - { name: php, runtime_version: 5.3 } - - { name: php, runtime_version: 7.4 } - - { name: php, runtime_version: 8.3 } +# - { name: php, runtime_version: 5.3 } +# - { name: php, runtime_version: 7.4 } +# - { name: php, runtime_version: 8.3 } include: # Windows Meterpreter - - { meterpreter: { name: windows_meterpreter }, os: windows-2019 } - - { meterpreter: { name: windows_meterpreter }, os: windows-2022 } +# - { meterpreter: { name: windows_meterpreter }, os: windows-2019 } +# - { meterpreter: { name: windows_meterpreter }, os: windows-2022 } # Mettle - { meterpreter: { name: mettle }, os: macos-12 } @@ -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 }} @@ -96,7 +108,7 @@ jobs: if: runner.os == 'Linux' run: sudo apt-get -y --no-install-recommends install libpcap-dev graphviz - - uses: shivammathur/setup-php@fc14643b0a99ee9db10a3c025a33d76544fa3761 + - uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d if: ${{ matrix.meterpreter.name == 'php' }} with: php-version: ${{ matrix.meterpreter.runtime_version }} @@ -129,8 +141,70 @@ jobs: dir %WINDIR% type %WINDIR%\\system32\\drivers\\etc\\hosts - - name: Checkout code + # The job checkout structure is: + # . + # ├── metasploit-framework + # └── metasploit-payloads (Only if metasploit-payloads-testing-branch is applied) + # └── mettle (Only if mettle-testing-branch is applied) + + # Install Docker if on MACOS + - name: Install Docker - macOS + if: ${{ ( matrix.meterpreter.name == 'java') && (runner.os == 'macos' ) && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }} + run: | + brew install docker + colima delete + colima start --arch x86_64 + + # Checkout mettle + - name: Checkout mettle + if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'mettle-testing-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, 'mettle-testing-branch')) }} + run: | + echo "METTLE_VERSION=$(grep -oh '[0-9].[0-9].[0-9]*' lib/metasploit_payloads/mettle/version.rb)" >> $GITHUB_ENV + working-directory: mettle + + - name: Use mettle version + if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }} + run: | + echo "${{ env.METTLE_VERSION }}" + working-directory: mettle + + # Prerequisite mettle gem setup + - name: Build mettle payloads + if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'mettle-testing-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 + + # Compile mettle payload + - name: Compile mettle payloads + if: ${{ matrix.meterpreter.name == 'mettle' && runner.os != 'macos' && (contains(github.event.issue.labels.*.name, 'mettle-testing-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 + + # Compile mettle payload - macOS + - name: Compile mettle payloads - macOS + if: ${{ matrix.meterpreter.name == 'mettle' && runner.os == 'macos' && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }} + run: | + make TARGET=x86_64-apple-darwin + rake build + working-directory: mettle + + # Checkout metasploit-framework + - name: Checkout metasploit-framework code uses: actions/checkout@v4 + with: + path: metasploit-framework - name: Setup Ruby env: @@ -139,12 +213,84 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - cache-version: 4 + working-directory: metasploit-framework + cache-version: 5 # Github actions with Ruby requires Bundler 2.2.18+ # https://github.com/ruby/setup-ruby/tree/d2b39ad0b52eca07d23f3aa14fdf2a3fcc1f411c#windows bundler: 2.2.33 - - name: acceptance + # Copying mettle gem into framework - macOS + - name: Move mettle gem - macOS + if: ${{ matrix.meterpreter.name == 'mettle' && runner.os == 'macos' && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }} + run: | + cp /Users/runner/work/metasploit-framework/metasploit-framework/mettle/pkg/metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem /Users/runner/work/metasploit-framework/metasploit-framework/metasploit-framework + working-directory: metasploit-framework + + # Copying mettle gem into framework + - name: Move mettle gem + if: ${{ matrix.meterpreter.name == 'mettle' && runner.os != 'macos' && (contains(github.event.issue.labels.*.name, 'mettle-testing-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 + + # Copying mettle gem into framework + - name: Install mettle gem + if: ${{ matrix.meterpreter.name == 'mettle' && (contains(github.event.issue.labels.*.name, 'mettle-testing-branch')) }} + run: | + set -x + bundle exec gem install metasploit_payloads-mettle-${{ env.METTLE_VERSION }}.pre.dev.gem + gem list metasploit_payloads-mettle + ruby -pi.bak -e "gsub(/'metasploit_payloads-mettle', '1.0.31'/, '\'metasploit_payloads-mettle\', \'${{ env.METTLE_VERSION }}.pre.dev\'')" metasploit-framework.gemspec + cat Gemfile.lock + bundle config + bundle config unset deployment + bundle update metasploit_payloads-mettle + bundle install + working-directory: metasploit-framework + + # Checkout metasploit-payloads + - name: Checkout metasploit-payloads + if: contains(github.event.issue.labels.*.name, 'metasploit-payloads-testing-branch') + uses: actions/checkout@v4 + with: + repository: rapid7/metasploit-payloads + path: metasploit-payloads + ref: ${{ env.metasploitPayloadsCommit }} + + # Build Java and Android payloads, Docker command needs to be ran from the directory up from metasploit-payloads + - name: Build Java & Android payloads + if: ${{ (matrix.meterpreter.name == 'java') && (runner.os != 'Windows') && (contains(github.event.issue.labels.*.name, 'metasploit-payloads-testing-branch')) }} + run: | + cd .. + 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" + working-directory: metasploit-payloads + + - 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, 'metasploit-payloads-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, 'metasploit-payloads-testing-branch'))}} + run: | + cd c/meterpreter + git submodule init && git submodule update + make.bat + working-directory: metasploit-payloads + + # Run makefile within 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, 'metasploit-payloads-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" @@ -157,6 +303,7 @@ 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() @@ -164,7 +311,7 @@ jobs: 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: diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml deleted file mode 100644 index ed70f8bb89fcf..0000000000000 --- a/.github/workflows/verify.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Verify - -# 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 - - weekly-dependency-updates - pull_request: - branches-ignore: - - weekly-dependency-updates - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 40 - name: Docker Build - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: docker-compose build - run: | - curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/bin - - /usr/bin/docker-compose build - - test: - runs-on: ${{ matrix.os }} - timeout-minutes: 40 - - services: - postgres: - image: postgres:9.6 - ports: ["5432:5432"] - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - strategy: - fail-fast: true - matrix: - ruby: - - '3.1' - - '3.2' - - '3.3' - - '3.4.0-preview1' - os: - - ubuntu-20.04 - - ubuntu-latest - exclude: - - { os: ubuntu-latest, ruby: '3.0' } - include: - - os: ubuntu-latest - ruby: '3.1' - test_cmd: 'bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" MSF_FEATURE_DEFER_MODULE_LOADS=1' - test_cmd: - - bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" - - bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag ~content" - # Used for testing the remote data service - - bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" REMOTE_DB=1 - - bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag ~content" REMOTE_DB=1 - - env: - RAILS_ENV: test - BUNDLE_WITHOUT: "coverage development pcap" - - name: ${{ matrix.os }} - Ruby ${{ matrix.ruby }} - ${{ matrix.test_cmd }} - 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: - # 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: Create database - run: | - cp config/database.yml.github_actions config/database.yml - bundle exec rake --version - bundle exec rake db:create - bundle exec rake db:migrate - # fail build if db/schema.rb update is not committed - git diff --exit-code db/schema.rb - - - name: ${{ matrix.test_cmd }} - run: | - echo "${CMD}" - bash -c "${CMD}" - env: - CMD: ${{ matrix.test_cmd }} diff --git a/data/cmd_exec/README.md b/data/cmd_exec/README.md new file mode 100644 index 0000000000000..019cb143bdf23 --- /dev/null +++ b/data/cmd_exec/README.md @@ -0,0 +1,31 @@ +## 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" +``` diff --git a/data/cmd_exec/makefile.mk b/data/cmd_exec/makefile.mk new file mode 100644 index 0000000000000..12b98e53427a6 --- /dev/null +++ b/data/cmd_exec/makefile.mk @@ -0,0 +1,5 @@ +all: show_args_linux show_args_windows +show_args_linux: show_args.c + cc show_args.c -o show_args +show_args_windows: show_args.c + x86_64-w64-mingw32-gcc show_args.c -o show_args.exe diff --git a/data/cmd_exec/show_args b/data/cmd_exec/show_args new file mode 100755 index 0000000000000..2e90a22d44cac Binary files /dev/null and b/data/cmd_exec/show_args differ diff --git a/data/cmd_exec/show_args.c b/data/cmd_exec/show_args.c new file mode 100644 index 0000000000000..8822cada48a4a --- /dev/null +++ b/data/cmd_exec/show_args.c @@ -0,0 +1,8 @@ +int printf(const char *format, ...); + +int main(int argc, char *argv[]) { + + for (int i = 0; i < argc; i++) { + printf("%s\n", argv[i]); + } +} diff --git a/data/cmd_exec/show_args.exe b/data/cmd_exec/show_args.exe new file mode 100755 index 0000000000000..05b7a8784cc4c Binary files /dev/null and b/data/cmd_exec/show_args.exe differ diff --git a/data/cmd_exec/show_args_macos b/data/cmd_exec/show_args_macos new file mode 100755 index 0000000000000..059da12f4d2ce Binary files /dev/null and b/data/cmd_exec/show_args_macos differ diff --git a/spec/acceptance/README.md b/spec/acceptance/README.md index 3f19022ffc4ab..f559e9541fd93 100644 --- a/spec/acceptance/README.md +++ b/spec/acceptance/README.md @@ -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' ``` diff --git a/test/modules/post/test/cmd_exec.rb b/test/modules/post/test/cmd_exec.rb index 0b32f5132e363..0cbf6b10ed161 100644 --- a/test/modules/post/test/cmd_exec.rb +++ b/test/modules/post/test/cmd_exec.rb @@ -21,11 +21,32 @@ def initialize(info = {}) ) end + def upload_create_process_precompiled_binaries + print_status 'Uploading precompiled binaries' + if session.platform.eql? 'linux' + upload_file('show_args', 'data/cmd_exec/show_args') + end + + if session.platform.eql? 'windows' + upload_file('show_args.exe', 'data/cmd_exec/show_args.exe') + end + + if session.platform.eql? 'osx' + upload_file('show_args', 'data/cmd_exec/show_args_macos') + end + + if session.platform.eql?('linux') || session.platform.eql?('osx') + chmod('show_args') + end + end + def test_cmd_exec # we are inconsistent reporting windows session types windows_strings = ['windows', 'win'] vprint_status("Starting cmd_exec tests") + upload_create_process_precompiled_binaries + it "should return the result of echo" do test_string = Rex::Text.rand_text_alpha(4) if windows_strings.include? session.platform and session.type.eql? 'meterpreter' @@ -37,6 +58,28 @@ def test_cmd_exec output == test_string end + it 'should make use the show_args binaries' do + if session.platform.eql? 'windows' + output = cmd_exec('./show_args.exe one two') + $stderr.puts output.rstrip + if session.type.eql? 'powershell' + output.rstrip == "#{pwd}\\show_args.exe\r\none\r\ntwo" + elsif session.type.eql? 'shell' + output = cmd_exec('show_args.exe one two') + $stderr.puts output.rstrip + output.rstrip == "show_args.exe\r\none\r\ntwo" + elsif session.type.eql?('meterpreter') && session.arch.eql?('java') + output.rstrip == ".\\show_args.exe\r\none\r\ntwo" + else + output.rstrip == "./show_args.exe\r\none\r\ntwo" + end + else + output = cmd_exec('./show_args one two') + $stderr.puts output.rstrip + output.rstrip == "./show_args\none\ntwo" + end + 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