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/lib/msf/ui/debug.rb
Views: 11780
1
# -*- coding: binary -*-
2
# frozen_string_literal: true
3
4
module Msf
5
module Ui
6
###
7
#
8
# Displays Metasploit information useful for Debugging.
9
#
10
###
11
module Debug
12
COMMAND_HISTORY_TOTAL = 50
13
FRAMEWORK_LOG_LINE_TOTAL = 50
14
WEB_SERVICE_LOG_LINE_TOTAL = 150
15
16
# "[mm/dd/yyyy hh:mm:ss] [e([ANY_NUMBER])]" Indicates the start of an error message
17
# The end of an error message is indicated by the start of the next log message [mm/dd/yyyy hh:mm:ss] [[ANY_LETTER]([ANY_NUMBER])]
18
#
19
#
20
# When using the commented regex, the below example framework.log will only return three separate errors, and their accompanying traces:
21
#
22
# [05/15/2020 14:13:38] [e(0)] core: [-] Error during IRB: undefined method `[]' for nil:NilClass
23
#
24
# [06/19/2020 12:05:02] [i(0)] core: Trying to continue despite failed database creation: could not connect to server: Connection refused
25
# Is the server running on host "127.0.0.1" and accepting
26
# TCP/IP connections on port 5433?
27
#
28
# [05/15/2020 14:19:20] [e(0)] core: [-] Error while running command debug: can't modify frozen String
29
# Call stack:
30
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/ui/debug.rb:33:in `get_all'
31
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/ui/console/command_dispatcher/core.rb:318:in `cmd_debug'
32
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:523:in `run_command'
33
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:474:in `block in run_single'
34
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:468:in `each'
35
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/dispatcher_shell.rb:468:in `run_single'
36
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/rex/ui/text/shell.rb:158:in `run'
37
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/metasploit/framework/command/console.rb:48:in `start'
38
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/metasploit/framework/command/base.rb:82:in `start'
39
#
40
# [06/19/2020 11:51:44] [d(2)] core: Stager osx/armle/reverse_tcp and stage osx/x64/meterpreter have incompatible architectures: armle - x64
41
#
42
# [05/15/2020 14:23:55] [e(0)] core: [-] Error during IRB: undefined method `[]' for nil:NilClass
43
FRAMEWORK_ERROR_REGEX = %r|\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}\] \[e\(\d+\)\] (?:(?!\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}\] \[[A-Za-z]\(\d+\)\]).)+|m
44
FRAMEWORK_ERROR_TOTAL = 10
45
46
# "[-]" Indicates the start of an error message
47
# The end of an error message is indicated by a \n character followed by any non-whitespace character
48
#
49
# When using the commented regex, the below example msf-ws.log will only return three separate errors, and their accompanying traces:
50
#
51
# [-] Error that does not return a stack trace.
52
# Writing PID to /Users/agalway/.msf4/msf-ws.pid
53
# Thin web server (v1.7.2 codename Bachmanity)
54
# Maximum connections set to 1024
55
# Listening on localhost:5443, CTRL+C to stop
56
#
57
#
58
# [-] Error handling request: wrong number of arguments (given 4, expected 1).
59
# Call Stack:
60
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/db_manager/service.rb:44:in `get_service'
61
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/db_manager/note.rb:136:in `block in report_note'
62
# /Users/agalway/vendor/bundle/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in `with_connection'
63
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/db_manager/note.rb:81:in `report_note'
64
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/web_services/servlet/note_servlet.rb:42:in `block (2 levels) in report_note'
65
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/web_services/servlet_helper.rb:78:in `exec_report_job'
66
# /Users/agalway/vendor/bundle/gems/thin-1.7.2/bin/thin:6:in `<top (required)>'
67
# /Users/agalway/vendor/bundle/bin/thin:23:in `load'
68
# /Users/agalway/vendor/bundle/bin/thin:23:in `<main>'
69
# [-] Error handling request: wrong number of arguments (given 4, expected 1).
70
# Call Stack:
71
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/db_manager/service.rb:44:in `get_service'
72
# /Users/Shared/Relocated_Items/Security/rapid7/metasploit-framework/lib/msf/core/db_manager/note.rb:136:in `block in report_note'
73
# /Users/agalway/vendor/bundle/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in `with_connection'
74
# /Users/agalway/vendor/bundle/gems/thin-1.7.2/bin/thin:6:in `<top (required)>'
75
# /Users/agalway/vendor/bundle/bin/thin:23:in `load'
76
# /Users/agalway/vendor/bundle/bin/thin:23:in `<main>'
77
WEB_SERVICE_ERROR_REGEX = %r|\[-\].+?\n(?!\s)|m
78
WEB_SERVICE_ERROR_TOTAL = 10
79
80
ISSUE_LINK = 'https://github.com/rapid7/metasploit-framework/issues/new/choose'
81
PREAMBLE = <<~PREMABLE
82
Please provide the below information in any Github issues you open. New issues can be opened here #{ISSUE_LINK.dup}
83
%red%undENSURE YOU HAVE REMOVED ANY SENSITIVE INFORMATION BEFORE SUBMITTING!%clr
84
85
===8<=== CUT AND PASTE EVERYTHING BELOW THIS LINE ===8<===
86
87
88
PREMABLE
89
90
ERROR_BLURB = 'An error occurred when trying to build this section:'
91
92
def self.issue_link
93
return ISSUE_LINK.dup
94
end
95
96
def self.preamble
97
return PREAMBLE.dup
98
end
99
100
def self.all(framework, driver)
101
all_information = preamble
102
all_information << datastore(framework, driver)
103
all_information << database_configuration(framework)
104
all_information << framework_config(framework)
105
all_information << history(driver)
106
all_information << errors
107
all_information << logs
108
all_information << versions(framework)
109
110
all_information
111
end
112
113
def self.datastore(framework, driver)
114
115
# Generate an ini with the existing config file
116
ini = Rex::Parser::Ini.new(Msf::Config.config_file)
117
118
# Delete all groups from the config ini that potentially have more up to date information
119
ini.keys.each do |key|
120
unless key.start_with?("framework/database") || key.start_with?("framework/features")
121
ini.delete(key)
122
end
123
end
124
125
# Retrieve and add more up to date information
126
add_hash_to_ini_group(ini, framework.datastore.to_h, driver.get_config_core)
127
add_hash_to_ini_group(ini, driver.get_config, driver.get_config_group)
128
129
if driver.active_module
130
add_hash_to_ini_group(ini, driver.active_module.datastore.to_h, driver.active_module.refname)
131
end
132
133
# Filter credentials
134
ini.each do |key, value|
135
if key =~ %r{^framework/database/}
136
value.transform_values! { '[Filtered]' }
137
end
138
end
139
140
if ini.to_s.empty?
141
content = 'The local config file is empty, no global variables are set, and there is no active module.'
142
else
143
content = ini.to_s
144
end
145
146
build_section(
147
'Module/Datastore',
148
'The following global/module datastore, and database setup was configured before the issue occurred:',
149
content
150
)
151
rescue StandardError => e
152
build_section(
153
'Module/Datastore',
154
ERROR_BLURB,
155
section_build_error('Failed to extract Datastore', e)
156
)
157
end
158
159
def self.database_configuration(framework)
160
output = "```\nSession Type: #{db_connection_info(framework)}\n```\n\n"
161
162
if framework.db&.active
163
current_workspace = framework.db.workspace
164
example_workspaces = ::Mdm::Workspace.order(id: :desc).take(10)
165
ordered_workspaces = ([current_workspace] + example_workspaces).uniq.sort_by(&:id)
166
workspace_rows = ordered_workspaces.map do |workspace|
167
id = current_workspace.id == workspace.id ? "#{workspace.id.to_fs(:delimited)} **(Current)**" : workspace.id.to_fs(:delimited)
168
[
169
id,
170
workspace.hosts.count.to_fs(:delimited),
171
workspace.vulns.count.to_fs(:delimited),
172
workspace.notes.count.to_fs(:delimited),
173
workspace.services.count.to_fs(:delimited)
174
]
175
end
176
177
totals_row = [
178
"**Total (#{::Mdm::Workspace.count.to_fs(:delimited)})**",
179
"**#{::Mdm::Host.count.to_fs(:delimited)}**",
180
"**#{::Mdm::Vuln.count.to_fs(:delimited)}**",
181
"**#{::Mdm::Note.count.to_fs(:delimited)}**",
182
"**#{::Mdm::Service.count.to_fs(:delimited)}**"
183
]
184
185
table = "| ID | Hosts | Vulnerabilities | Notes | Services |\n"
186
table += "|-:|-:|-:|-:|-:|\n"
187
table += (workspace_rows + [totals_row]).map { |x| "| #{x.join(" | ")} |" }.join("\n")
188
output += table
189
end
190
191
# The markdown table can't be placed in a code block or it will not render as a table.
192
build_section_no_block(
193
'Database Configuration',
194
'The database contains the following information:',
195
output
196
)
197
rescue StandardError => e
198
build_section(
199
'Database Configuration',
200
ERROR_BLURB,
201
section_build_error('Failed to extract Database configuration', e)
202
)
203
end
204
205
def self.framework_config(framework)
206
required_features = framework.features.all.map { |feature| [feature[:name], feature[:enabled].to_s] }
207
markdown_formatted_features = required_features.map { |feature| "| #{feature.join(' | ')} |" }
208
required_fields = %w[name enabled]
209
210
table = "| #{required_fields.join(' | ')} |\n"
211
table += '|' + '-:|' * required_fields.count + "\n"
212
table += markdown_formatted_features.join("\n").to_s
213
214
# The markdown table can't be placed in a code block or it will not render as a table.
215
build_section_no_block(
216
'Framework Configuration',
217
'The features are configured as follows:',
218
table
219
)
220
end
221
222
def self.history(driver)
223
end_pos = Readline::HISTORY.length - 1
224
start_pos = end_pos - COMMAND_HISTORY_TOTAL > driver.hist_last_saved ? end_pos - (COMMAND_HISTORY_TOTAL - 1) : driver.hist_last_saved
225
226
commands = ''
227
while start_pos <= end_pos
228
# Formats command position in history to 6 characters in length
229
commands += "#{'%-6.6s' % start_pos.to_s} #{Readline::HISTORY[start_pos]}\n"
230
start_pos += 1
231
end
232
233
build_section(
234
'History',
235
'The following commands were ran during the session and before this issue occurred:',
236
commands
237
)
238
rescue StandardError => e
239
build_section(
240
'History',
241
ERROR_BLURB,
242
section_build_error('Failed to extract History', e)
243
)
244
end
245
246
def self.errors
247
errors = build_regex_file_section(Pathname.new(Msf::Config.log_directory).join('framework.log'),
248
FRAMEWORK_ERROR_TOTAL,
249
FRAMEWORK_ERROR_REGEX,
250
'Framework Errors',
251
'The following framework errors occurred before the issue occurred:')
252
253
errors += build_regex_file_section(Pathname.new(Msf::Config.log_directory).join('msf-ws.log'),
254
WEB_SERVICE_ERROR_TOTAL,
255
WEB_SERVICE_ERROR_REGEX,
256
'Web Service Errors',
257
'The following web service errors occurred before the issue occurred:')
258
errors
259
end
260
261
def self.logs
262
logs = build_file_section(Pathname.new(Msf::Config.log_directory).join('framework.log'),
263
FRAMEWORK_LOG_LINE_TOTAL,
264
'Framework Logs',
265
'The following framework logs were recorded before the issue occurred:')
266
267
logs += build_file_section(Pathname.new(Msf::Config.log_directory).join('msf-ws.log'),
268
WEB_SERVICE_LOG_LINE_TOTAL,
269
'Web Service Logs',
270
'The following web service logs were recorded before the issue occurred:')
271
logs
272
end
273
274
def self.versions(framework)
275
276
str = <<~VERSIONS
277
Framework: #{framework.version}
278
Ruby: #{RUBY_DESCRIPTION}
279
OpenSSL: #{OpenSSL::OPENSSL_VERSION}
280
Install Root: #{Msf::Config.install_root}
281
Session Type: #{db_connection_info(framework)}
282
Install Method: #{installation_method}
283
VERSIONS
284
285
build_section('Version/Install', 'The versions and install method of your Metasploit setup:', str)
286
rescue StandardError => e
287
build_section(
288
'Version/Install',
289
ERROR_BLURB,
290
section_build_error('Failed to extract Versions', e)
291
)
292
end
293
294
class << self
295
296
private
297
298
def build_regex_file_section(path, match_total, regex, header_name, blurb)
299
unless File.file?(path)
300
return build_section(
301
header_name,
302
blurb,
303
"#{path.basename.to_s} does not exist."
304
)
305
end
306
307
file_contents = File.read(path)
308
matches = file_contents.scan(regex)
309
310
if matches.empty?
311
return build_section(
312
header_name,
313
blurb,
314
"No matching patterns were found in #{path.basename}."
315
)
316
end
317
318
# +.scan+ can sometimes return each match as a single item array
319
matches.flatten!
320
321
# create a string consisting of the last +match_total+ matches
322
# if +matches.length+ < +match_total+ then concat all matches
323
str = concat_str_array_from_last_idx(matches, match_total)
324
325
build_section(
326
header_name,
327
blurb,
328
str
329
)
330
rescue StandardError => e
331
build_section(
332
header_name,
333
ERROR_BLURB,
334
section_build_error("Failed to extract matches from #{path.basename}", e)
335
)
336
end
337
338
def build_file_section(path, line_total, header_name, blurb)
339
unless File.file?(path)
340
return build_section(
341
header_name,
342
blurb,
343
"#{path.basename.to_s} does not exist."
344
)
345
end
346
347
log_lines = File.readlines(path)
348
349
# create a string consisting of the last +line_total+ lines
350
# if +log_lines.length+ < +line_total+ then concat all lines
351
str = concat_str_array_from_last_idx(log_lines, line_total)
352
353
build_section(
354
header_name,
355
blurb,
356
str
357
)
358
rescue StandardError => e
359
build_section(
360
header_name,
361
ERROR_BLURB,
362
section_build_error("Failed to extract contents of #{path.basename.to_s}", e)
363
)
364
end
365
366
def add_hash_to_ini_group(ini, hash, group_name)
367
if hash.empty?
368
return
369
end
370
371
unless ini.group?(group_name)
372
ini.add_group(group_name)
373
end
374
375
hash.each_pair do |k, v|
376
ini[group_name][k] = v
377
end
378
end
379
380
def concat_str_array_from_last_idx(array, concat_total)
381
start_pos = array.length > concat_total ? array.length - concat_total : 0
382
end_pos = array.length - 1
383
384
str = array[start_pos..end_pos].join('')
385
386
str.strip
387
end
388
389
# Copy pasta of the print_connection_info method in console/command_dispatcher/db.rb
390
def db_connection_info(framework)
391
unless framework.db.connection_established?
392
return "#{framework.db.driver} selected, no connection"
393
end
394
395
cdb = ''
396
if framework.db.driver == 'http'
397
cdb = framework.db.name
398
else
399
::ApplicationRecord.connection_pool.with_connection do |conn|
400
if conn.respond_to?(:current_database)
401
cdb = conn.current_database
402
end
403
end
404
end
405
406
if cdb.empty?
407
output = "Connected Database Name could not be extracted. DB Connection type: #{framework.db.driver}."
408
else
409
output = "Connected to #{cdb}. Connection type: #{framework.db.driver}."
410
end
411
412
output
413
end
414
415
def build_section(header_name, blurb, content)
416
<<~SECTION
417
## %grn#{header_name.strip}%clr
418
419
#{blurb.strip}
420
#{with_collapsible_wrapper(content.strip)}
421
422
SECTION
423
end
424
425
def with_collapsible_wrapper(content)
426
<<~WRAPPER
427
<details>
428
<summary>Collapse</summary>
429
430
```
431
#{content}
432
```
433
434
</details>
435
WRAPPER
436
end
437
438
def with_collapsible_wrapper_no_block(content)
439
<<~WRAPPER
440
<details>
441
<summary>Collapse</summary>
442
443
#{content}
444
445
</details>
446
WRAPPER
447
end
448
449
# Useful for building tables or other content that can't be placed inside a code block.
450
def build_section_no_block(header_name, blurb, content)
451
<<~SECTION
452
## %grn#{header_name.strip}%clr
453
454
#{blurb.strip}
455
#{with_collapsible_wrapper_no_block(content.strip)}
456
457
SECTION
458
end
459
460
def installation_method
461
if File.exist?(File.join(Msf::Config.install_root, 'version.yml'))
462
'Omnibus Installer'
463
elsif File.directory?(File.join(Msf::Config.install_root, '.git'))
464
'Git Clone'
465
else
466
'Other - Please specify'
467
end
468
end
469
470
def section_build_error(msg, error)
471
"#{msg}: #{error.class} - #{error.message} \n Call stack:\n#{error.backtrace.join("\n")}"
472
end
473
end
474
end
475
end
476
end
477
478