Merge pull request #19108 from smashery/new_cmd_exec
New process launch API
This commit is contained in:
+4
-4
@@ -41,9 +41,9 @@ PATH
|
||||
metasploit-concern
|
||||
metasploit-credential
|
||||
metasploit-model
|
||||
metasploit-payloads (= 2.0.175)
|
||||
metasploit-payloads (= 2.0.183)
|
||||
metasploit_data_models
|
||||
metasploit_payloads-mettle (= 1.0.31)
|
||||
metasploit_payloads-mettle (= 1.0.32)
|
||||
mqtt
|
||||
msgpack (~> 1.6.0)
|
||||
mutex_m
|
||||
@@ -295,7 +295,7 @@ GEM
|
||||
activemodel (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
railties (~> 7.0)
|
||||
metasploit-payloads (2.0.175)
|
||||
metasploit-payloads (2.0.183)
|
||||
metasploit_data_models (6.0.3)
|
||||
activerecord (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
@@ -306,7 +306,7 @@ GEM
|
||||
railties (~> 7.0)
|
||||
recog
|
||||
webrick
|
||||
metasploit_payloads-mettle (1.0.31)
|
||||
metasploit_payloads-mettle (1.0.32)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.5.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
|
||||
+2
-2
@@ -88,9 +88,9 @@ metasploit-concern, 5.0.2, "New BSD"
|
||||
metasploit-credential, 6.0.9, "New BSD"
|
||||
metasploit-framework, 6.4.31, "New BSD"
|
||||
metasploit-model, 5.0.2, "New BSD"
|
||||
metasploit-payloads, 2.0.175, "3-clause (or ""modified"") BSD"
|
||||
metasploit-payloads, 2.0.183, "3-clause (or ""modified"") BSD"
|
||||
metasploit_data_models, 6.0.3, "New BSD"
|
||||
metasploit_payloads-mettle, 1.0.31, "3-clause (or ""modified"") BSD"
|
||||
metasploit_payloads-mettle, 1.0.32, "3-clause (or ""modified"") BSD"
|
||||
method_source, 1.1.0, MIT
|
||||
mime-types, 3.5.2, MIT
|
||||
mime-types-data, 3.2024.0604, MIT
|
||||
|
||||
@@ -735,6 +735,49 @@ Shell Banner:
|
||||
end
|
||||
end
|
||||
|
||||
# Perform command line escaping wherein most chars are able to be escaped by quoting them,
|
||||
# but others don't have a valid way of existing inside quotes, so we need to "glue" together
|
||||
# a series of sections of the original command line; some sections inside quotes, and some outside
|
||||
# @param arg [String] The command line arg to escape
|
||||
# @param quote_requiring [Array<String>] The chars that can successfully be escaped inside quotes
|
||||
# @param unquotable_char [String] The character that can't exist inside quotes
|
||||
# @param escaped_unquotable_char [String] The escaped form of unquotable_char
|
||||
# @param quote_char [String] The char used for quoting
|
||||
def self._glue_cmdline_escape(arg, quote_requiring, unquotable_char, escaped_unquotable_char, quote_char)
|
||||
current_token = ""
|
||||
result = ""
|
||||
in_quotes = false
|
||||
|
||||
arg.each_char do |char|
|
||||
if char == unquotable_char
|
||||
if in_quotes
|
||||
# This token has been in an inside-quote context, so let's properly wrap that before continuing
|
||||
current_token = "#{quote_char}#{current_token}#{quote_char}"
|
||||
end
|
||||
result += current_token
|
||||
result += escaped_unquotable_char # Escape the offending percent
|
||||
|
||||
# Start a new token - we'll assume we're remaining outside quotes
|
||||
current_token = ''
|
||||
in_quotes = false
|
||||
next
|
||||
elsif quote_requiring.include?(char)
|
||||
# Oh, it turns out we should have been inside quotes for this token.
|
||||
# Let's note that, for when we actually append the token
|
||||
in_quotes = true
|
||||
end
|
||||
current_token += char
|
||||
end
|
||||
|
||||
if in_quotes
|
||||
# The final token has been in an inside-quote context, so let's properly wrap that before continuing
|
||||
current_token = "#{quote_char}#{current_token}#{quote_char}"
|
||||
end
|
||||
result += current_token
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
attr_accessor :arch
|
||||
attr_accessor :platform
|
||||
attr_accessor :max_threads
|
||||
|
||||
@@ -5,9 +5,44 @@ module Msf::Sessions
|
||||
self.platform = "unix"
|
||||
super
|
||||
end
|
||||
|
||||
def shell_command_token(cmd,timeout = 10)
|
||||
shell_command_token_unix(cmd,timeout)
|
||||
end
|
||||
|
||||
# Convert the executable and argument array to a command that can be run in this command shell
|
||||
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
|
||||
def to_cmd(cmd_and_args)
|
||||
self.class.to_cmd(cmd_and_args)
|
||||
end
|
||||
|
||||
# Escape an individual argument per Unix shell rules
|
||||
# @param arg [String] Shell argument
|
||||
def escape_arg(arg)
|
||||
self.class.escape_arg(arg)
|
||||
end
|
||||
|
||||
# Convert the executable and argument array to a command that can be run in this command shell
|
||||
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
|
||||
def self.to_cmd(cmd_and_args)
|
||||
escaped = cmd_and_args.map do |arg|
|
||||
escape_arg(arg)
|
||||
end
|
||||
|
||||
escaped.join(' ')
|
||||
end
|
||||
|
||||
# Escape an individual argument per Unix shell rules
|
||||
# @param arg [String] Shell argument
|
||||
def self.escape_arg(arg)
|
||||
quote_requiring = ['\\', '`', '(', ')', '<', '>', '&', '|', ' ', '@', '"', '$', ';']
|
||||
result = CommandShell._glue_cmdline_escape(arg, quote_requiring, "'", "\\'", "'")
|
||||
if result == ''
|
||||
result = "''"
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
module Msf::Sessions
|
||||
|
||||
class CommandShellWindows < CommandShell
|
||||
@@ -6,9 +5,115 @@ module Msf::Sessions
|
||||
self.platform = "windows"
|
||||
super
|
||||
end
|
||||
|
||||
def self.space_chars
|
||||
[' ', '\t', '\v']
|
||||
end
|
||||
|
||||
def shell_command_token(cmd,timeout = 10)
|
||||
shell_command_token_win32(cmd,timeout)
|
||||
end
|
||||
|
||||
# Convert the executable and argument array to a command that can be run in this command shell
|
||||
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
|
||||
def to_cmd(cmd_and_args)
|
||||
self.class.to_cmd(cmd_and_args)
|
||||
end
|
||||
|
||||
# Escape a process for the command line
|
||||
# @param executable [String] The process to launch
|
||||
def self.escape_cmd(executable)
|
||||
needs_quoting = space_chars.any? do |char|
|
||||
executable.include?(char)
|
||||
end
|
||||
|
||||
if needs_quoting
|
||||
executable = "\"#{executable}\""
|
||||
end
|
||||
|
||||
executable
|
||||
end
|
||||
|
||||
# Convert the executable and argument array to a commandline that can be passed to CreateProcessAsUserW.
|
||||
# @param args [Array<String>] The arguments to the process
|
||||
# @remark The difference between this and `to_cmd` is that the output of `to_cmd` is expected to be passed
|
||||
# to cmd.exe, whereas this is expected to be passed directly to the Win32 API, anticipating that it
|
||||
# will in turn be interpreted by CommandLineToArgvW.
|
||||
def self.argv_to_commandline(args)
|
||||
escaped_args = args.map do |arg|
|
||||
escape_arg(arg)
|
||||
end
|
||||
|
||||
escaped_args.join(' ')
|
||||
end
|
||||
|
||||
# Escape an individual argument per Windows shell rules
|
||||
# @param arg [String] Shell argument
|
||||
def self.escape_arg(arg)
|
||||
needs_quoting = space_chars.any? do |char|
|
||||
arg.include?(char)
|
||||
end
|
||||
|
||||
# Fix the weird behaviour when backslashes are treated differently when immediately prior to a double-quote
|
||||
# We need to send double the number of backslashes to make it work as expected
|
||||
# See: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
|
||||
arg = arg.gsub(/(\\*)"/, '\\1\\1"')
|
||||
|
||||
# Quotes need to be escaped
|
||||
arg = arg.gsub('"', '\\"')
|
||||
|
||||
if needs_quoting
|
||||
# At the end of the argument, we're about to add another quote - so any backslashes need to be doubled here too
|
||||
arg = arg.gsub(/(\\*)$/, '\\1\\1')
|
||||
arg = "\"#{arg}\""
|
||||
end
|
||||
|
||||
# Empty string needs to be coerced to have a value
|
||||
arg = '""' if arg == ''
|
||||
|
||||
arg
|
||||
end
|
||||
|
||||
# Convert the executable and argument array to a command that can be run in this command shell
|
||||
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
|
||||
def self.to_cmd(cmd_and_args)
|
||||
# The space, caret and quote chars need to be inside double-quoted strings.
|
||||
# The percent character needs to be escaped using a caret char, while being outside a double-quoted string.
|
||||
#
|
||||
# Situations where these two situations combine are going to be the trickiest cases: something that has quote-requiring
|
||||
# characters (e.g. spaces), but which also needs to avoid expanding an environment variable. In this case,
|
||||
# the string needs to end up being partially quoted; with parts of the string in quotes, but others (i.e. bits with percents) not.
|
||||
# For example:
|
||||
# 'env var is %temp%, yes, %TEMP%' needs to end up as '"env var is "^%temp^%", yes, "^%TEMP^%'
|
||||
#
|
||||
# There is flexibility in how you might implement this, but I think this one looks the most "human" to me,
|
||||
# which would make it less signaturable.
|
||||
#
|
||||
# To do this, we'll consider each argument character-by-character. Each time we encounter a percent sign, we break out of any quotes
|
||||
# (if we've been inside them in the current "token"), and then start a new "token".
|
||||
|
||||
quote_requiring = ['"', '^', ' ', "\t", "\v", '&', '<', '>', '|']
|
||||
|
||||
escaped_cmd_and_args = cmd_and_args.map do |arg|
|
||||
# Escape quote chars by doubling them up, except those preceeded by a backslash (which are already effectively escaped, and handled below)
|
||||
arg = arg.gsub(/([^\\])"/, '\\1""')
|
||||
arg = arg.gsub(/^"/, '""')
|
||||
|
||||
result = CommandShell._glue_cmdline_escape(arg, quote_requiring, '%', '^%', '"')
|
||||
|
||||
# Fix the weird behaviour when backslashes are treated differently when immediately prior to a double-quote
|
||||
# We need to send double the number of backslashes to make it work as expected
|
||||
# See: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
|
||||
result.gsub!(/(\\*)"/, '\\1\\1"')
|
||||
|
||||
# Empty string needs to be coerced to have a value
|
||||
result = '""' if result == ''
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
escaped_cmd_and_args.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -38,6 +38,91 @@ class Msf::Sessions::PowerShell < Msf::Sessions::CommandShell
|
||||
|
||||
include Mixin
|
||||
|
||||
# Convert the executable and argument array to a command that can be run in this command shell
|
||||
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
|
||||
def to_cmd(cmd_and_args)
|
||||
self.class.to_cmd(cmd_and_args)
|
||||
end
|
||||
|
||||
# Convert the executable and argument array to a command that can be run in this command shell
|
||||
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
|
||||
def self.to_cmd(cmd_and_args)
|
||||
# The principle here is that we want to launch a process such that it receives *exactly* what is in `args`.
|
||||
# This means we need to:
|
||||
# - Escape all special characters
|
||||
# - Not escape environment variables
|
||||
# - Side-step any PowerShell magic
|
||||
# If someone specifically wants to use the PowerShell magic, they can use other APIs
|
||||
|
||||
needs_wrapping_chars = ['$', '`', '(', ')', '@', '>', '<', '{','}', '&', ',', ' ', ';']
|
||||
|
||||
result = ""
|
||||
cmd_and_args.each_with_index do |arg, index|
|
||||
needs_single_quoting = false
|
||||
if arg.include?("'")
|
||||
arg = arg.gsub("'", "''")
|
||||
needs_single_quoting = true
|
||||
end
|
||||
|
||||
if arg.include?('"')
|
||||
# PowerShell acts weird around quotes and backslashes
|
||||
# First we need to escape backslashes immediately prior to a double-quote, because
|
||||
# they're treated differently than backslashes anywhere else
|
||||
arg = arg.gsub(/(\\+)"/, '\\1\\1"')
|
||||
|
||||
# Then we can safely prepend a backslash to escape our double-quote
|
||||
arg = arg.gsub('"', '\\"')
|
||||
needs_single_quoting = true
|
||||
end
|
||||
|
||||
needs_wrapping_chars.each do |char|
|
||||
if arg.include?(char)
|
||||
needs_single_quoting = true
|
||||
end
|
||||
end
|
||||
|
||||
# PowerShell magic - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_special_characters?view=powershell-7.4#stop-parsing-token---
|
||||
if arg == '--%'
|
||||
needs_single_quoting = true
|
||||
end
|
||||
|
||||
will_be_double_quoted_by_powershell = [' ', '\t', '\v'].any? do |bad_char|
|
||||
arg.include?(bad_char)
|
||||
end
|
||||
|
||||
if will_be_double_quoted_by_powershell
|
||||
# This is horrible, and I'm so so sorry.
|
||||
# If an argument ends with a series of backslashes, and it will be quoted by PowerShell when *it* launches the process (e.g. because the arg contains a space),
|
||||
# PowerShell will not correctly handle backslashes immediately preceeding the quote that it *itself* adds. So we need to be responsible for this.
|
||||
arg = arg.gsub(/(\\*)$/, '\\1\\1')
|
||||
end
|
||||
|
||||
if needs_single_quoting
|
||||
arg = "'#{arg}'"
|
||||
end
|
||||
|
||||
if arg == ''
|
||||
# Pass in empty strings
|
||||
arg = '\'""\''
|
||||
end
|
||||
|
||||
if index == 0
|
||||
if needs_single_quoting
|
||||
# If the executable name (i.e. index 0) has beeen wrapped, then we'll have converted it to a string.
|
||||
# We then need to use the call operator ('&') to call it.
|
||||
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.3#call-operator-
|
||||
result = "& #{arg}"
|
||||
else
|
||||
result = arg
|
||||
end
|
||||
else
|
||||
result = "#{result} #{arg}"
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
#
|
||||
# Execute any specified auto-run scripts for this session
|
||||
#
|
||||
|
||||
@@ -52,6 +52,71 @@ module Msf::Post::Common
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
# Create a new process, receiving the program's output
|
||||
# @param executable [String] The path to the executable; either absolute or relative to the session's current directory
|
||||
# @param args [Array<String>] The arguments to the executable
|
||||
# @time_out [Integer] Number of seconds before the call will time out
|
||||
# @param opts [Hash] Optional settings to parameterise the process launch
|
||||
# @option Hidden [Boolean] Is the process launched without creating a visible window
|
||||
# @option Channelized [Boolean] The process is launched with pipes connected to a channel, e.g. for sending input/receiving output
|
||||
# @option Suspended [Boolean] Start the process suspended
|
||||
# @option UseThreadToken [Boolean] Use the thread token (as opposed to the process token) to launch the process
|
||||
# @option Desktop [Boolean] Run on meterpreter's current desktop
|
||||
# @option Session [Integer] Execute process in a given session as the session user
|
||||
# @option Subshell [Boolean] Execute process in a subshell
|
||||
# @option Pty [Boolean] Execute process in a pty (if available)
|
||||
# @option ParentId [Integer] Spoof the parent PID (if possible)
|
||||
# @option InMemory [Boolean,String] Execute from memory (`path` is treated as a local file to upload, and the actual path passed
|
||||
# to meterpreter is this parameter's value, if provided as a String)
|
||||
def create_process(executable, args: [], time_out: 15, opts: {})
|
||||
case session.type
|
||||
when 'meterpreter'
|
||||
session.response_timeout = time_out
|
||||
opts = {
|
||||
'Hidden' => true,
|
||||
'Channelized' => true,
|
||||
# Well-behaving meterpreters will ignore the Subshell flag when using arg arrays.
|
||||
# This is still provided for supporting old meterpreters.
|
||||
'Subshell' => true
|
||||
}.merge(opts)
|
||||
|
||||
if session.platform == 'windows'
|
||||
if session.arch == 'php'
|
||||
opts[:legacy_args] = Msf::Sessions::CommandShellWindows.to_cmd(args)
|
||||
opts[:legacy_path] = Msf::Sessions::CommandShellWindows.to_cmd([executable])
|
||||
elsif session.arch == 'python'
|
||||
opts[:legacy_path] = executable
|
||||
# Yes, Unix. Old Python meterp had a bug where it used posix shell splitting
|
||||
# syntax even on Windows. For backwards-compatibility, we can trick it into
|
||||
# doing the right thing by using Unix escaping.
|
||||
opts[:legacy_args] = Msf::Sessions::CommandShellUnix.to_cmd(args)
|
||||
else
|
||||
opts[:legacy_args] = Msf::Sessions::CommandShellWindows.argv_to_commandline(args)
|
||||
opts[:legacy_path] = Msf::Sessions::CommandShellWindows.escape_cmd(executable)
|
||||
end
|
||||
else
|
||||
opts[:legacy_args] = Msf::Sessions::CommandShellUnix.to_cmd(args)
|
||||
opts[:legacy_path] = Msf::Sessions::CommandShellUnix.to_cmd([executable])
|
||||
end
|
||||
|
||||
if opts['Channelized']
|
||||
o = session.sys.process.capture_output(executable, args, opts, time_out)
|
||||
else
|
||||
session.sys.process.execute(executable, args, opts)
|
||||
end
|
||||
when 'powershell'
|
||||
cmd = session.to_cmd([executable] + args)
|
||||
o = session.shell_command(cmd, time_out)
|
||||
o.chomp! if o
|
||||
when 'shell'
|
||||
cmd = session.to_cmd([executable] + args)
|
||||
o = session.shell_command_token(cmd, time_out)
|
||||
o.chomp! if o
|
||||
end
|
||||
return "" if o.nil?
|
||||
return o
|
||||
end
|
||||
|
||||
#
|
||||
# Executes +cmd+ on the remote system
|
||||
#
|
||||
|
||||
@@ -580,7 +580,7 @@ module Msf::Post::File
|
||||
if session.type == 'meterpreter' && session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_FS_CHMOD)
|
||||
session.fs.file.chmod(path, mode)
|
||||
else
|
||||
cmd_exec("chmod #{mode.to_s(8)} '#{path}'")
|
||||
create_process('chmod', args: [mode.to_s(8), path])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -740,6 +740,7 @@ module Msf::Post::File
|
||||
else
|
||||
file_mode = 'Create'
|
||||
end
|
||||
file_name = file_name.gsub("'","''")
|
||||
pwsh_code = <<~PSH
|
||||
try {
|
||||
$encoded='#{encoded_chunk}';
|
||||
@@ -911,7 +912,7 @@ protected
|
||||
success = _win_ansi_write_file(b64_filename, b64_data, chunk_size)
|
||||
return false unless success
|
||||
vprint_status("Uploaded Base64-encoded file. Decoding using certutil")
|
||||
success = _shell_command_with_success_code("certutil -f -decode #{b64_filename} #{file_name}")
|
||||
success = _shell_process_with_success_code('certutil', ['-f', '-decode', b64_filename, file_name])
|
||||
return false unless success
|
||||
rescue ::Exception => e
|
||||
print_error("Exception while running #{__method__}: #{e}")
|
||||
@@ -937,10 +938,10 @@ protected
|
||||
success = _win_ansi_write_file(b64_filename, b64_data, chunk_size)
|
||||
return false unless success
|
||||
vprint_status("Uploaded Base64-encoded file. Decoding using certutil")
|
||||
success = _shell_command_with_success_code("certutil -decode #{b64_filename} #{tmp_filename}")
|
||||
success = _shell_process_with_success_code('certutil', ['-decode', b64_filename, tmp_filename])
|
||||
return false unless success
|
||||
vprint_status("Certutil succeeded. Appending using copy")
|
||||
success = _shell_command_with_success_code("copy /b #{file_name}+#{tmp_filename} #{file_name}")
|
||||
success = _shell_process_with_success_code('copy', ['/b', "#{file_name}+#{tmp_filename}", file_name])
|
||||
return false unless success
|
||||
rescue ::Exception => e
|
||||
print_error("Exception while running #{__method__}: #{e}")
|
||||
@@ -980,7 +981,7 @@ protected
|
||||
# Short-circuit an empty string. The : builtin is part of posix
|
||||
# standard and should theoretically exist everywhere.
|
||||
if data.empty?
|
||||
return _shell_command_with_success_code(": #{redirect} #{file_name}")
|
||||
return _shell_command_with_success_code(": #{redirect} #{session.escape_arg(file_name)}")
|
||||
end
|
||||
|
||||
d = data.dup
|
||||
@@ -1081,7 +1082,7 @@ protected
|
||||
# The first command needs to use the provided redirection for either
|
||||
# appending or truncating.
|
||||
cmd = command.sub('CONTENTS') { chunks.shift }
|
||||
succeeded = _shell_command_with_success_code("#{cmd} #{redirect} \"#{file_name}\"")
|
||||
succeeded = _shell_command_with_success_code("#{cmd} #{redirect} #{session.escape_arg(file_name)}")
|
||||
return false unless succeeded
|
||||
|
||||
# After creating/truncating or appending with the first command, we
|
||||
@@ -1090,7 +1091,7 @@ protected
|
||||
vprint_status("Next chunk is #{chunk.length} bytes")
|
||||
cmd = command.sub('CONTENTS') { chunk }
|
||||
|
||||
succeeded = _shell_command_with_success_code("#{cmd} >> '#{file_name}'")
|
||||
succeeded = _shell_command_with_success_code("#{cmd} >> #{session.escape_arg(file_name)}")
|
||||
unless succeeded
|
||||
print_warning("Write partially succeeded then failed. May need to manually clean up #{file_name}")
|
||||
return false
|
||||
@@ -1107,6 +1108,15 @@ protected
|
||||
return result&.include?(token)
|
||||
end
|
||||
|
||||
def _shell_process_with_success_code(executable, args)
|
||||
cmd = session.to_cmd([executable] + args)
|
||||
token = "_#{::Rex::Text.rand_text_alpha(32)}"
|
||||
result = session.shell_command_token("#{cmd} && echo #{token}")
|
||||
|
||||
return result&.include?(token)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Calculate the maximum line length for a unix shell.
|
||||
#
|
||||
|
||||
@@ -93,11 +93,11 @@ module SingleCommandShell
|
||||
output
|
||||
end
|
||||
|
||||
def to_cmd(cmd, args)
|
||||
def to_cmd(cmd_and_args)
|
||||
if platform == 'windows'
|
||||
result = Msf::Sessions::CommandShellWindows.to_cmd(cmd, args)
|
||||
result = Msf::Sessions::CommandShellWindows.to_cmd(cmd_and_args)
|
||||
else
|
||||
result = Msf::Sessions::CommandShellUnix.to_cmd(cmd, args)
|
||||
result = Msf::Sessions::CommandShellUnix.to_cmd(cmd_and_args)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -107,15 +107,24 @@ class Process < Rex::Post::Process
|
||||
|
||||
#
|
||||
# Executes an application using the arguments provided
|
||||
# @param path [String] Path on the remote system to the executable to run
|
||||
# @param arguments [String,Array<String>] Arguments to the process. When passed as a String (rather than an array of Strings),
|
||||
# this is treated as a string containing all arguments.
|
||||
# @param opts [Hash] Optional settings to parameterise the process launch
|
||||
# @option Hidden [Boolean] Is the process launched without creating a visible window
|
||||
# @option Channelized [Boolean] The process is launched with pipes connected to a channel, e.g. for sending input/receiving output
|
||||
# @option Suspended [Boolean] Start the process suspended
|
||||
# @option UseThreadToken [Boolean] Use the thread token (as opposed to the process token) to launch the process
|
||||
# @option Desktop [Boolean] Run on meterpreter's current desktopt
|
||||
# @option Session [Integer] Execute process in a given session as the session user
|
||||
# @option Subshell [Boolean] Execute process in a subshell
|
||||
# @option Pty [Boolean] Execute process in a pty (if available)
|
||||
# @option ParentId [Integer] Spoof the parent PID (if possible)
|
||||
# @option InMemory [Boolean,String] Execute from memory (`path` is treated as a local file to upload, and the actual path passed
|
||||
# to meterpreter is this parameter's value, if provided as a String)
|
||||
# @option :legacy_args [String] When arguments is an array, this is the command to execute if the receiving Meterpreter does not support arguments as an array
|
||||
#
|
||||
# Hash arguments supported:
|
||||
#
|
||||
# Hidden => true/false
|
||||
# Channelized => true/false
|
||||
# Suspended => true/false
|
||||
# InMemory => true/false
|
||||
#
|
||||
def Process.execute(path, arguments = nil, opts = nil)
|
||||
def Process.execute(path, arguments = '', opts = nil)
|
||||
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_EXECUTE)
|
||||
flags = 0
|
||||
|
||||
@@ -164,11 +173,26 @@ class Process < Rex::Post::Process
|
||||
end
|
||||
end
|
||||
|
||||
request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ));
|
||||
|
||||
# Add arguments
|
||||
# If process arguments were supplied
|
||||
if (arguments != nil)
|
||||
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments);
|
||||
if arguments.kind_of?(Array)
|
||||
request.add_tlv(TLV_TYPE_PROCESS_UNESCAPED_PATH, client.unicode_filter_decode( path ));
|
||||
# This flag is needed to disambiguate how to handle escaping special characters in the path when no arguments are provided
|
||||
flags |= PROCESS_EXECUTE_FLAG_ARG_ARRAY
|
||||
arguments.each do |arg|
|
||||
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENT, arg);
|
||||
end
|
||||
if opts[:legacy_path]
|
||||
request.add_tlv(TLV_TYPE_PROCESS_PATH, opts[:legacy_path])
|
||||
end
|
||||
if opts[:legacy_args]
|
||||
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, opts[:legacy_args])
|
||||
end
|
||||
elsif arguments.kind_of?(String)
|
||||
request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ));
|
||||
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments)
|
||||
else
|
||||
raise ArgumentError.new('Unknown type for arguments')
|
||||
end
|
||||
|
||||
request.add_tlv(TLV_TYPE_PROCESS_FLAGS, flags);
|
||||
@@ -194,7 +218,7 @@ class Process < Rex::Post::Process
|
||||
#
|
||||
# Execute an application and capture the output
|
||||
#
|
||||
def Process.capture_output(path, arguments = nil, opts = nil, time_out = 15)
|
||||
def Process.capture_output(path, arguments = '', opts = nil, time_out = 15)
|
||||
start = Time.now.to_i
|
||||
process = execute(path, arguments, opts)
|
||||
data = ""
|
||||
|
||||
@@ -119,6 +119,7 @@ PROCESS_EXECUTE_FLAG_DESKTOP = (1 << 4)
|
||||
PROCESS_EXECUTE_FLAG_SESSION = (1 << 5)
|
||||
PROCESS_EXECUTE_FLAG_SUBSHELL = (1 << 6)
|
||||
PROCESS_EXECUTE_FLAG_PTY = (1 << 7)
|
||||
PROCESS_EXECUTE_FLAG_ARG_ARRAY = (1 << 8)
|
||||
|
||||
# Registry
|
||||
TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000
|
||||
@@ -151,25 +152,27 @@ TLV_TYPE_ENV_GROUP = TLV_META_TYPE_GROUP | 1102
|
||||
DELETE_KEY_FLAG_RECURSIVE = (1 << 0)
|
||||
|
||||
# Process
|
||||
TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000
|
||||
TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001
|
||||
TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002
|
||||
TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003
|
||||
TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004
|
||||
TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005
|
||||
TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006
|
||||
TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007
|
||||
TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008
|
||||
TLV_TYPE_PID = TLV_META_TYPE_UINT | 2300
|
||||
TLV_TYPE_PROCESS_NAME = TLV_META_TYPE_STRING | 2301
|
||||
TLV_TYPE_PROCESS_PATH = TLV_META_TYPE_STRING | 2302
|
||||
TLV_TYPE_PROCESS_GROUP = TLV_META_TYPE_GROUP | 2303
|
||||
TLV_TYPE_PROCESS_FLAGS = TLV_META_TYPE_UINT | 2304
|
||||
TLV_TYPE_PROCESS_ARGUMENTS = TLV_META_TYPE_STRING | 2305
|
||||
TLV_TYPE_PROCESS_ARCH = TLV_META_TYPE_UINT | 2306
|
||||
TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307
|
||||
TLV_TYPE_PROCESS_SESSION = TLV_META_TYPE_UINT | 2308
|
||||
TLV_TYPE_PROCESS_ARCH_NAME = TLV_META_TYPE_STRING | 2309
|
||||
TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000
|
||||
TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001
|
||||
TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002
|
||||
TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003
|
||||
TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004
|
||||
TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005
|
||||
TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006
|
||||
TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007
|
||||
TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008
|
||||
TLV_TYPE_PID = TLV_META_TYPE_UINT | 2300
|
||||
TLV_TYPE_PROCESS_NAME = TLV_META_TYPE_STRING | 2301
|
||||
TLV_TYPE_PROCESS_PATH = TLV_META_TYPE_STRING | 2302
|
||||
TLV_TYPE_PROCESS_GROUP = TLV_META_TYPE_GROUP | 2303
|
||||
TLV_TYPE_PROCESS_FLAGS = TLV_META_TYPE_UINT | 2304
|
||||
TLV_TYPE_PROCESS_ARGUMENTS = TLV_META_TYPE_STRING | 2305
|
||||
TLV_TYPE_PROCESS_ARCH = TLV_META_TYPE_UINT | 2306
|
||||
TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307
|
||||
TLV_TYPE_PROCESS_SESSION = TLV_META_TYPE_UINT | 2308
|
||||
TLV_TYPE_PROCESS_ARCH_NAME = TLV_META_TYPE_STRING | 2309
|
||||
TLV_TYPE_PROCESS_ARGUMENT = TLV_META_TYPE_STRING | 2310
|
||||
TLV_TYPE_PROCESS_UNESCAPED_PATH = TLV_META_TYPE_STRING | 2311
|
||||
|
||||
TLV_TYPE_DRIVER_ENTRY = TLV_META_TYPE_GROUP | 2320
|
||||
TLV_TYPE_DRIVER_BASENAME = TLV_META_TYPE_STRING | 2321
|
||||
|
||||
@@ -74,9 +74,9 @@ Gem::Specification.new do |spec|
|
||||
# are needed when there's no database
|
||||
spec.add_runtime_dependency 'metasploit-model'
|
||||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '2.0.175'
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '2.0.183'
|
||||
# Needed for the next-generation POSIX Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.31'
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.32'
|
||||
# Needed by msfgui and other rpc components
|
||||
# Locked until build env can handle newer version. See: https://github.com/msgpack/msgpack-ruby/issues/334
|
||||
spec.add_runtime_dependency 'msgpack', '~> 1.6.0'
|
||||
|
||||
@@ -26,7 +26,7 @@ module MetasploitModule
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Handler' => Msf::Handler::ReverseTcp,
|
||||
'Session' => Msf::Sessions::CommandShell,
|
||||
'Session' => Msf::Sessions::CommandShellUnix,
|
||||
'PayloadType' => 'cmd_bash',
|
||||
'RequiredCmd' => 'bash-tcp',
|
||||
'Payload' =>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1061712
|
||||
CachedSize = 1061912
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1061712
|
||||
CachedSize = 1061912
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1061712
|
||||
CachedSize = 1061912
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1061884
|
||||
CachedSize = 1062084
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1061884
|
||||
CachedSize = 1062084
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1061884
|
||||
CachedSize = 1062084
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1516268
|
||||
CachedSize = 1516524
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1516268
|
||||
CachedSize = 1516524
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1516268
|
||||
CachedSize = 1516524
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1519288
|
||||
CachedSize = 1519544
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1519288
|
||||
CachedSize = 1519544
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1519288
|
||||
CachedSize = 1519544
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
CachedSize = 813091
|
||||
CachedSize = 813075
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
CachedSize = 813091
|
||||
CachedSize = 813075
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
# Module generated by tools/modules/generate_mettle_payloads.rb
|
||||
module MetasploitModule
|
||||
CachedSize = 813091
|
||||
CachedSize = 813075
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 34854
|
||||
CachedSize = 34928
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Php::ReverseTcp
|
||||
|
||||
@@ -27,6 +27,6 @@ class MetasploitModule < Msf::Post
|
||||
def run
|
||||
print_status("Executing #{datastore['COMMAND']} on #{session.inspect}...")
|
||||
res = cmd_exec(datastore['COMMAND'])
|
||||
print_status("Response: #{res}")
|
||||
print_status("Response: \n#{res}")
|
||||
end
|
||||
end
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
RSpec.describe Msf::Sessions::CommandShellUnix do
|
||||
describe 'to_cmd processing' do
|
||||
it 'should not do anything for simple args' do
|
||||
expect(described_class.to_cmd(['./test'] + [])).to eq('./test')
|
||||
expect(described_class.to_cmd(['sh'] + [])).to eq('sh')
|
||||
expect(described_class.to_cmd(['./test'] + ['basic','args'])).to eq('./test basic args')
|
||||
expect(described_class.to_cmd(['basic','args'])).to eq('basic args')
|
||||
end
|
||||
|
||||
it 'should escape spaces' do
|
||||
expect(described_class.to_cmd(['/home/user/some folder/some program'] + [])).to eq("'/home/user/some folder/some program'")
|
||||
expect(described_class.to_cmd(['./test'] + ['with space'])).to eq("./test 'with space'")
|
||||
end
|
||||
|
||||
it 'should escape logical operators' do
|
||||
expect(described_class.to_cmd(['./test'] + ['&&', 'echo', 'words'])).to eq("./test '&&' echo words")
|
||||
expect(described_class.to_cmd(['./test'] + ['||', 'echo', 'words'])).to eq("./test '||' echo words")
|
||||
expect(described_class.to_cmd(['./test'] + ['&echo', 'words'])).to eq("./test '&echo' words")
|
||||
expect(described_class.to_cmd(['./test'] + ['run&echo', 'words'])).to eq("./test 'run&echo' words")
|
||||
end
|
||||
|
||||
it 'should quote if single quotes are present' do
|
||||
expect(described_class.to_cmd(['./test'] + ["it's"])).to eq("./test it\\'s")
|
||||
expect(described_class.to_cmd(['./test'] + ["it's a param"])).to eq("./test it\\''s a param'")
|
||||
end
|
||||
|
||||
it 'should escape redirectors' do
|
||||
expect(described_class.to_cmd(['./test'] + ['>', 'out.txt'])).to eq("./test '>' out.txt")
|
||||
expect(described_class.to_cmd(['./test'] + ['<', 'in.txt'])).to eq("./test '<' in.txt")
|
||||
end
|
||||
|
||||
it 'should not expand env vars' do
|
||||
expect(described_class.to_cmd(['./test'] + ['$PATH'])).to eq("./test '$PATH'")
|
||||
expect(described_class.to_cmd(['./test'] + ["it's $PATH"])).to eq("./test it\\''s $PATH'")
|
||||
expect(described_class.to_cmd(['./test'] + ["\"$PATH\""])).to eq("./test '\"$PATH\"'")
|
||||
expect(described_class.to_cmd(['./test'] + ["it's \"$PATH\""])).to eq("./test it\\''s \"$PATH\"'")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
RSpec.describe Msf::Sessions::CommandShellWindows do
|
||||
|
||||
describe 'to_cmd processing' do
|
||||
it 'should not do anything for simple args' do
|
||||
expect(described_class.to_cmd(['test.exe'] + [])).to eq('test.exe')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['basic','args'])).to eq('test.exe basic args')
|
||||
end
|
||||
|
||||
it 'should quote spaces' do
|
||||
expect(described_class.to_cmd(['C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE'] + [])).to eq('"C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE"')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['with space'])).to eq('test.exe "with space"')
|
||||
end
|
||||
|
||||
it 'should escape logical operators' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['&&', 'echo', 'words'])).to eq('test.exe "&&" echo words')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['||', 'echo', 'words'])).to eq('test.exe "||" echo words')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['&echo', 'words'])).to eq('test.exe "&echo" words')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['run&echo', 'words'])).to eq('test.exe "run&echo" words')
|
||||
end
|
||||
|
||||
it 'should escape redirectors' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['>', 'out.txt'])).to eq('test.exe ">" out.txt')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['<', 'in.txt'])).to eq('test.exe "<" in.txt')
|
||||
end
|
||||
|
||||
it 'should escape carets' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['with^caret'])).to eq('test.exe "with^caret"')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['with^^carets'])).to eq('test.exe "with^^carets"')
|
||||
end
|
||||
|
||||
it 'should not expand env vars' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['%temp%'])).to eq('test.exe ^%temp^%')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['env', 'var', 'is', '%temp%'])).to eq('test.exe env var is ^%temp^%')
|
||||
end
|
||||
|
||||
it 'should handle the weird backslash escaping behaviour in front of quotes' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['quote\\\\"'])).to eq('test.exe "quote\\\\\\\\""')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['will be quoted\\\\'])).to eq('test.exe "will be quoted\\\\\\\\"')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['will be quoted\\\\ '])).to eq('test.exe "will be quoted\\\\ "') # Should not be doubled up
|
||||
expect(described_class.to_cmd(['test.exe'] + ['"test"', 'test\\"', 'test\\\\"', 'test words\\\\\\\\', 'test words\\\\\\', '\\\\'])).to eq('test.exe """test""" "test\\\\"" "test\\\\\\\\"" "test words\\\\\\\\\\\\\\\\" "test words\\\\\\\\\\\\" \\\\')
|
||||
end
|
||||
|
||||
it 'should handle combinations of quoting and percent-escaping' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['env var is %temp%'])).to eq('test.exe "env var is "^%temp^%')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['env var is %temp%, yes, %TEMP%'])).to eq('test.exe "env var is "^%temp^%", yes, "^%TEMP^%')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['%temp%found at the start shouldn\'t %temp% be quoted'])).to eq('test.exe ^%temp^%"found at the start shouldn\'t "^%temp^%" be quoted"')
|
||||
end
|
||||
|
||||
it 'should handle single percents' do
|
||||
expect(described_class.to_cmd(['test.exe'] + ['%single percent'])).to eq('test.exe ^%"single percent"')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['100%'])).to eq('test.exe 100^%')
|
||||
end
|
||||
|
||||
it 'should handle empty args' do
|
||||
expect(described_class.to_cmd(['test.exe'] + [''])).to eq('test.exe ""')
|
||||
expect(described_class.to_cmd(['test.exe'] + ['', ''])).to eq('test.exe "" ""')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'argv_to_commandline processing' do
|
||||
it 'should not do anything for simple args' do
|
||||
expect(described_class.argv_to_commandline([])).to eq('')
|
||||
expect(described_class.argv_to_commandline(['basic','args'])).to eq('basic args')
|
||||
expect(described_class.argv_to_commandline(['!@#$%^&*(){}><.,\''])).to eq('!@#$%^&*(){}><.,\'')
|
||||
end
|
||||
|
||||
it 'should quote space characters' do
|
||||
expect(described_class.argv_to_commandline([])).to eq('')
|
||||
expect(described_class.argv_to_commandline(['basic','args'])).to eq('basic args')
|
||||
end
|
||||
|
||||
it 'should escape double-quote characters' do
|
||||
expect(described_class.argv_to_commandline(['"one','"two"'])).to eq('\\"one \\"two\\"')
|
||||
expect(described_class.argv_to_commandline(['"one "two"'])).to eq('"\\"one \\"two\\""')
|
||||
end
|
||||
|
||||
it 'should handle the weird backslash escaping behaviour in front of quotes' do
|
||||
expect(described_class.argv_to_commandline(['\\\\"'])).to eq('\\\\\\\\\\"')
|
||||
expect(described_class.argv_to_commandline(['space \\\\'])).to eq('"space \\\\\\\\"')
|
||||
expect(described_class.argv_to_commandline(['"test"', 'test\\"', 'test\\\\"', 'test words\\\\\\\\', 'test words\\\\\\', '\\\\'])).to eq('\"test\" test\\\\\\" test\\\\\\\\\\" "test words\\\\\\\\\\\\\\\\" "test words\\\\\\\\\\\\" \\\\')
|
||||
end
|
||||
|
||||
it 'should handle empty args' do
|
||||
expect(described_class.argv_to_commandline([''])).to eq('""')
|
||||
expect(described_class.argv_to_commandline(['', ''])).to eq('"" ""')
|
||||
end
|
||||
end
|
||||
end
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
RSpec.describe Msf::Sessions::PowerShell do
|
||||
describe 'to_cmd processing' do
|
||||
it 'should not do anything for simple args' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['abc', '123'])).to eq(".\\test.exe abc 123")
|
||||
expect(described_class.to_cmd(["C:\\SysinternalsSuite\\procexp.exe"] + [])).to eq("C:\\SysinternalsSuite\\procexp.exe")
|
||||
end
|
||||
|
||||
it 'should double single-quotes' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ["'abc'"])).to eq(".\\test.exe '''abc'''")
|
||||
end
|
||||
|
||||
it 'should escape less than' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ["'abc'", '>', 'out.txt'])).to eq(".\\test.exe '''abc''' '>' out.txt")
|
||||
end
|
||||
|
||||
it 'should escape other special chars' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ["'abc'", '<', '(', ')', '$test', '`words`', 'abc,def'])).to eq(".\\test.exe '''abc''' '<' '(' ')' '$test' '`words`' 'abc,def'")
|
||||
end
|
||||
|
||||
it 'should backslash escape double-quotes' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['"abc'])).to eq(".\\test.exe '\\\"abc'")
|
||||
end
|
||||
|
||||
it 'should correctly backslash escape backslashes and double-quotes' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['\\"abc'])).to eq(".\\test.exe '\\\\\\\"abc'")
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['\\\\"abc'])).to eq(".\\test.exe '\\\\\\\\\\\"abc'")
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['\\\\"ab\\\\c'])).to eq(".\\test.exe '\\\\\\\\\\\"ab\\\\c'")
|
||||
end
|
||||
|
||||
it 'should quote the executable and add the call operator' do
|
||||
expect(described_class.to_cmd([".\\test$.exe"] + ['abc'])).to eq("& '.\\test$.exe' abc")
|
||||
expect(described_class.to_cmd([".\\test'.exe"] + ['abc'])).to eq("& '.\\test''.exe' abc")
|
||||
expect(described_class.to_cmd(["C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE"] + [])).to eq("& 'C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE'")
|
||||
end
|
||||
|
||||
it 'should not expand environment variables' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['$env:path'])).to eq(".\\test.exe '$env:path'")
|
||||
end
|
||||
|
||||
it 'should not respect PowerShell Magic' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['--%', 'not', '$parsed'])).to eq(".\\test.exe '--%' not '$parsed'")
|
||||
end
|
||||
|
||||
it 'should not split comma args' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['arg1,notarg2'])).to eq(".\\test.exe 'arg1,notarg2'")
|
||||
end
|
||||
|
||||
it 'should handle empty strings' do
|
||||
expect(described_class.to_cmd([".\\test.exe"] + ['', 'a', '', 'b'])).to eq(".\\test.exe '\"\"' a '\"\"' b")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,7 +22,7 @@ module Acceptance
|
||||
def initialize
|
||||
super
|
||||
|
||||
@default_timeout = ENV['CI'] ? 120 : 40
|
||||
@default_timeout = ENV['CI'] ? 480 : 40
|
||||
@debug = false
|
||||
@env ||= {}
|
||||
@cmd ||= []
|
||||
|
||||
+104
-115
@@ -21,15 +21,42 @@ class MetasploitModule < Msf::Post
|
||||
)
|
||||
end
|
||||
|
||||
def upload_show_args_binary
|
||||
def upload_show_args_binary(details)
|
||||
print_status 'Uploading precompiled binaries'
|
||||
upload_file(show_args_binary[:path], "data/cmd_exec/#{show_args_binary[:path]}")
|
||||
upload_file(details[:upload_path], "data/cmd_exec/#{details[:path]}")
|
||||
unless session.platform.eql?('windows')
|
||||
chmod(show_args_binary[:path])
|
||||
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'
|
||||
@@ -40,9 +67,10 @@ class MetasploitModule < Msf::Post
|
||||
{ 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' }
|
||||
elsif session.type == 'meterpreter' && session.arch == 'java'
|
||||
else
|
||||
raise "unknown platform #{session.platform}"
|
||||
end
|
||||
@@ -56,7 +84,11 @@ class MetasploitModule < Msf::Post
|
||||
|
||||
# Match the binary name, to support the binary name containing relative or absolute paths, i.e.
|
||||
# "show_args.exe\r\none\r\ntwo",
|
||||
match = output_binary.match?(expected[0]) && output_args == expected[1..]
|
||||
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
|
||||
@@ -68,7 +100,7 @@ class MetasploitModule < Msf::Post
|
||||
# we are inconsistent reporting windows session types
|
||||
windows_strings = ['windows', 'win']
|
||||
vprint_status("Starting cmd_exec tests")
|
||||
upload_show_args_binary
|
||||
upload_show_args_binary(show_args_binary)
|
||||
|
||||
it "should return the result of echo" do
|
||||
test_string = Rex::Text.rand_text_alpha(4)
|
||||
@@ -82,11 +114,6 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
|
||||
it 'should execute the show_args binary with a 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
|
||||
@@ -130,12 +157,8 @@ class MetasploitModule < Msf::Post
|
||||
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.arch == ARCH_PYTHON
|
||||
output = cmd_exec("cmd.exe", "/c echo \"#{test_string}\"")
|
||||
output == test_string
|
||||
# TODO: Fix this functionality
|
||||
elsif session.type.eql?('shell') || session.type.eql?('powershell')
|
||||
vprint_status("test skipped for Windows CMD and Powershell - functionality not correct")
|
||||
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}'")
|
||||
@@ -150,12 +173,8 @@ class MetasploitModule < Msf::Post
|
||||
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.platform.eql? 'windows' and session.arch == ARCH_PYTHON
|
||||
output = cmd_exec("cmd.exe", "/c echo \"#{test_string}\"")
|
||||
output == test_string
|
||||
# TODO: Fix this functionality
|
||||
elsif session.type.eql?('shell') || session.type.eql?('powershell')
|
||||
vprint_status("test skipped for Windows CMD and Powershell - functionality not correct")
|
||||
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}\"")
|
||||
@@ -189,96 +208,66 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: These tests are in preparation for Smashery's create process API
|
||||
# def test_create_process
|
||||
# upload_show_args_binary
|
||||
#
|
||||
# test_string = Rex::Text.rand_text_alpha(4)
|
||||
#
|
||||
# it 'should accept blank strings and return the create_process output' do
|
||||
# if session.arch.eql?("php")
|
||||
# # TODO: Fix this functionality
|
||||
#
|
||||
# vprint_status("test skipped for PHP - functionality not correct")
|
||||
# true
|
||||
# end
|
||||
# output = create_process(show_args_binary[:cmd], args: [test_string, '', test_string, '', test_string])
|
||||
# valid_show_args_response?(output, expected: [show_args_binary[: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[: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[: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[: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[:path], "it's $PATH"])
|
||||
# end
|
||||
#
|
||||
# it 'should accept special characters and return the create_process output' do
|
||||
# if session.platform.eql? 'windows'
|
||||
# # TODO: Fix this functionality
|
||||
# vprint_status('test skipped for Windows - functionality not correct')
|
||||
# true
|
||||
# end
|
||||
# output = create_process(show_args_binary[:cmd], args: ['~!@#$%^&*(){`1234567890[]",.\'<>'])
|
||||
# valid_show_args_response?(output, expected: [show_args_binary[:path], '~!@#$%^&*(){`1234567890[]",.\'<>'])
|
||||
# end
|
||||
#
|
||||
# it 'should accept command line commands and return the create_process output' do
|
||||
# if session.arch.eql?("php")
|
||||
# # TODO: Fix this functionality
|
||||
# vprint_status("test skipped for PHP - functionality not correct")
|
||||
# true
|
||||
# end
|
||||
#
|
||||
# output = create_process(show_args_binary[:cmd], args: ['run&echo'])
|
||||
# valid_show_args_response?(output, expected: [show_args_binary[:path], 'run&echo'])
|
||||
# end
|
||||
#
|
||||
# it 'should accept semicolons to separate multiple command on a single line and return the create_process output' do
|
||||
# if session.arch.eql?("php")
|
||||
# # TODO: Fix this functionality
|
||||
# vprint_status("test skipped for PHP - functionality not correct")
|
||||
# true
|
||||
# end
|
||||
#
|
||||
# output = create_process(show_args_binary[:cmd], args: ['run&echo;test'])
|
||||
# valid_show_args_response?(output, expected: [show_args_binary[:path], 'run&echo;test'])
|
||||
# end
|
||||
#
|
||||
# it 'should accept spaces in the filename and return the create_process output' do
|
||||
# if session.platform.eql? 'windows'
|
||||
# # TODO: Fix this functionality
|
||||
# vprint_status('test skipped for Windows CMD - functionality not correct')
|
||||
# true
|
||||
# end
|
||||
#
|
||||
# output = create_process('./show_args file', args: [test_string, test_string])
|
||||
# valid_show_args_response?(output, expected: ['./show_args file', test_string, test_string])
|
||||
# end
|
||||
#
|
||||
# it 'should accept special characters in the filename and return the create_process output' do
|
||||
# if session.platform.eql? 'windows'
|
||||
# # TODO: Fix this functionality
|
||||
# vprint_status('test skipped for Windows CMD - functionality not correct')
|
||||
# true
|
||||
# end
|
||||
#
|
||||
# output = create_process('./~!@#$%^&*(){}', args: [test_string, test_string])
|
||||
# valid_show_args_response?(output, expected: ['./~!@#$%^&*(){}', test_string, 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
|
||||
|
||||
Reference in New Issue
Block a user