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. Commercial Alternative to JupyterHub.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/recon/local_exploit_suggester.rb
Views: 16263
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
8
include Msf::Auxiliary::Report
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Multi Recon Local Exploit Suggester',
15
'Description' => %q{
16
This module suggests local meterpreter exploits that can be used.
17
18
The exploits are suggested based on the architecture and platform
19
that the user has a shell opened as well as the available exploits
20
in meterpreter.
21
22
It's important to note that not all local exploits will be fired.
23
Exploits are chosen based on these conditions: session type,
24
platform, architecture, and required default options.
25
},
26
'License' => MSF_LICENSE,
27
'Author' => [ 'sinn3r', 'Mo' ],
28
'Platform' => all_platforms,
29
'SessionTypes' => [ 'meterpreter', 'shell' ]
30
)
31
)
32
register_options [
33
Msf::OptInt.new('SESSION', [ true, 'The session to run this module on' ]),
34
Msf::OptBool.new('SHOWDESCRIPTION', [true, 'Displays a detailed description for the available exploits', false])
35
]
36
37
register_advanced_options(
38
[
39
Msf::OptBool.new('ValidateArch', [true, 'Validate architecture', true]),
40
Msf::OptBool.new('ValidatePlatform', [true, 'Validate platform', true]),
41
Msf::OptBool.new('ValidateMeterpreterCommands', [true, 'Validate Meterpreter commands', false]),
42
Msf::OptString.new('Colors', [false, 'Valid, Invalid and Ignored colors for module checks (unset to disable)', 'grn/red/blu'])
43
]
44
)
45
end
46
47
def all_platforms
48
Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase }
49
end
50
51
def session_arch
52
# Prefer calling native arch when available, as most LPEs will require this (e.g. x86, x64) as opposed to Java/Python Meterpreter's values (e.g. Java, Python)
53
session.respond_to?(:native_arch) ? session.native_arch : session.arch
54
end
55
56
def is_module_arch?(mod)
57
mod_arch = mod.target.arch || mod.arch
58
mod_arch.include?(session_arch)
59
end
60
61
def is_module_wanted?(mod)
62
mod[:result][:incompatibility_reasons].empty?
63
end
64
65
def is_session_type?(mod)
66
# There are some modules that do not define any compatible session types.
67
# We could assume that means the module can run on all session types,
68
# Or we could consider that as incorrect module metadata.
69
mod.session_types.include?(session.type)
70
end
71
72
def is_module_platform?(mod)
73
platform_obj = Msf::Module::Platform.find_platform session.platform
74
return false if mod.target.nil?
75
76
module_platforms = mod.target.platform ? mod.target.platform.platforms : mod.platform.platforms
77
module_platforms.include? platform_obj
78
rescue ArgumentError => e
79
# When not found, find_platform raises an ArgumentError
80
elog('Could not find a platform', error: e)
81
return false
82
end
83
84
def has_required_module_options?(mod)
85
get_all_missing_module_options(mod).empty?
86
end
87
88
def get_all_missing_module_options(mod)
89
missing_options = []
90
mod.options.each_pair do |option_name, option|
91
missing_options << option_name if option.required && option.default.nil? && mod.datastore[option_name].blank?
92
end
93
missing_options
94
end
95
96
def valid_incompatibility_reasons(mod, verify_reasons)
97
# As we can potentially ignore some `reasons` (e.g. accepting arch values which are, on paper, not compatible),
98
# this keeps track of valid reasons why we will not consider the module that we are evaluating to be valid.
99
valid_reasons = []
100
valid_reasons << "Missing required module options (#{get_all_missing_module_options(mod).join('. ')})" unless verify_reasons[:has_required_module_options]
101
102
incompatible_opts = []
103
incompatible_opts << 'architecture' unless verify_reasons[:is_module_arch]
104
incompatible_opts << 'platform' unless verify_reasons[:is_module_platform]
105
incompatible_opts << 'session type' unless verify_reasons[:is_session_type]
106
valid_reasons << "Not Compatible (#{incompatible_opts.join(', ')})" if incompatible_opts.any?
107
108
valid_reasons << 'Missing/unloadable Meterpreter commands' if verify_reasons[:missing_meterpreter_commands].any?
109
valid_reasons
110
end
111
112
def set_module_options(mod)
113
ignore_list = ['ACTION', 'TARGET'].freeze
114
datastore.each_pair do |k, v|
115
mod.datastore[k] = v unless ignore_list.include?(k.upcase)
116
end
117
if !mod.datastore['SESSION'] && session.present?
118
mod.datastore['SESSION'] = session.sid
119
end
120
end
121
122
def set_module_target(mod)
123
session_platform = Msf::Module::Platform.find_platform(session.platform)
124
target_index = mod.targets.find_index do |target|
125
# If the target doesn't define its own compatible platforms or architectures, default to the parent (module) values.
126
target_platforms = target.platform&.platforms || mod.platform.platforms
127
target_architectures = target.arch || mod.arch
128
129
target_platforms.include?(session_platform) && target_architectures.include?(session_arch)
130
end
131
mod.datastore['Target'] = target_index if target_index
132
end
133
134
def setup
135
return unless session
136
137
print_status "Collecting local exploits for #{session.session_type}..."
138
139
setup_validation_options
140
setup_color_options
141
142
# Collects exploits into an array
143
@local_exploits = []
144
exploit_refnames = framework.exploits.module_refnames
145
exploit_refnames.each_with_index do |name, index|
146
print "%bld%blu[*]%clr Collecting exploit #{index + 1} / #{exploit_refnames.count}\r"
147
mod = framework.exploits.create name
148
next unless mod
149
150
set_module_options mod
151
set_module_target mod
152
153
verify_result = verify_mod(mod)
154
@local_exploits << { module: mod, result: verify_result } if verify_result[:has_check]
155
end
156
end
157
158
def verify_mod(mod)
159
return { has_check: false } unless mod.is_a?(Msf::Exploit::Local) && mod.has_check?
160
161
result = {
162
has_check: true,
163
is_module_platform: (@validate_platform ? is_module_platform?(mod) : true),
164
is_module_arch: (@validate_arch ? is_module_arch?(mod) : true),
165
has_required_module_options: has_required_module_options?(mod),
166
missing_meterpreter_commands: (@validate_meterpreter_commands && session.type == 'meterpreter') ? meterpreter_session_incompatibility_reasons(session) : [],
167
is_session_type: is_session_type?(mod)
168
}
169
result[:incompatibility_reasons] = valid_incompatibility_reasons(mod, result)
170
result
171
end
172
173
def setup_validation_options
174
@validate_arch = datastore['ValidateArch']
175
@validate_platform = datastore['ValidatePlatform']
176
@validate_meterpreter_commands = datastore['ValidateMeterpreterCommands']
177
end
178
179
def setup_color_options
180
@valid_color, @invalid_color, @ignored_color =
181
(datastore['Colors'] || '').split('/')
182
183
@valid_color = "%#{@valid_color}" unless @valid_color.blank?
184
@invalid_color = "%#{@invalid_color}" unless @invalid_color.blank?
185
@ignored_color = "%#{@ignored_color}" unless @ignored_color.blank?
186
end
187
188
def show_found_exploits
189
unless datastore['VERBOSE']
190
print_status "#{@local_exploits.length} exploit checks are being tried..."
191
return
192
end
193
194
vprint_status "The following #{@local_exploits.length} exploit checks are being tried:"
195
@local_exploits.each do |x|
196
vprint_status x[:module].fullname
197
end
198
end
199
200
def run
201
runnable_exploits = @local_exploits.select { |mod| is_module_wanted?(mod) }
202
if runnable_exploits.empty?
203
print_error 'No suggestions available.'
204
vprint_line
205
vprint_session_info
206
vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) })
207
return
208
end
209
210
show_found_exploits
211
results = runnable_exploits.map.with_index do |mod, index|
212
print "%bld%blu[*]%clr Running check method for exploit #{index + 1} / #{runnable_exploits.count}\r"
213
begin
214
checkcode = mod[:module].check
215
rescue StandardError => e
216
elog("#Local Exploit Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e)
217
vprint_error "Check with module #{mod[:module].fullname} failed with error #{e.class}"
218
next { module: mod[:module], errors: ['The check raised an exception.'] }
219
end
220
221
if checkcode.nil?
222
vprint_error "Check failed with #{mod[:module].fullname} for unknown reasons"
223
next { module: mod[:module], errors: ['The check failed for unknown reasons.'] }
224
end
225
226
# See def is_check_interesting?
227
unless is_check_interesting? checkcode
228
vprint_status "#{mod[:module].fullname}: #{checkcode.message}"
229
next { module: mod[:module], errors: [checkcode.message] }
230
end
231
232
# Prints the full name and the checkcode message for the exploit
233
print_good "#{mod[:module].fullname}: #{checkcode.message}"
234
235
# If the datastore option is true, a detailed description will show
236
if datastore['SHOWDESCRIPTION']
237
# Formatting for the description text
238
Rex::Text.wordwrap(Rex::Text.compress(mod[:module].description), 2, 70).split(/\n/).each do |line|
239
print_line line
240
end
241
end
242
243
next { module: mod[:module], checkcode: checkcode.message }
244
end
245
246
print_line
247
print_status valid_modules_table(results)
248
249
vprint_line
250
vprint_session_info
251
vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) })
252
253
report_data = []
254
results.each do |result|
255
report_data << [result[:module].fullname, result[:checkcode]] if result[:checkcode]
256
end
257
report_note(
258
host: session.session_host,
259
type: 'local.suggested_exploits',
260
data: report_data
261
)
262
end
263
264
def valid_modules_table(results)
265
name_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
266
check_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
267
268
# Split all the results by their checkcode.
269
# We want the modules that returned a checkcode to be at the top.
270
checkcode_rows, without_checkcode_rows = results.partition { |result| result[:checkcode] }
271
rows = (checkcode_rows + without_checkcode_rows).map.with_index do |result, index|
272
color = result[:checkcode] ? @valid_color : @invalid_color
273
check_res = result.fetch(:checkcode) { result[:errors].join('. ') }
274
name_styler.merge!({ result[:module].fullname => color })
275
check_styler.merge!({ check_res => color })
276
277
[
278
index + 1,
279
result[:module].fullname,
280
result[:checkcode] ? 'Yes' : 'No',
281
check_res
282
]
283
end
284
285
Rex::Text::Table.new(
286
'Header' => "Valid modules for session #{session.sid}:",
287
'Indent' => 1,
288
'Columns' => [ '#', 'Name', 'Potentially Vulnerable?', 'Check Result' ],
289
'SortIndex' => -1,
290
'WordWrap' => false, # Don't wordwrap as it messes up coloured output when it is broken up into more than one line
291
'ColProps' => {
292
'Name' => {
293
'Stylers' => [name_styler]
294
},
295
'Potentially Vulnerable?' => {
296
'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => @valid_color, 'No' => @invalid_color })]
297
},
298
'Check Result' => {
299
'Stylers' => [check_styler]
300
}
301
},
302
'Rows' => rows
303
)
304
end
305
306
def unwanted_modules_table(unwanted_modules)
307
arch_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
308
platform_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
309
session_type_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
310
311
rows = unwanted_modules.map.with_index do |mod, index|
312
platforms = mod[:module].target.platform&.platforms&.any? ? mod[:module].target.platform.platforms : mod[:module].platform.platforms
313
platforms ||= []
314
arch = mod[:module].target.arch&.any? ? mod[:module].target.arch : mod[:module].arch
315
arch ||= []
316
317
arch.each do |a|
318
if a != session_arch
319
if @validate_arch
320
color = @invalid_color
321
else
322
color = @ignored_color
323
end
324
else
325
color = @valid_color
326
end
327
328
arch_styler.merge!({ a.to_s => color })
329
end
330
331
platforms.each do |module_platform|
332
if module_platform != ::Msf::Module::Platform.find_platform(session.platform)
333
if @validate_platform
334
color = @invalid_color
335
else
336
color = @ignored_color
337
end
338
else
339
color = @valid_color
340
end
341
342
platform_styler.merge!({ module_platform.realname => color })
343
end
344
345
mod[:module].session_types.each do |session_type|
346
color = session_type == session.type ? @valid_color : @invalid_color
347
session_type_styler.merge!(session_type.to_s => color)
348
end
349
350
[
351
index + 1,
352
mod[:module].fullname,
353
mod[:result][:incompatibility_reasons].join('. '),
354
platforms.map(&:realname).sort.join(', '),
355
arch.any? ? arch.sort.join(', ') : 'No defined architectures',
356
mod[:module].session_types.any? ? mod[:module].session_types.sort.join(', ') : 'No defined session types'
357
]
358
end
359
360
Rex::Text::Table.new(
361
'Header' => "Incompatible modules for session #{session.sid}:",
362
'Indent' => 1,
363
'Columns' => [ '#', 'Name', 'Reasons', 'Platform', 'Architecture', 'Session Type' ],
364
'WordWrap' => false,
365
'ColProps' => {
366
'Architecture' => {
367
'Stylers' => [arch_styler]
368
},
369
'Platform' => {
370
'Stylers' => [platform_styler]
371
},
372
'Session Type' => {
373
'Stylers' => [session_type_styler]
374
}
375
},
376
'Rows' => rows
377
)
378
end
379
380
def vprint_session_info
381
vprint_status 'Current Session Info:'
382
vprint_status "Session Type: #{session.type}"
383
vprint_status "Architecture: #{session_arch}"
384
vprint_status "Platform: #{session.platform}"
385
end
386
387
def is_check_interesting?(checkcode)
388
[
389
Msf::Exploit::CheckCode::Vulnerable,
390
Msf::Exploit::CheckCode::Appears,
391
Msf::Exploit::CheckCode::Detected
392
].include? checkcode
393
end
394
395
def print_status(msg = '')
396
super(session ? "#{session.session_host} - #{msg}" : msg)
397
end
398
399
def print_good(msg = '')
400
super(session ? "#{session.session_host} - #{msg}" : msg)
401
end
402
403
def print_error(msg = '')
404
super(session ? "#{session.session_host} - #{msg}" : msg)
405
end
406
end
407
408