Files
metasploit-gs/test/modules/post/test/cmd_exec.rb
T
2024-10-16 16:04:39 +11:00

274 lines
11 KiB
Ruby

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::Post::File
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Meterpreter cmd_exec test',
'Description' => %q( This module will test the meterpreter cmd_exec API ),
'License' => MSF_LICENSE,
'Platform' => [ 'windows', 'linux', 'unix', 'java', 'osx' ],
'SessionTypes' => ['meterpreter', 'shell', 'powershell']
)
)
end
def upload_show_args_binary(details)
print_status 'Uploading precompiled binaries'
upload_file(details[:upload_path], "data/cmd_exec/#{details[:path]}")
unless session.platform.eql?('windows')
chmod(details[:upload_path])
end
end
def show_args_binary_space
result = show_args_binary_base
result[:upload_path] = result[:path].gsub('_',' ')
result[:cmd] = result[:cmd].gsub('_',' ')
result
end
def show_args_binary_special
result = show_args_binary_base
chars = '~!@#$%^&*(){}`\'"<>,.;:=?+|'
if session.platform == 'windows'
chars = '~!@#$%^&(){}`\',.;=+'
end
result[:upload_path] = result[:path].gsub('show_args', chars)
result[:cmd] = result[:cmd].gsub('show_args', chars)
result
end
def show_args_binary
result = show_args_binary_base
result[:upload_path] = result[:path]
result
end
def show_args_binary_base
if session.platform == 'linux' || session.platform == 'unix'
{ path: 'show_args_linux', cmd: './show_args_linux' }
elsif session.platform == 'osx'
{ path: 'show_args_osx', cmd: './show_args_osx' }
elsif session.platform == 'windows' && session.type == 'powershell'
{ path: 'show_args.exe', cmd: "#{pwd}\\show_args.exe" }
elsif session.platform == 'windows' && session.type == 'shell'
{ path: 'show_args.exe', cmd: 'show_args.exe' }
elsif session.platform == 'windows' && session.arch == 'php'
{ path: 'show_args.exe', cmd: '.\\show_args.exe' }
elsif session.platform == 'windows' && session.arch == 'java'
{ path: 'show_args.exe', cmd: '.\\show_args.exe' }
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 containing relative or absolute paths, i.e.
# "show_args.exe\r\none\r\ntwo",
if output_binary.nil?
vprint_status("#{__method__}: Malformed output: no process binary returned")
return false
end
match = output_binary.include?(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_show_args_binary(show_args_binary)
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'
vprint_status("meterpreter?")
output = cmd_exec('cmd.exe', "/c echo #{test_string}")
else
output = cmd_exec("echo #{test_string}")
end
output == test_string
end
it 'should execute the show_args binary with a string' do
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
test_string = Rex::Text.rand_text_alpha(4)
output = cmd_exec("sleep 1; echo #{test_string}")
output == test_string
end
it "should return the full response after sleeping" do
test_string = Rex::Text.rand_text_alpha(4)
test_string2 = Rex::Text.rand_text_alpha(4)
output = cmd_exec("echo #{test_string}; sleep 1; echo #{test_string2}")
output.delete("\r") == "#{test_string}\n#{test_string2}"
end
it "should return the result of echo 10 times" do
10.times do
test_string = Rex::Text.rand_text_alpha(4)
output = cmd_exec("echo #{test_string}")
return false unless output == test_string
end
true
end
else
vprint_status("Session does not support sleep, skipping sleep tests")
end
vprint_status("Finished cmd_exec tests")
end
def test_cmd_exec_quotes
vprint_status("Starting cmd_exec quote tests")
it "should return the result of echo with single quotes" do
test_string = Rex::Text.rand_text_alpha(4)
if session.platform.eql? 'windows'
if session.type.eql?('powershell')
vprint_status("test skipped for Powershell - functionality not correct")
true
else
output = cmd_exec("cmd.exe", "/c echo '#{test_string}'")
output == "'" + test_string + "'"
end
else
output = cmd_exec("echo '#{test_string}'")
output == test_string
end
end
it "should return the result of echo with double quotes" do
test_string = Rex::Text.rand_text_alpha(4)
if session.platform.eql? 'windows'
if session.type.eql?('powershell')
vprint_status("test skipped for Powershell - functionality not correct")
true
else
output = cmd_exec("cmd.exe", "/c echo \"#{test_string}\"")
output == "\"" + test_string + "\""
end
else
output = cmd_exec("echo \"#{test_string}\"")
output == test_string
end
end
end
def test_cmd_exec_stderr
vprint_status("Starting cmd_exec stderr tests")
it "should return the stderr output" do
test_string = Rex::Text.rand_text_alpha(4)
if session.platform.eql? 'windows'
# TODO: Fix this functionality
if session.type.eql?('shell') || session.arch.eql?("php") || session.type.eql?("powershell")
vprint_status("test skipped for Windows CMD, Powershell and PHP - functionality not correct")
true
else
output = cmd_exec("cmd.exe", "/c echo #{test_string} 1>&2")
output.rstrip == test_string
end
else
output = cmd_exec("echo #{test_string} 1>&2")
output == test_string
end
end
end
def test_create_process
upload_show_args_binary(show_args_binary)
upload_show_args_binary(show_args_binary_space)
upload_show_args_binary(show_args_binary_special)
test_string = Rex::Text.rand_text_alpha(4)
it 'should accept blank strings and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: [test_string, '', test_string, '', test_string])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], test_string, '', test_string, '', test_string])
end
it 'should accept multiple args and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: [test_string, test_string])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], test_string, test_string])
end
it 'should accept spaces and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: ['with spaces'])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], 'with spaces'])
end
it 'should accept environment variables and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: ['$PATH'])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], '$PATH'])
end
it 'should accept environment variables within a string and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: ["it's $PATH"])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], "it's $PATH"])
end
it 'should deal with weird windows edge cases' do
output = create_process(show_args_binary[:cmd], args: ['"test"', 'test\\"', 'test\\\\"', 'test words\\\\\\\\', 'test words\\\\\\', '\\\\'])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], '"test"', 'test\\"', 'test\\\\"', 'test words\\\\\\\\', 'test words\\\\\\', '\\\\'])
end
it 'should accept special characters and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: ['~!@#$%^&*(){`1234567890[]",.\'<>\\'])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], '~!@#$%^&*(){`1234567890[]",.\'<>\\'])
end
it 'should accept command line commands and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: ['run&echo'])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], 'run&echo'])
end
it 'should accept semicolons to separate multiple command on a single line and return the create_process output' do
output = create_process(show_args_binary[:cmd], args: ['run&echo;test'])
valid_show_args_response?(output, expected: [show_args_binary[:upload_path], 'run&echo;test'])
end
it 'should accept spaces in the filename and return the create_process output' do
output = create_process(show_args_binary_space[:cmd], args: [test_string, test_string])
valid_show_args_response?(output, expected: [show_args_binary_space[:cmd], test_string, test_string])
end
it 'should accept special characters in the filename and return the create_process output' do
output = create_process(show_args_binary_special[:cmd], args: [test_string, test_string])
valid_show_args_response?(output, expected: [show_args_binary_special[:cmd], test_string, test_string])
end
end
end