|
| 1 | +## |
| 2 | +# $Id$ |
| 3 | +# $Revision$ |
| 4 | +## |
| 5 | + |
| 6 | +require 'rex/ui/text/table' |
| 7 | + |
| 8 | +module Msf |
| 9 | + |
| 10 | +class Plugin::Alias < Msf::Plugin |
| 11 | + class AliasCommandDispatcher |
| 12 | + include Msf::Ui::Console::CommandDispatcher |
| 13 | + |
| 14 | + attr_reader :aliases |
| 15 | + def initialize(driver) |
| 16 | + super(driver) |
| 17 | + @aliases = {} |
| 18 | + end |
| 19 | + |
| 20 | + def name |
| 21 | + "Alias" |
| 22 | + end |
| 23 | + |
| 24 | + @@alias_opts = Rex::Parser::Arguments.new( |
| 25 | + "-h" => [ false, "Help banner." ], |
| 26 | + "-c" => [ true, "Clear an alias (* to clear all)."], |
| 27 | + "-f" => [ true, "Force an alias assignment." ] |
| 28 | + ) |
| 29 | + # |
| 30 | + # Returns the hash of commands supported by this dispatcher. |
| 31 | + # |
| 32 | + def commands # driver.dispatcher_stack[3].commands |
| 33 | + { |
| 34 | + "alias" => "create or view an alias." |
| 35 | + # "alias_clear" => "clear an alias (or all aliases).", |
| 36 | + # "alias_force" => "Force an alias (such as to override)" |
| 37 | + }.merge(aliases) # make aliased commands available as commands of their own |
| 38 | + end |
| 39 | + |
| 40 | + # |
| 41 | + # the main alias command handler |
| 42 | + # |
| 43 | + # usage: alias [options] [name [value]] |
| 44 | + def cmd_alias(*args) |
| 45 | + # we parse args manually instead of using @@alias.opts.parse to handle special cases |
| 46 | + case args.length |
| 47 | + when 0 # print the list of current aliases |
| 48 | + if @aliases.length == 0 |
| 49 | + return print_status("No aliases currently defined") |
| 50 | + else |
| 51 | + tbl = Rex::Ui::Text::Table.new( |
| 52 | + 'Header' => "Current Aliases", |
| 53 | + 'Prefix' => "\n", |
| 54 | + 'Postfix' => "\n", |
| 55 | + 'Columns' => [ 'Alias Name', 'Alias Value' ] |
| 56 | + ) |
| 57 | + @aliases.each_pair do |key,val| |
| 58 | + tbl << [key,val] |
| 59 | + end |
| 60 | + return print(tbl.to_s) |
| 61 | + end |
| 62 | + when 1 # display the alias if one matches this name (or help) |
| 63 | + return cmd_alias_help if args[0] == "-h" or args[0] == "--help" |
| 64 | + if @aliases.keys.include?(args[0]) |
| 65 | + print_status("\'#{args[0]}\' is aliased to \'#{@aliases[args[0]]}\'") |
| 66 | + else |
| 67 | + print_status("\'#{args[0]}\' is not currently aliased") |
| 68 | + end |
| 69 | + else # let's see if we can assign or clear the alias |
| 70 | + force = false |
| 71 | + clear = false |
| 72 | + # if using -f or -c, they must be the first arg, because -f/-c may also show up in the alias |
| 73 | + # value so we can't do something like if args.include("-f") or delete_if etc |
| 74 | + # we sould never have to force and clear simultaneously. |
| 75 | + if args[0] == "-f" |
| 76 | + force = true |
| 77 | + args.shift |
| 78 | + elsif args[0] == "-c" |
| 79 | + clear = true |
| 80 | + args.shift |
| 81 | + end |
| 82 | + name = args.shift |
| 83 | + print_good "The alias name is #{name}" |
| 84 | + if clear |
| 85 | + # clear all aliases if "*" |
| 86 | + if name == "*" |
| 87 | + @aliases.keys.each do |a| |
| 88 | + deregister_alias(a) |
| 89 | + end |
| 90 | + print_status "Cleared all aliases" |
| 91 | + else # clear the named alias if it exists |
| 92 | + print_status "Checking alias #{name} for clear" |
| 93 | + deregister_alias(name) if @aliases.keys.include?(name) |
| 94 | + print_status "Cleared alias #{name}" |
| 95 | + end |
| 96 | + return |
| 97 | + end |
| 98 | + # smash everything that's left together |
| 99 | + value = args.join(" ") |
| 100 | + |
| 101 | + if is_valid_alias?(name,value) |
| 102 | + if force or (not Rex::FileUtils.find_full_path(name) and not @aliases.keys.include?(name)) |
| 103 | + register_alias(name, value) |
| 104 | + else |
| 105 | + print_error("#{name} already exists as system command or current alias, use -f to force") |
| 106 | + end |
| 107 | + else |
| 108 | + print_error("\'#{name}\' is not a permitted name or \'#{value}\' is not a valid/permitted console or system command") |
| 109 | + end |
| 110 | + end |
| 111 | + end |
| 112 | + |
| 113 | + def cmd_alias_help |
| 114 | + print_line "Usage: alias [options] [name [value]]" |
| 115 | + print_line |
| 116 | + print(@@alias_opts.usage()) |
| 117 | + end |
| 118 | + |
| 119 | + # |
| 120 | + # Tab completion for the alias command |
| 121 | + # |
| 122 | + def cmd_alias_tabs(str, words) |
| 123 | + if words.length <= 1 |
| 124 | + return @@alias_opts.fmt.keys + tab_complete_aliases_and_commands(str, words) |
| 125 | + else |
| 126 | + return tab_complete_aliases_and_commands(str, words) |
| 127 | + end |
| 128 | + end |
| 129 | + |
| 130 | + private |
| 131 | + # |
| 132 | + # do everything needed to add an alias of +name+ having the value +value+ |
| 133 | + # |
| 134 | + def register_alias(name, value) |
| 135 | + #TODO: begin rescue? |
| 136 | + #TODO: security concerns since we are using eval |
| 137 | + |
| 138 | + # define some class instance methods |
| 139 | + self.class_eval do |
| 140 | + # define a class instance method that will respond for the alias |
| 141 | + define_method "cmd_#{name}" do |*args| |
| 142 | + # just replace the alias w/the alias' value and run that |
| 143 | + driver.run_single("#{value} #{args.join(' ')}") |
| 144 | + end |
| 145 | + # define a class instance method that will tab complete the aliased command |
| 146 | + # we just proxy to the top-level tab complete function and let them handle it |
| 147 | + define_method "cmd_#{name}_tabs" do |str, words| |
| 148 | + #print_good "Creating cmd_#{name}_tabs as driver.tab_complete(#{value} #{words.join(' ')})" |
| 149 | + #driver.tab_complete("MONKEY") |
| 150 | + words.delete(name) |
| 151 | + driver.tab_complete("#{value} #{words.join(' ')}") |
| 152 | + end |
| 153 | + # we don't need a cmd_#{name}_help method, we just let the original handle that |
| 154 | + end |
| 155 | + # add the alias to the list |
| 156 | + @aliases[name] = value |
| 157 | + end |
| 158 | + |
| 159 | + # |
| 160 | + # do everything required to remove an alias of name +name+ |
| 161 | + # |
| 162 | + def deregister_alias(name) |
| 163 | + self.class_eval do |
| 164 | + # remove the methods we defined for this alias |
| 165 | + remove_method("cmd_#{name}") |
| 166 | + remove_method("cmd_#{name}_tab") |
| 167 | + end |
| 168 | + end |
| 169 | + |
| 170 | + # |
| 171 | + # Validate a proposed alias |
| 172 | + # |
| 173 | + def is_valid_alias?(name,value) |
| 174 | + # some "bad words" to avoid for the value. value would have to not match these regexes |
| 175 | + # this is just basic idiot protection, it's not meant to be "undefeatable" |
| 176 | + value.strip! |
| 177 | + bad_words = [/^rm +(-rf|-r +-f|-f +-r) +\/+.*$/, /^msfconsole$/] |
| 178 | + bad_words.each do |regex| |
| 179 | + # don't mess around, just return false if we match |
| 180 | + return false if value =~ regex |
| 181 | + end |
| 182 | + # we're only gonna validate the first part of the cmd, e.g. just ls from "ls -lh" |
| 183 | + value = value.split(" ").first |
| 184 | + valid_value = false |
| 185 | + |
| 186 | + # value is considered valid if it's a ref to a valid console command or |
| 187 | + # a system executable or existing alias |
| 188 | + |
| 189 | + # gather all the current commands the driver's dispatcher's have & check 'em |
| 190 | + driver.dispatcher_stack.each do |dispatcher| |
| 191 | + next unless dispatcher.respond_to?(:commands) |
| 192 | + next if (dispatcher.commands.nil?) |
| 193 | + next if (dispatcher.commands.length == 0) |
| 194 | + |
| 195 | + if dispatcher.respond_to?("cmd_#{value.split(" ").first}") |
| 196 | + valid_value = true |
| 197 | + break |
| 198 | + end |
| 199 | + end |
| 200 | + if not valid_value # then check elsewhere |
| 201 | + if @aliases.keys.include?(value) |
| 202 | + valid_value = true |
| 203 | + else |
| 204 | + [value, value+".exe"].each do |cmd| |
| 205 | + if Rex::FileUtils.find_full_path(cmd) |
| 206 | + valid_value = true |
| 207 | + end |
| 208 | + end |
| 209 | + end |
| 210 | + end |
| 211 | + # go ahead and return false at this point if the value isn't valid |
| 212 | + return false if not valid_value |
| 213 | + |
| 214 | + # we don't check if this alias name exists or if it's a console command already etc as |
| 215 | + # -f can override that so those need to be checked externally. |
| 216 | + # We pretty much just check to see if the name is sane |
| 217 | + valid_name = true |
| 218 | + name.strip! |
| 219 | + bad_words = [/^alias$/,/\*/] |
| 220 | + # there are probably a bunch of others that need to be added here. We prevent the user |
| 221 | + # from naming the alias "alias" cuz they can end up unable to clear the aliases |
| 222 | + # for example you 'alias -f set unse't and then 'alias -f alias sessions', now you're |
| 223 | + # screwed. This prevents you from aliasing alias to alias -f etc, but no biggie. |
| 224 | + bad_words.each do |regex| |
| 225 | + # don't mess around, just return false in this case, prevents wasted processing |
| 226 | + return false if name =~ regex |
| 227 | + end |
| 228 | + |
| 229 | + return valid_name |
| 230 | + end |
| 231 | + |
| 232 | + # |
| 233 | + # Provide tab completion list for aliases and commands |
| 234 | + # |
| 235 | + def tab_complete_aliases_and_commands(str, words) |
| 236 | + items = [] |
| 237 | + items.concat(driver.commands.keys) if driver.respond_to?('commands') |
| 238 | + items.concat(@aliases.keys) |
| 239 | + items |
| 240 | + end |
| 241 | + |
| 242 | + end # end AliasCommandDispatcher class |
| 243 | + |
| 244 | + # |
| 245 | + # The constructor is called when an instance of the plugin is created. The |
| 246 | + # framework instance that the plugin is being associated with is passed in |
| 247 | + # the framework parameter. Plugins should call the parent constructor when |
| 248 | + # inheriting from Msf::Plugin to ensure that the framework attribute on |
| 249 | + # their instance gets set. |
| 250 | + # |
| 251 | + attr_accessor :controller |
| 252 | + |
| 253 | + def initialize(framework, opts) |
| 254 | + super |
| 255 | + |
| 256 | + ## Register the commands above |
| 257 | + add_console_dispatcher(AliasCommandDispatcher) |
| 258 | + end |
| 259 | + |
| 260 | + |
| 261 | + # |
| 262 | + # The cleanup routine for plugins gives them a chance to undo any actions |
| 263 | + # they may have done to the framework. For instance, if a console |
| 264 | + # dispatcher was added, then it should be removed in the cleanup routine. |
| 265 | + # |
| 266 | + def cleanup |
| 267 | + # If we had previously registered a console dispatcher with the console, |
| 268 | + # deregister it now. |
| 269 | + remove_console_dispatcher('Alias') |
| 270 | + end |
| 271 | + |
| 272 | + # |
| 273 | + # This method returns a short, friendly name for the plugin. |
| 274 | + # |
| 275 | + def name |
| 276 | + "Alias" |
| 277 | + end |
| 278 | + |
| 279 | + # |
| 280 | + # This method returns a brief description of the plugin. It should be no |
| 281 | + # more than 60 characters, but there are no hard limits. |
| 282 | + # |
| 283 | + def desc |
| 284 | + "Adds the ability to alias console commands" |
| 285 | + end |
| 286 | + |
| 287 | +end ## End Plugin Class |
| 288 | +end ## End Module |
0 commit comments