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