forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathalias.rb
346 lines (314 loc) · 12.3 KB
/
alias.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
##
# $Id$
# $Revision$
##
require 'rex/ui/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::Ui::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 << 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