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