require 'rex/text/table' module Msf class Plugin::Alias < Msf::Plugin class AliasCommandDispatcher include Msf::Ui::Console::CommandDispatcher attr_reader :aliases def initialize(driver) super(driver) @aliases = {} end def name "Alias" end @@alias_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ], "-c" => [ true, "Clear an alias (* to clear all)."], "-f" => [ true, "Force an alias assignment." ] ) # # Returns the hash of commands supported by this dispatcher. # def commands # driver.dispatcher_stack[3].commands { "alias" => "create or view an alias." # "alias_clear" => "clear an alias (or all aliases).", # "alias_force" => "Force an alias (such as to override)" }.merge(aliases) # make aliased commands available as commands of their own end # # the main alias command handler # # usage: alias [options] [name [value]] def cmd_alias(*args) # we parse args manually instead of using @@alias.opts.parse to handle special cases case args.length when 0 # print the list of current aliases if @aliases.length == 0 return print_status("No aliases currently defined") else tbl = Rex::Text::Table.new( 'Header' => "Current Aliases", 'Prefix' => "\n", 'Postfix' => "\n", 'Columns' => [ '', 'Alias Name', 'Alias Value' ] ) # add 'alias' in front of each row so that the output can be copy pasted into an rc file if desired @aliases.each_pair do |key,val| tbl << ["alias",key,val] end return print(tbl.to_s) end when 1 # display the alias if one matches this name (or help) return cmd_alias_help if args[0] == "-h" or args[0] == "--help" if @aliases.keys.include?(args[0]) print_status("\'#{args[0]}\' is aliased to \'#{@aliases[args[0]]}\'") else print_status("\'#{args[0]}\' is not currently aliased") end else # let's see if we can assign or clear the alias force = false clear = false # if using -f or -c, they must be the first arg, because -f/-c may also show up in the alias # value so we can't do something like if args.include("-f") or delete_if etc # we should never have to force and clear simultaneously. if args[0] == "-f" force = true args.shift elsif args[0] == "-c" clear = true args.shift end name = args.shift # alias name can NEVER be certain reserved words like 'alias', add any other reserved words here # We prevent the user from naming the alias "alias" cuz they could end up unable to clear the aliases, # for example you 'alias -f set unset and then 'alias -f alias sessions', now you're screwed. The byproduct # of this is that it prevents you from aliasing 'alias' to 'alias -f' etc, but that's acceptable reserved_words = [/^alias$/i] reserved_words.each do |regex| if name =~ regex print_error "You cannot use #{name} as the name for an alias, sorry" return false end end if clear # clear all aliases if "*" if name == "*" @aliases.keys.each do |a| deregister_alias(a) end print_status "Cleared all aliases" else # clear the named alias if it exists if @aliases.keys.include?(name) deregister_alias(name) print_status "Cleared alias #{name}" else print_error("#{name} is not a currently active alias") end end return end # smash everything that's left together value = args.join(" ") value.strip! # value can NEVER be certain bad words like 'rm -rf /', add any other reserved words here # this is basic idiot protection, not meant to be impervious to subversive intentions reserved_words = [/^rm +(-rf|-r +-f|-f +-r) +\/.*$/] reserved_words.each do |regex| if value =~ regex print_error "You cannot use #{value} as the value for an alias, sorry" return false end end is_valid_alias = is_valid_alias?(name,value) #print_good "Alias validity = #{is_valid_alias.to_s}" is_sys_cmd = Rex::FileUtils.find_full_path(name) is_already_alias = @aliases.keys.include?(name) if is_valid_alias and not is_sys_cmd and not is_already_alias register_alias(name, value) elsif force if not is_valid_alias print_status "The alias failed validation, but force is set so we allow this. This is often the case" print_status "when for instance 'exploit' is being overridden but msfconsole is not currently in the" print_status "exploit context (an exploit is not loaded), or you are overriding a system command" end register_alias(name, value) else print_error("#{name} already exists as a system command, use -f to force override") if is_sys_cmd print_error("#{name} is already an alias, use -f to force override") if is_already_alias if not is_valid_alias and not force print_error("\'#{name}\' is not a permitted name or \'#{value}\' is not valid/permitted") print_error("It's possible the responding dispatcher isn't loaded yet, try changing to the proper context or using -f to force") end end end end def cmd_alias_help print_line "Usage: alias [options] [name [value]]" print_line print(@@alias_opts.usage()) end # # Tab completion for the alias command # def cmd_alias_tabs(str, words) if words.length <= 1 #puts "1 word or less" return @@alias_opts.fmt.keys + tab_complete_aliases_and_commands else #puts "more than 1 word" return tab_complete_aliases_and_commands end end private # # do everything needed to add an alias of +name+ having the value +value+ # def register_alias(name, value) #TODO: begin rescue? #TODO: security concerns since we are using eval # define some class instance methods self.class_eval do # define a class instance method that will respond for the alias define_method "cmd_#{name}" do |*args| # just replace the alias w/the alias' value and run that driver.run_single("#{value} #{args.join(' ')}") end # define a class instance method that will tab complete the aliased command # we just proxy to the top-level tab complete function and let them handle it define_method "cmd_#{name}_tabs" do |str, words| # we need to repair the tab complete string/words and pass back # replace alias name with the root alias value value_words = value.split(/[\s\t\n]+/) # in case value is e.g. 'sessions -l' # valwords is now [sessions,-l] words[0] = value_words[0] # words[0] is now 'sessions' (was 'sue') value_words.shift # valwords is now ['-l'] # insert any remaining parts of value and rebuild the line line = words.join(" ") + " " + value_words.join(" ") + " " + str #print_good "passing (#{line.strip}) back to tab_complete" # clear current tab_words driver.tab_words = [] driver.tab_complete(line.strip) end # add a cmd_#{name}_help method define_method "cmd_#{name}_help" do |*args| driver.run_single("help #{value}") end end # add the alias to the list @aliases[name] = value end # # do everything required to remove an alias of name +name+ # def deregister_alias(name) self.class_eval do # remove the class methods we created when the alias was registered remove_method("cmd_#{name}") remove_method("cmd_#{name}_tabs") remove_method("cmd_#{name}_help") end # remove the alias from the list of active aliases @aliases.delete(name) end # # Validate a proposed alias with the +name+ and having the value +value+ # def is_valid_alias?(name,value) #print_good "Assessing validay for #{name} and #{value}" # we validate two things, the name and the value ### name # we don't check if this alias name exists or if it's a console command already etc as -f can override # that so those need to be checked externally, we pretty much just check to see if the name is sane name.strip! bad_words = [/\*/] # add any additional "bad word" regexes here bad_words.each do |regex| # don't mess around, just return false in this case, prevents wasted processing return false if name =~ regex end ### value # value is considered valid if it's a ref to a valid console cmd, a system executable, or an existing # alias AND isn't a "bad word" # Here we check for "bad words" to avoid for the value...value would have to NOT match these regexes # this is just basic idiot protection value.strip! bad_words = [/^msfconsole$/] bad_words.each do |regex| # don't mess around, just return false if we match return false if value =~ regex end # we're only gonna validate the first part of the cmd, e.g. just ls from "ls -lh" value = value.split(" ").first if @aliases.keys.include?(value) return true else [value, value+".exe"].each do |cmd| if Rex::FileUtils.find_full_path(cmd) return true end end end # gather all the current commands the driver's dispatcher's have & check 'em driver.dispatcher_stack.each do |dispatcher| next unless dispatcher.respond_to?(:commands) next if (dispatcher.commands.nil?) next if (dispatcher.commands.length == 0) if dispatcher.respond_to?("cmd_#{value.split(" ").first}") #print_status "Dispatcher (#{dispatcher.name}) responds to cmd_#{value.split(" ").first}" return true else #print_status "Dispatcher (#{dispatcher.name}) does not respond to cmd_#{value.split(" ").first}" end end return false end # # Provide tab completion list for aliases and commands # def tab_complete_aliases_and_commands items = [] # gather all the current commands the driver's dispatcher's have driver.dispatcher_stack.each do |dispatcher| next unless dispatcher.respond_to?(:commands) next if (dispatcher.commands.nil? or dispatcher.commands.length == 0) items.concat(dispatcher.commands.keys) end # add all the current aliases to the list items.concat(@aliases.keys) return items end end # end AliasCommandDispatcher class # # The constructor is called when an instance of the plugin is created. The # framework instance that the plugin is being associated with is passed in # the framework parameter. Plugins should call the parent constructor when # inheriting from Msf::Plugin to ensure that the framework attribute on # their instance gets set. # def initialize(framework, opts) super ## Register the commands above add_console_dispatcher(AliasCommandDispatcher) end # # The cleanup routine for plugins gives them a chance to undo any actions # they may have done to the framework. For instance, if a console # dispatcher was added, then it should be removed in the cleanup routine. # def cleanup # If we had previously registered a console dispatcher with the console, # deregister it now. remove_console_dispatcher('Alias') # we don't need to remove class methods we added because they were added to # AliasCommandDispatcher class end # # This method returns a short, friendly name for the plugin. # def name "alias" end # # This method returns a brief description of the plugin. It should be no # more than 60 characters, but there are no hard limits. # def desc "Adds the ability to alias console commands" end end ## End Plugin Class end ## End Module