alias.rb 12 KB

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