123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- 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.
- #
- # driver.dispatcher_stack[3].commands
- def 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.empty?
- 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') || (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.each_key do |a|
- deregister_alias(a)
- end
- print_status 'Cleared all aliases'
- elsif @aliases.keys.include?(name) # clear the named alias if it exists
- deregister_alias(name)
- print_status "Cleared alias #{name}"
- else
- print_error("#{name} is not a currently active alias")
- 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 = [%r{^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 = valid_alias?(name, value)
- # print_good "Alias validity = #{is_valid_alias}"
- is_sys_cmd = Rex::FileUtils.find_full_path(name)
- is_already_alias = @aliases.keys.include?(name)
- if is_valid_alias && !is_sys_cmd && !is_already_alias
- register_alias(name, value)
- elsif force
- if !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 !is_valid_alias && !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.option_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
- 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
- [driver.tab_complete(line.strip), :override_completions]
- 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)
- 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 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
- return true if @aliases.keys.include?(value)
- [value, value + '.exe'].each do |cmd|
- return true if Rex::FileUtils.find_full_path(cmd)
- 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.empty?
- if dispatcher.respond_to?("cmd_#{value.split(' ').first}")
- # print_status "Dispatcher (#{dispatcher.name}) responds to cmd_#{value.split(" ").first}"
- return true
- end
- end
- 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? || dispatcher.commands.empty?)
- items.concat(dispatcher.commands.keys)
- end
- # add all the current aliases to the list
- items.concat(@aliases.keys)
- return items
- end
- end
- #
- # 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
|