d1f5fa06cf
It does not look like shell sessions define their own File class, meaning that the local-platform specific one is always used. Instead we'll define the separator ourselves since it's all we need to perform the basic operations necessary to analyze the path string.
821 lines
22 KiB
Ruby
821 lines
22 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'shellwords'
|
|
require 'rex/text/table'
|
|
require "base64"
|
|
|
|
module Msf
|
|
module Sessions
|
|
|
|
###
|
|
#
|
|
# This class provides basic interaction with a command shell on the remote
|
|
# endpoint. This session is initialized with a stream that will be used
|
|
# as the pipe for reading and writing the command shell.
|
|
#
|
|
###
|
|
class CommandShell
|
|
|
|
#
|
|
# This interface supports basic interaction.
|
|
#
|
|
include Msf::Session::Basic
|
|
|
|
#
|
|
# This interface supports interacting with a single command shell.
|
|
#
|
|
include Msf::Session::Provider::SingleCommandShell
|
|
|
|
include Msf::Sessions::Scriptable
|
|
|
|
include Rex::Ui::Text::Resource
|
|
|
|
@@irb_opts = Rex::Parser::Arguments.new(
|
|
'-h' => [false, 'Help menu.' ],
|
|
'-e' => [true, 'Expression to evaluate.']
|
|
)
|
|
|
|
##
|
|
# :category: Msf::Session::Scriptable implementors
|
|
#
|
|
# Runs the shell session script or resource file.
|
|
#
|
|
def execute_file(full_path, args)
|
|
if File.extname(full_path) == '.rb'
|
|
Rex::Script::Shell.new(self, full_path).run(args)
|
|
else
|
|
load_resource(full_path)
|
|
end
|
|
end
|
|
|
|
#
|
|
# Returns the type of session.
|
|
#
|
|
def self.type
|
|
"shell"
|
|
end
|
|
|
|
def self.can_cleanup_files
|
|
true
|
|
end
|
|
|
|
def initialize(conn, opts = {})
|
|
self.platform ||= ""
|
|
self.arch ||= ""
|
|
self.max_threads = 1
|
|
@cleanup = false
|
|
datastore = opts[:datastore]
|
|
if datastore && !datastore["CommandShellCleanupCommand"].blank?
|
|
@cleanup_command = datastore["CommandShellCleanupCommand"]
|
|
end
|
|
super
|
|
end
|
|
|
|
#
|
|
# Returns the session description.
|
|
#
|
|
def desc
|
|
"Command shell"
|
|
end
|
|
|
|
#
|
|
# Calls the class method
|
|
#
|
|
def type
|
|
self.class.type
|
|
end
|
|
|
|
def abort_foreground_supported
|
|
self.platform != 'windows'
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session::Provider::SingleCommandShell implementors
|
|
#
|
|
# The shell will have been initialized by default.
|
|
#
|
|
def shell_init
|
|
return true
|
|
end
|
|
|
|
def bootstrap(datastore = {}, handler = nil)
|
|
session = self
|
|
|
|
if datastore['AutoVerifySession']
|
|
session_info = ''
|
|
|
|
# Read the initial output and mash it into a single line
|
|
# Timeout set to 1 to read in banner of all payload responses (may capture prompt as well)
|
|
# Encoding is not forced to support non ASCII shells
|
|
if session.info.nil? || session.info.empty?
|
|
banner = shell_read(-1, 1)
|
|
if banner && !banner.empty?
|
|
banner.gsub!(/[^[:print:][:space:]]+/n, "_")
|
|
banner.strip!
|
|
|
|
session_info = @banner = %Q{
|
|
Shell Banner:
|
|
#{banner}
|
|
-----
|
|
}
|
|
end
|
|
end
|
|
|
|
token = Rex::Text.rand_text_alphanumeric(8..24)
|
|
response = shell_command("echo #{token}")
|
|
unless response&.include?(token)
|
|
dlog("Session #{session.sid} failed to respond to an echo command")
|
|
print_error("Command shell session #{session.sid} is not valid and will be closed")
|
|
session.kill
|
|
return nil
|
|
end
|
|
|
|
# Only populate +session.info+ with a captured banner if the shell is responsive and verified
|
|
session.info = session_info
|
|
session
|
|
else
|
|
# Encrypted shells need all information read before anything is written, so we read in the banner here. However we
|
|
# don't populate session.info with the captured value since without AutoVerify there's no way to be certain this
|
|
# actually is a banner and not junk/malicious input
|
|
if session.class == ::Msf::Sessions::EncryptedShell
|
|
shell_read(-1, 0.1)
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Return the subdir of the `documentation/` directory that should be used
|
|
# to find usage documentation
|
|
#
|
|
def docs_dir
|
|
File.join(super, 'shell_session')
|
|
end
|
|
|
|
#
|
|
# List of supported commands.
|
|
#
|
|
def commands
|
|
{
|
|
'help' => 'Help menu',
|
|
'background' => 'Backgrounds the current shell session',
|
|
'sessions' => 'Quickly switch to another session',
|
|
'resource' => 'Run a meta commands script stored in a local file',
|
|
'shell' => 'Spawn an interactive shell (*NIX Only)',
|
|
'download' => 'Download files',
|
|
'upload' => 'Upload files',
|
|
'source' => 'Run a shell script on remote machine (*NIX Only)',
|
|
'irb' => 'Open an interactive Ruby shell on the current session',
|
|
'pry' => 'Open the Pry debugger on the current session'
|
|
}
|
|
end
|
|
|
|
def cmd_help_help
|
|
print_line "There's only so much I can do"
|
|
end
|
|
|
|
def cmd_help(*args)
|
|
cmd = args.shift
|
|
|
|
if cmd
|
|
unless commands.key?(cmd)
|
|
return print_error('No such command')
|
|
end
|
|
|
|
unless respond_to?("cmd_#{cmd}_help")
|
|
return print_error("No help for #{cmd}, try -h")
|
|
end
|
|
|
|
return send("cmd_#{cmd}_help")
|
|
end
|
|
|
|
columns = ['Command', 'Description']
|
|
|
|
tbl = Rex::Text::Table.new(
|
|
'Header' => 'Meta shell commands',
|
|
'Prefix' => "\n",
|
|
'Postfix' => "\n",
|
|
'Indent' => 4,
|
|
'Columns' => columns,
|
|
'SortIndex' => -1
|
|
)
|
|
|
|
commands.each do |key, value|
|
|
tbl << [key, value]
|
|
end
|
|
|
|
print(tbl.to_s)
|
|
end
|
|
|
|
def cmd_background_help
|
|
print_line "Usage: background"
|
|
print_line
|
|
print_line "Stop interacting with this session and return to the parent prompt"
|
|
print_line
|
|
end
|
|
|
|
def cmd_background(*args)
|
|
if !args.empty?
|
|
# We assume that background does not need arguments
|
|
# If user input does not follow this specification
|
|
# Then show help (Including '-h' '--help'...)
|
|
return cmd_background_help
|
|
end
|
|
|
|
if prompt_yesno("Background session #{name}?")
|
|
Rex::Ui::Text::Shell::HistoryManager.pop_context
|
|
self.interacting = false
|
|
end
|
|
end
|
|
|
|
def cmd_sessions_help
|
|
print_line('Usage: sessions <id>')
|
|
print_line
|
|
print_line('Interact with a different session Id.')
|
|
print_line('This command only accepts one positive numeric argument.')
|
|
print_line('This works the same as calling this from the MSF shell: sessions -i <session id>')
|
|
print_line
|
|
end
|
|
|
|
def cmd_sessions(*args)
|
|
if args.length.zero? || args[0].to_i <= 0
|
|
# No args
|
|
return cmd_sessions_help
|
|
end
|
|
|
|
if args.length == 1 && (args[1] == '-h' || args[1] == 'help')
|
|
# One arg, and args[1] => '-h' '-H' 'help'
|
|
return cmd_sessions_help
|
|
end
|
|
|
|
if args.length != 1
|
|
# More than one argument
|
|
return cmd_sessions_help
|
|
end
|
|
|
|
if args[0].to_s == self.name.to_s
|
|
# Src == Dst
|
|
print_status("Session #{self.name} is already interactive.")
|
|
else
|
|
print_status("Backgrounding session #{self.name}...")
|
|
Rex::Ui::Text::Shell::HistoryManager.pop_context
|
|
# store the next session id so that it can be referenced as soon
|
|
# as this session is no longer interacting
|
|
self.next_session = args[0]
|
|
self.interacting = false
|
|
end
|
|
end
|
|
|
|
def cmd_resource(*args)
|
|
if args.empty?
|
|
cmd_resource_help
|
|
return false
|
|
end
|
|
|
|
args.each do |res|
|
|
good_res = nil
|
|
if res == '-'
|
|
good_res = res
|
|
elsif ::File.exist?(res)
|
|
good_res = res
|
|
elsif
|
|
# let's check to see if it's in the scripts/resource dir (like when tab completed)
|
|
[
|
|
::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
|
|
::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter'
|
|
].each do |dir|
|
|
res_path = ::File::join(dir, res)
|
|
if ::File.exist?(res_path)
|
|
good_res = res_path
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if good_res
|
|
print_status("Executing resource script #{good_res}")
|
|
load_resource(good_res)
|
|
print_status("Resource script #{good_res} complete")
|
|
else
|
|
print_error("#{res} is not a valid resource file")
|
|
next
|
|
end
|
|
end
|
|
end
|
|
|
|
def cmd_resource_help
|
|
print_line "Usage: resource path1 [path2 ...]"
|
|
print_line
|
|
print_line "Run the commands stored in the supplied files. (- for stdin, press CTRL+D to end input from stdin)"
|
|
print_line "Resource files may also contain ERB or Ruby code between <ruby></ruby> tags."
|
|
print_line
|
|
end
|
|
|
|
def cmd_shell_help()
|
|
print_line('Usage: shell')
|
|
print_line
|
|
print_line('Pop up an interactive shell via multiple methods.')
|
|
print_line('An interactive shell means that you can use several useful commands like `passwd`, `su [username]`')
|
|
print_line('There are four implementations of it: ')
|
|
print_line('\t1. using python `pty` module (default choice)')
|
|
print_line('\t2. using `socat` command')
|
|
print_line('\t3. using `script` command')
|
|
print_line('\t4. upload a pty program via reverse shell')
|
|
print_line
|
|
end
|
|
|
|
def cmd_shell(*args)
|
|
if args.length == 1 && (args[1] == '-h' || args[1] == 'help')
|
|
# One arg, and args[1] => '-h' '-H' 'help'
|
|
return cmd_sessions_help
|
|
end
|
|
|
|
if platform == 'windows'
|
|
print_error('Functionality not supported on windows')
|
|
return
|
|
end
|
|
|
|
# 1. Using python
|
|
python_path = binary_exists("python") || binary_exists("python3")
|
|
if python_path != nil
|
|
print_status("Using `python` to pop up an interactive shell")
|
|
# Ideally use bash for a friendlier shell, but fall back to /bin/sh if it doesn't exist
|
|
shell_path = binary_exists("bash") || '/bin/sh'
|
|
shell_command("#{python_path} -c \"#{ Msf::Payload::Python.create_exec_stub("import pty; pty.spawn('#{shell_path}')") } \"")
|
|
return
|
|
end
|
|
|
|
# 2. Using script
|
|
script_path = binary_exists("script")
|
|
if script_path != nil
|
|
print_status("Using `script` to pop up an interactive shell")
|
|
# Payload: script /dev/null
|
|
# Using /dev/null to make sure there is no log file on the target machine
|
|
# Prevent being detected by the admin or antivirus softwares
|
|
shell_command("#{script_path} /dev/null")
|
|
return
|
|
end
|
|
|
|
# 3. Using socat
|
|
socat_path = binary_exists("socat")
|
|
if socat_path != nil
|
|
# Payload: socat - exec:'bash -li',pty,stderr,setsid,sigint,sane
|
|
print_status("Using `socat` to pop up an interactive shell")
|
|
shell_command("#{socat_path} - exec:'/bin/sh -li',pty,stderr,setsid,sigint,sane")
|
|
return
|
|
end
|
|
|
|
# 4. Using pty program
|
|
# 4.1 Detect arch and destribution
|
|
# 4.2 Real time compiling
|
|
# 4.3 Upload binary
|
|
# 4.4 Change mode of binary
|
|
# 4.5 Execute binary
|
|
|
|
print_error("Can not pop up an interactive shell")
|
|
end
|
|
|
|
def self.binary_exists(binary, platform: nil, &block)
|
|
if block.call('command -v command').to_s.strip == 'command'
|
|
binary_path = block.call("command -v '#{binary}' && echo true").to_s.strip
|
|
else
|
|
binary_path = block.call("which '#{binary}' && echo true").to_s.strip
|
|
end
|
|
return nil unless binary_path.include?('true')
|
|
|
|
binary_path.split("\n")[0].strip # removes 'true' from stdout
|
|
end
|
|
|
|
#
|
|
# Returns path of a binary in PATH env.
|
|
#
|
|
def binary_exists(binary)
|
|
print_status("Trying to find binary '#{binary}' on the target machine")
|
|
|
|
binary_path = self.class.binary_exists(binary, platform: platform) do |command|
|
|
shell_command_token(command)
|
|
end
|
|
|
|
if binary_path.nil?
|
|
print_error("#{binary} not found")
|
|
else
|
|
print_status("Found #{binary} at #{binary_path}")
|
|
end
|
|
|
|
return binary_path
|
|
end
|
|
|
|
def cmd_download_help
|
|
print_line("Usage: download [src] [dst]")
|
|
print_line
|
|
print_line("Downloads remote files to the local machine.")
|
|
print_line("This command does not support directories")
|
|
print_line
|
|
end
|
|
|
|
def cmd_download(*args)
|
|
if args.length != 2
|
|
# no arguments, just print help message
|
|
return cmd_download_help
|
|
end
|
|
|
|
src = args[0]
|
|
dst = args[1]
|
|
|
|
# Check if src exists
|
|
if !_file_transfer.file_exist?(src)
|
|
print_error('The target file does not exist')
|
|
return
|
|
end
|
|
|
|
fs_sep = platform == 'windows' ? '\\' : '/'
|
|
if dst.blank?
|
|
dst = src.split(fs_sep).last
|
|
elsif ::File.directory?(dst)
|
|
dst += ::File::SEPARATOR unless dst.end_with?(::File::SEPARATOR)
|
|
dst += src.split(fs_sep).last
|
|
end
|
|
dst_dir = ::File.dirname(dst)
|
|
::FileUtils.mkdir_p(dst_dir) if dst_dir and not ::File.directory?(dst_dir)
|
|
|
|
# Get file content
|
|
# match the output style of the Meterpreter equivalent
|
|
print_status("Downloading: #{src} -> #{dst}")
|
|
content = _file_transfer.read_file(src)
|
|
|
|
# Write file to local machine
|
|
::File.binwrite(dst, content)
|
|
print_status("Completed : #{src} -> #{dst}")
|
|
end
|
|
|
|
def cmd_upload_help
|
|
print_line("Usage: upload [src] [dst]")
|
|
print_line
|
|
print_line("Uploads load file to the victim machine.")
|
|
print_line("This command does not support directories")
|
|
print_line
|
|
end
|
|
|
|
def cmd_upload(*args)
|
|
if args.length != 2
|
|
# no arguments, just print help message
|
|
return cmd_upload_help
|
|
end
|
|
|
|
src = args[0]
|
|
dst = args[1]
|
|
|
|
if dst.blank?
|
|
dst = ::File.basename(src)
|
|
elsif _file_transfer.directory?(dst)
|
|
fs_sep = platform == 'windows' ? '\\' : '/'
|
|
dst += fs_sep unless dst.end_with?(fs_sep)
|
|
dst += ::File.basename(src)
|
|
end
|
|
|
|
# Check target file exists on the target machine
|
|
if _file_transfer.file_exist?(dst)
|
|
print_warning('The target file already exists')
|
|
unless prompt_yesno("Overwrite the target file #{dst}?")
|
|
return
|
|
end
|
|
end
|
|
|
|
print_status("Uploading : #{src} -> #{dst}")
|
|
begin
|
|
# Read file from local machine
|
|
content = ::File.binread(src)
|
|
_file_transfer.write_file(dst, content)
|
|
print_status("Completed : #{src} -> #{dst}")
|
|
rescue => e
|
|
print_error("Failed : #{src} -> #{dst} - #{e.message}")
|
|
elog(e)
|
|
end
|
|
end
|
|
|
|
def cmd_source_help
|
|
print_line("Usage: source [file] [background]")
|
|
print_line
|
|
print_line("Execute a local shell script file on remote machine")
|
|
print_line("This meta command will upload the script then execute it on the remote machine")
|
|
print_line
|
|
print_line("background")
|
|
print_line("`y` represent execute the script in background, `n` represent on foreground")
|
|
end
|
|
|
|
def cmd_source(*args)
|
|
if args.length != 2
|
|
# no arguments, just print help message
|
|
return cmd_source_help
|
|
end
|
|
|
|
if platform == 'windows'
|
|
print_error('Functionality not supported on windows')
|
|
return
|
|
end
|
|
|
|
background = args[1].downcase == 'y'
|
|
|
|
local_file = args[0]
|
|
remote_file = "/tmp/." + ::Rex::Text.rand_text_alpha(32) + ".sh"
|
|
|
|
cmd_upload(local_file, remote_file)
|
|
|
|
# Change file permission in case of TOCTOU
|
|
shell_command("chmod 0600 #{remote_file}")
|
|
|
|
if background
|
|
print_status("Executing on remote machine background")
|
|
print_line(shell_command("nohup sh -x #{remote_file} &"))
|
|
else
|
|
print_status("Executing on remote machine foreground")
|
|
print_line(shell_command("sh -x #{remote_file}"))
|
|
end
|
|
print_status("Cleaning temp file on remote machine")
|
|
shell_command("rm -rf '#{remote_file}'")
|
|
end
|
|
|
|
def cmd_irb_help
|
|
print_line('Usage: irb')
|
|
print_line
|
|
print_line('Open an interactive Ruby shell on the current session.')
|
|
print @@irb_opts.usage
|
|
end
|
|
|
|
#
|
|
# Open an interactive Ruby shell on the current session
|
|
#
|
|
def cmd_irb(*args)
|
|
expressions = []
|
|
|
|
# Parse the command options
|
|
@@irb_opts.parse(args) do |opt, idx, val|
|
|
case opt
|
|
when '-e'
|
|
expressions << val
|
|
when '-h'
|
|
return cmd_irb_help
|
|
end
|
|
end
|
|
|
|
session = self
|
|
framework = self.framework
|
|
|
|
if expressions.empty?
|
|
print_status('Starting IRB shell...')
|
|
print_status("You are in the \"self\" (session) object\n")
|
|
Rex::Ui::Text::Shell::HistoryManager.with_context(name: :irb) do
|
|
Rex::Ui::Text::IrbShell.new(self).run
|
|
end
|
|
else
|
|
# XXX: No vprint_status here
|
|
if framework.datastore['VERBOSE'].to_s == 'true'
|
|
print_status("You are executing expressions in #{binding.receiver}")
|
|
end
|
|
|
|
expressions.each { |expression| eval(expression, binding) }
|
|
end
|
|
end
|
|
|
|
def cmd_pry_help
|
|
print_line 'Usage: pry'
|
|
print_line
|
|
print_line 'Open the Pry debugger on the current session.'
|
|
print_line
|
|
end
|
|
|
|
#
|
|
# Open the Pry debugger on the current session
|
|
#
|
|
def cmd_pry(*args)
|
|
if args.include?('-h')
|
|
cmd_pry_help
|
|
return
|
|
end
|
|
|
|
begin
|
|
require 'pry'
|
|
rescue LoadError
|
|
print_error('Failed to load Pry, try "gem install pry"')
|
|
return
|
|
end
|
|
|
|
print_status('Starting Pry shell...')
|
|
print_status("You are in the \"self\" (session) object\n")
|
|
Pry.config.history_load = false
|
|
Rex::Ui::Text::Shell::HistoryManager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
|
|
self.pry
|
|
end
|
|
end
|
|
|
|
#
|
|
# Explicitly runs a single line command.
|
|
#
|
|
def run_single(cmd)
|
|
# Do nil check for cmd (CTRL+D will cause nil error)
|
|
return unless cmd
|
|
|
|
begin
|
|
arguments = Shellwords.shellwords(cmd)
|
|
method = arguments.shift
|
|
rescue ArgumentError => e
|
|
# Handle invalid shellwords, such as unmatched quotes
|
|
# See https://github.com/rapid7/metasploit-framework/issues/15912
|
|
end
|
|
|
|
# Built-in command
|
|
if commands.key?(method)
|
|
return run_builtin_cmd(method, arguments)
|
|
end
|
|
|
|
# User input is not a built-in command, write to socket directly
|
|
shell_write(cmd + command_termination)
|
|
end
|
|
|
|
#
|
|
# Run built-in command
|
|
#
|
|
def run_builtin_cmd(method, arguments)
|
|
# Dynamic function call
|
|
self.send('cmd_' + method, *arguments)
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session::Provider::SingleCommandShell implementors
|
|
#
|
|
# Explicitly run a single command, return the output.
|
|
#
|
|
def shell_command(cmd, timeout=5)
|
|
# Send the command to the session's stdin.
|
|
shell_write(cmd + command_termination)
|
|
|
|
etime = ::Time.now.to_f + timeout
|
|
buff = ""
|
|
|
|
# Keep reading data until no more data is available or the timeout is
|
|
# reached.
|
|
while (::Time.now.to_f < etime and (self.respond_to?(:ring) or ::IO.select([rstream], nil, nil, timeout)))
|
|
res = shell_read(-1, 0.01)
|
|
buff << res if res
|
|
timeout = etime - ::Time.now.to_f
|
|
end
|
|
|
|
buff
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session::Provider::SingleCommandShell implementors
|
|
#
|
|
# Read from the command shell.
|
|
#
|
|
def shell_read(length=-1, timeout=1)
|
|
begin
|
|
rv = rstream.get_once(length, timeout)
|
|
framework.events.on_session_output(self, rv) if rv
|
|
return rv
|
|
rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE => e
|
|
#print_error("Socket error: #{e.class}: #{e}")
|
|
shell_close
|
|
raise e
|
|
end
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session::Provider::SingleCommandShell implementors
|
|
#
|
|
# Writes to the command shell.
|
|
#
|
|
def shell_write(buf)
|
|
return unless buf
|
|
|
|
begin
|
|
framework.events.on_session_command(self, buf.strip)
|
|
rstream.write(buf)
|
|
rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE => e
|
|
#print_error("Socket error: #{e.class}: #{e}")
|
|
shell_close
|
|
raise e
|
|
end
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session::Provider::SingleCommandShell implementors
|
|
#
|
|
# Closes the shell.
|
|
# Note: parent's 'self.kill' method calls cleanup below.
|
|
#
|
|
def shell_close()
|
|
self.kill
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session implementors
|
|
#
|
|
# Closes the shell.
|
|
#
|
|
def cleanup
|
|
return if @cleanup
|
|
|
|
@cleanup = true
|
|
if rstream
|
|
if !@cleanup_command.blank?
|
|
# this is a best effort, since the session is possibly already dead
|
|
shell_command_token(@cleanup_command) rescue nil
|
|
|
|
# we should only ever cleanup once
|
|
@cleanup_command = nil
|
|
end
|
|
|
|
# this is also a best-effort
|
|
rstream.close rescue nil
|
|
rstream = nil
|
|
end
|
|
super
|
|
end
|
|
|
|
#
|
|
# Execute any specified auto-run scripts for this session
|
|
#
|
|
def process_autoruns(datastore)
|
|
if datastore['InitialAutoRunScript'] && !datastore['InitialAutoRunScript'].empty?
|
|
args = Shellwords.shellwords( datastore['InitialAutoRunScript'] )
|
|
print_status("Session ID #{sid} (#{tunnel_to_s}) processing InitialAutoRunScript '#{datastore['InitialAutoRunScript']}'")
|
|
execute_script(args.shift, *args)
|
|
end
|
|
|
|
if (datastore['AutoRunScript'] && datastore['AutoRunScript'].empty? == false)
|
|
args = Shellwords.shellwords( datastore['AutoRunScript'] )
|
|
print_status("Session ID #{sid} (#{tunnel_to_s}) processing AutoRunScript '#{datastore['AutoRunScript']}'")
|
|
execute_script(args.shift, *args)
|
|
end
|
|
end
|
|
|
|
attr_accessor :arch
|
|
attr_accessor :platform
|
|
attr_accessor :max_threads
|
|
attr_reader :banner
|
|
|
|
protected
|
|
|
|
##
|
|
# :category: Msf::Session::Interactive implementors
|
|
#
|
|
# Override the basic session interaction to use shell_read and
|
|
# shell_write instead of operating on rstream directly.
|
|
def _interact
|
|
framework.events.on_session_interact(self)
|
|
Rex::Ui::Text::Shell::HistoryManager.with_context(name: self.type.to_sym) {
|
|
_interact_stream
|
|
}
|
|
end
|
|
|
|
##
|
|
# :category: Msf::Session::Interactive implementors
|
|
#
|
|
def _interact_stream
|
|
fds = [rstream.fd, user_input.fd]
|
|
|
|
# Displays +info+ on all session startups
|
|
# +info+ is set to the shell banner and initial prompt in the +bootstrap+ method
|
|
user_output.print("#{@banner}\n") if !@banner.blank? && self.interacting
|
|
|
|
run_single('')
|
|
|
|
while self.interacting
|
|
sd = Rex::ThreadSafe.select(fds, nil, fds, 0.5)
|
|
next unless sd
|
|
|
|
if sd[0].include? rstream.fd
|
|
user_output.print(shell_read)
|
|
end
|
|
if sd[0].include? user_input.fd
|
|
run_single((user_input.gets || '').chomp("\n"))
|
|
end
|
|
Thread.pass
|
|
end
|
|
end
|
|
|
|
# Functionality used as part of builtin commands/metashell support that isn't meant to be exposed
|
|
# as part of the CommandShell's public API
|
|
class FileTransfer
|
|
include Msf::Post::File
|
|
|
|
# @param [Msf::Sessions::CommandShell] session
|
|
def initialize(session)
|
|
@session = session
|
|
end
|
|
|
|
private
|
|
|
|
def vprint_status(s)
|
|
session.print_status(s)
|
|
end
|
|
|
|
attr_reader :session
|
|
end
|
|
|
|
def _file_transfer
|
|
FileTransfer.new(self)
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|