metasploit-framework/lib/msf/ui/console/driver.rb

376 lines
8.3 KiB
Ruby

require 'msf/core'
require 'msf/base'
require 'msf/ui'
require 'msf/ui/console/framework_event_manager'
require 'msf/ui/console/command_dispatcher'
require 'msf/ui/console/table'
require 'find'
module Msf
module Ui
module Console
###
#
# This class implements a user interface driver on a console interface.
#
###
class Driver < Msf::Ui::Driver
ConfigCore = "framework/core"
ConfigGroup = "framework/ui/console"
DefaultPrompt = "%umsf"
DefaultPromptChar = ">%c"
#
# The console driver processes various framework notified events.
#
include FrameworkEventManager
#
# The console driver is a command shell.
#
include Rex::Ui::Text::DispatcherShell
#
# Initializes a console driver instance with the supplied prompt string and
# prompt character. The optional hash can take extra values that will
# serve to initialize the console driver.
#
# The optional hash values can include:
#
# AllowCommandPassthru
#
# Whether or not unknown commands should be passed through and executed by
# the local system.
#
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
if (Rex::Compat.is_windows())
# Disable the color support
prompt = "msf"
prompt_char = ">"
end
# Call the parent
super(prompt, prompt_char)
# Temporarily disable output
self.disable_output = true
# Load pre-configuration
load_preconfig
# Initialize attributes
self.framework = opts['Framework'] || Msf::Simple::Framework.create
# Initialize the user interface to use a different input and output
# handle if one is supplied
if (opts['LocalInput'] or opts['LocalOutput'])
init_ui(
opts['LocalInput'],
opts['LocalOutput'])
end
# Add the core command dispatcher as the root of the dispatcher
# stack
enstack_dispatcher(CommandDispatcher::Core)
# Register event handlers
register_event_handlers
# Load console-specific configuration
load_config(opts['Config'])
# Re-enable output
self.disable_output = false
# Load additional modules as necessary
self.framework.modules.add_module_path(opts['ModulePath'], false) if opts['ModulePath']
# Process things before we actually display the prompt and get rocking
on_startup
# Process the resource script
load_resource(opts['Resource'])
# Whether or not command passthru should be allowed
self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true
# Disables "dangerous" functionality of the console
@defanged = opts['Defanged'] == true
# If we're defanged, then command passthru should be disabled
if @defanged
self.command_passthru = false
end
end
#
# Loads configuration that needs to be analyzed before the framework
# instance is created.
#
def load_preconfig
begin
conf = Msf::Config.load
rescue
wlog("Failed to load configuration: #{$!}")
return
end
if (conf.group?(ConfigCore))
conf[ConfigCore].each_pair { |k, v|
on_variable_set(true, k, v)
}
end
end
#
# Loads configuration for the console.
#
def load_config(path=nil)
begin
conf = Msf::Config.load(path)
rescue
wlog("Failed to load configuration: #{$!}")
return
end
# If we have configuration, process it
if (conf.group?(ConfigGroup))
conf[ConfigGroup].each_pair { |k, v|
case k.downcase
when "activemodule"
run_single("use #{v}")
end
}
end
end
#
# Saves configuration for the console.
#
def save_config
# Build out the console config group
group = {}
if (active_module)
group['ActiveModule'] = active_module.fullname
end
# Save it
begin
Msf::Config.save(
ConfigGroup => group)
rescue
log_error("Failed to save console config: #{$!}")
end
end
#
# Processes the resource script file for the console.
#
def load_resource(path=nil)
path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc')
return if not File.readable?(path)
rcfd = File.open(path, 'r')
rcfd.each_line do |line|
print_line("resource> #{line.strip}")
run_single(line.strip)
end
rcfd.close
end
#
# Creates the resource script file for the console.
#
def save_resource(data, path=nil)
path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc')
begin
rcfd = File.open(path, 'w')
rcfd.write(data)
rcfd.close
rescue ::Exception => e
end
end
#
# Called before things actually get rolling such that banners can be
# displayed, scripts can be processed, and other fun can be had.
#
def on_startup
# Check for modules that failed to load
if (framework.modules.failed.length > 0)
print("[*] WARNING! The following modules could not be loaded!\n\n")
framework.modules.failed.each_pair do |file, err|
print("\t#{file}: #{err.to_s}\n\n")
end
print("\n")
end
# Build the banner message
run_single("banner")
end
#
# Called when a variable is set to a specific value. This allows the
# console to do extra processing, such as enabling logging or doing
# some other kind of task. If this routine returns false it will indicate
# that the variable is not being set to a valid value.
#
def on_variable_set(glob, var, val)
case var.downcase
when "payload"
if (framework and framework.modules.valid?(val) == false)
return false
elsif (active_module)
active_module.datastore.clear_non_user_defined
elsif (framework)
framework.datastore.clear_non_user_defined
end
when "sessionlogging"
handle_session_logging(val) if (glob)
when "consolelogging"
handle_console_logging(val) if (glob)
when "loglevel"
handle_loglevel(val) if (glob)
end
end
#
# Called when a variable is unset. If this routine returns false it is an
# indication that the variable should not be allowed to be unset.
#
def on_variable_unset(glob, var)
case var.downcase
when "sessionlogging"
handle_session_logging('0') if (glob)
when "consolelogging"
handle_console_logging('0') if (glob)
when "loglevel"
handle_loglevel(nil) if (glob)
end
end
#
# The framework instance associated with this driver.
#
attr_reader :framework
#
# Whether or not commands can be passed through.
#
attr_reader :command_passthru
#
# The active module associated with the driver.
#
attr_accessor :active_module
#
# The active session associated with the driver.
#
attr_accessor :active_session
#
# If defanged is true, dangerous functionality, such as exploitation, irb,
# and command shell passthru is disabled. In this case, an exception is
# raised.
#
def defanged?
if @defanged
raise DefangedException
end
end
protected
attr_writer :framework # :nodoc:
attr_writer :command_passthru # :nodoc:
#
# If an unknown command was passed, try to see if it's a valid local
# executable. This is only allowed if command passthru has been permitted
#
def unknown_command(method, line)
if (command_passthru == true and Rex::FileUtils.find_full_path(method))
print_status("Executing: `#{line}`")
print_line('')
io = ::IO.popen(line, "r")
io.each_line do |data|
print(data)
end
io.close
else
super
end
end
##
#
# Handlers for various global configuration values
#
##
#
# SessionLogging.
#
def handle_session_logging(val)
if (val =~ /^(yes|y|true|t|1)/i)
Msf::Logging.enable_session_logging(true)
print_line("Session logging will be enabled for future sessions.")
else
Msf::Logging.enable_session_logging(false)
print_line("Session logging will be disabled for future sessions.")
end
end
#
# ConsoleLogging.
#
def handle_console_logging(val)
if (val =~ /^(yes|y|true|t|1)/i)
Msf::Logging.enable_log_source('console')
print_line("Console logging is now enabled.")
set_log_source('console')
rlog("\n[*] Console logging started: #{Time.now}\n\n", 'console')
else
rlog("\n[*] Console logging stopped: #{Time.now}\n\n", 'console')
unset_log_source
Msf::Logging.disable_log_source('console')
print_line("Console logging is now disabled.")
end
end
#
# This method handles adjusting the global log level threshold.
#
def handle_loglevel(val)
set_log_level(Rex::LogSource, val)
set_log_level(Msf::LogSource, val)
end
end
#
# This exception is used to indicate that functionality is disabled due to
# defanged being true
#
class DefangedException < ::Exception
def to_s
"This functionality is currently disabled (defanged mode)"
end
end
end
end
end