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