CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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