Allow running a shell command separately to the currently interactive shell
This commit is contained in:
parent
91d0a6cc55
commit
9d8193150f
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
require 'winrm'
|
require 'winrm'
|
||||||
|
require 'shellwords'
|
||||||
|
|
||||||
module Msf::Sessions
|
module Msf::Sessions
|
||||||
#
|
#
|
||||||
|
@ -13,12 +14,13 @@ module Msf::Sessions
|
||||||
class WinRMStreamAdapter
|
class WinRMStreamAdapter
|
||||||
# @param shell [Net::MsfWinRM::StdinShell] Shell for talking to the WinRM service
|
# @param shell [Net::MsfWinRM::StdinShell] Shell for talking to the WinRM service
|
||||||
# @param on_shell_ended [Method] Callback for when the background thread notices the shell has ended
|
# @param on_shell_ended [Method] Callback for when the background thread notices the shell has ended
|
||||||
def initialize(shell, on_shell_ended)
|
def initialize(shell, interactive_command_id, on_shell_ended)
|
||||||
# To buffer input received while a session is backgrounded, we stick responses in a list
|
# To buffer input received while a session is backgrounded, we stick responses in a list
|
||||||
@buffer_mutex = Mutex.new
|
@buffer_mutex = Mutex.new
|
||||||
@buffer = []
|
@buffer = []
|
||||||
@check_stdin_event = Rex::Sync::Event.new(false, true)
|
@check_stdin_event = Rex::Sync::Event.new(false, true)
|
||||||
@received_stdout_event = Rex::Sync::Event.new(false, true)
|
@received_stdout_event = Rex::Sync::Event.new(false, true)
|
||||||
|
self.interactive_command_id = interactive_command_id
|
||||||
self.shell = shell
|
self.shell = shell
|
||||||
self.on_shell_ended = on_shell_ended
|
self.on_shell_ended = on_shell_ended
|
||||||
end
|
end
|
||||||
|
@ -37,7 +39,7 @@ module Msf::Sessions
|
||||||
end
|
end
|
||||||
|
|
||||||
def write(buf)
|
def write(buf)
|
||||||
shell.send_stdin(buf)
|
shell.send_stdin(buf, interactive_command_id)
|
||||||
refresh_stdout
|
refresh_stdout
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -91,7 +93,7 @@ module Msf::Sessions
|
||||||
loop do
|
loop do
|
||||||
tmp_buffer = []
|
tmp_buffer = []
|
||||||
output_seen = false
|
output_seen = false
|
||||||
shell.read_stdout do |stdout, stderr|
|
shell.read_stdout(interactive_command_id) do |stdout, stderr|
|
||||||
if stdout || stderr
|
if stdout || stderr
|
||||||
output_seen = true
|
output_seen = true
|
||||||
end
|
end
|
||||||
|
@ -146,12 +148,12 @@ module Msf::Sessions
|
||||||
# rubocop:disable Lint/SuppressedException
|
# rubocop:disable Lint/SuppressedException
|
||||||
def close
|
def close
|
||||||
stop_keep_alive_loop
|
stop_keep_alive_loop
|
||||||
shell.cleanup_shell
|
shell.cleanup_command(interactive_command_id)
|
||||||
rescue WinRM::WinRMWSManFault
|
rescue WinRM::WinRMWSManFault
|
||||||
end
|
end
|
||||||
# rubocop:enable Lint/SuppressedException
|
# rubocop:enable Lint/SuppressedException
|
||||||
|
|
||||||
attr_accessor :shell, :keep_alive_thread, :on_shell_ended
|
attr_accessor :shell, :keep_alive_thread, :on_shell_ended, :interactive_command_id
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -171,9 +173,10 @@ module Msf::Sessions
|
||||||
#
|
#
|
||||||
# @param shell [WinRM::Shells::Base] A WinRM shell object
|
# @param shell [WinRM::Shells::Base] A WinRM shell object
|
||||||
# @param opts [Hash] Optional parameters to pass to the session object.
|
# @param opts [Hash] Optional parameters to pass to the session object.
|
||||||
def initialize(shell, opts = {})
|
def initialize(shell, interactive_command_id, opts = {})
|
||||||
self.shell = shell
|
self.shell = shell
|
||||||
self.adapter = WinRMStreamAdapter.new(self.shell, method(:shell_ended))
|
self.interactive_command_id = interactive_command_id
|
||||||
|
self.adapter = WinRMStreamAdapter.new(self.shell, interactive_command_id, method(:shell_ended))
|
||||||
super(adapter, opts)
|
super(adapter, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -182,10 +185,16 @@ module Msf::Sessions
|
||||||
end
|
end
|
||||||
|
|
||||||
def abort_foreground
|
def abort_foreground
|
||||||
shell.send_ctrl_c
|
shell.send_ctrl_c(interactive_command_id)
|
||||||
adapter.refresh_stdout
|
adapter.refresh_stdout
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def shell_command(cmd, timeout = 5)
|
||||||
|
args = Shellwords.shellwords(cmd)
|
||||||
|
command = args.shift
|
||||||
|
shell.shell_command_synchronous(command, args, timeout)
|
||||||
|
end
|
||||||
|
|
||||||
# The characters used to terminate a command in this shell
|
# The characters used to terminate a command in this shell
|
||||||
# (Breaks in 2012 without this)
|
# (Breaks in 2012 without this)
|
||||||
def command_termination
|
def command_termination
|
||||||
|
@ -225,7 +234,7 @@ module Msf::Sessions
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_accessor :shell, :adapter
|
attr_accessor :shell, :adapter, :interactive_command_id
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,25 +16,29 @@ module Net
|
||||||
|
|
||||||
def add_finalizer; end
|
def add_finalizer; end
|
||||||
|
|
||||||
def create_proc
|
def send_command(command, arguments = [])
|
||||||
# We use cmd rather than powershell because powershell v3 on 2012 (and maybe earlier)
|
open unless shell_id
|
||||||
# do not seem to pass us stdout/stderr.
|
super(command, arguments)
|
||||||
self.command_id = send_command('cmd.exe', [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_command_shell(input, _arguments = [])
|
# Runs a shell command synchronously, and returns the output
|
||||||
tries ||= 2
|
def shell_command_synchronous(command, args, timeout)
|
||||||
send_stdin(input)
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
||||||
yield shell_id, command_id
|
command_id = send_command(command, args)
|
||||||
rescue WinRM::WinRMWSManFault => e
|
buffer = []
|
||||||
raise unless FAULTS_FOR_RESET.include?(e.fault_code) && (tries -= 1) > 0
|
begin
|
||||||
|
while (Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start_time) < (timeout * 1000)
|
||||||
reset_on_error(e)
|
read_stdout(command_id) do |stdout, stderr|
|
||||||
retry
|
buffer << stdout if stdout
|
||||||
end
|
buffer << stderr if stderr
|
||||||
|
end
|
||||||
def cleanup_shell
|
end
|
||||||
cleanup_command(command_id)
|
rescue EOFError
|
||||||
|
# Shell terminated of its own accord
|
||||||
|
ensure
|
||||||
|
cleanup_command(command_id)
|
||||||
|
end
|
||||||
|
buffer.join('')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Runs the specified command with optional arguments
|
# Runs the specified command with optional arguments
|
||||||
|
@ -42,9 +46,8 @@ module Net
|
||||||
# @yieldparam [string] standard out response text
|
# @yieldparam [string] standard out response text
|
||||||
# @yieldparam [string] standard error response text
|
# @yieldparam [string] standard error response text
|
||||||
# @yieldreturn [WinRM::Output] The command output
|
# @yieldreturn [WinRM::Output] The command output
|
||||||
def read_stdout(&block)
|
def read_stdout(command_id, &block)
|
||||||
open unless shell_id
|
open unless shell_id
|
||||||
create_proc unless command_id
|
|
||||||
begin
|
begin
|
||||||
response_reader.read_output(command_output_message(shell_id, command_id), &block)
|
response_reader.read_output(command_output_message(shell_id, command_id), &block)
|
||||||
rescue WinRM::WinRMWSManFault => e
|
rescue WinRM::WinRMWSManFault => e
|
||||||
|
@ -61,10 +64,7 @@ module Net
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_ctrl_c
|
def send_ctrl_c(command_id)
|
||||||
open unless shell_id
|
|
||||||
create_proc unless command_id
|
|
||||||
|
|
||||||
ctrl_c_msg = CtrlC.new(
|
ctrl_c_msg = CtrlC.new(
|
||||||
connection_opts,
|
connection_opts,
|
||||||
shell_uri: shell_uri,
|
shell_uri: shell_uri,
|
||||||
|
@ -74,9 +74,8 @@ module Net
|
||||||
transport.send_request(ctrl_c_msg.build)
|
transport.send_request(ctrl_c_msg.build)
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_stdin(input)
|
def send_stdin(input, command_id)
|
||||||
open unless shell_id
|
open unless shell_id
|
||||||
create_proc unless command_id
|
|
||||||
|
|
||||||
stdin_msg = WinRM::WSMV::WriteStdin.new(
|
stdin_msg = WinRM::WSMV::WriteStdin.new(
|
||||||
connection_opts,
|
connection_opts,
|
||||||
|
@ -101,15 +100,13 @@ module Net
|
||||||
def open_shell
|
def open_shell
|
||||||
msg = WinRM::WSMV::CreateShell.new(connection_opts, shell_opts)
|
msg = WinRM::WSMV::CreateShell.new(connection_opts, shell_opts)
|
||||||
resp_doc = transport.send_request(msg.build)
|
resp_doc = transport.send_request(msg.build)
|
||||||
self.owner = REXML::XPath.first(resp_doc, '//rsp:Owner').text
|
match = REXML::XPath.first(resp_doc, '//rsp:Owner')
|
||||||
|
self.owner = match.text if match
|
||||||
REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
|
REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :owner
|
attr_accessor :owner
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
attr_accessor :command_id
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -98,8 +98,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
shell = conn.shell(:stdin, {})
|
shell = conn.shell(:stdin, {})
|
||||||
# Trigger the shell to open
|
|
||||||
shell.send_stdin('')
|
|
||||||
session_setup(shell, rhost, rport, endpoint)
|
session_setup(shell, rhost, rport, endpoint)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -110,7 +108,10 @@ class MetasploitModule < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
|
|
||||||
def session_setup(shell, _rhost, _rport, _endpoint)
|
def session_setup(shell, _rhost, _rport, _endpoint)
|
||||||
sess = Msf::Sessions::WinrmCommandShell.new(shell)
|
# We use cmd rather than powershell because powershell v3 on 2012 (and maybe earlier)
|
||||||
|
# do not seem to pass us stdout/stderr.
|
||||||
|
interactive_process_id = shell.send_command('cmd.exe')
|
||||||
|
sess = Msf::Sessions::WinrmCommandShell.new(shell, interactive_process_id)
|
||||||
sess.platform = 'windows'
|
sess.platform = 'windows'
|
||||||
username = datastore['USERNAME']
|
username = datastore['USERNAME']
|
||||||
password = datastore['PASSWORD']
|
password = datastore['PASSWORD']
|
||||||
|
|
Loading…
Reference in New Issue