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/plugins/wiki.rb
Views: 11705
1
##
2
#
3
# This plugin requires Metasploit: https://metasploit.com/download
4
# Current source: https://github.com/rapid7/metasploit-framework
5
#
6
##
7
8
module Msf
9
###
10
#
11
# This plugin extends the Rex::Text::Table class and provides commands
12
# that output database information for the current workspace in a wiki
13
# friendly format
14
#
15
# @author Trenton Ivey
16
# * *email:* ("[email protected]").gsub(/example/,"gmail")
17
# * *github:* kn0
18
# * *twitter:* trentonivey
19
###
20
class Plugin::Wiki < Msf::Plugin
21
22
###
23
#
24
# This class implements a command dispatcher that provides commands to
25
# output database information in a wiki friendly format.
26
#
27
###
28
class WikiCommandDispatcher
29
include Msf::Ui::Console::CommandDispatcher
30
31
#
32
# The dispatcher's name.
33
#
34
def name
35
'Wiki'
36
end
37
38
#
39
# Returns the hash of commands supported by the wiki dispatcher.
40
#
41
def commands
42
{
43
'dokuwiki' => 'Outputs data from the current workspace in dokuwiki markup.',
44
'mediawiki' => 'Outputs data from the current workspace in mediawiki markup.'
45
}
46
end
47
48
#
49
# Outputs database entries as Dokuwiki formatted text by passing the
50
# arguments to the wiki method with a wiki_type of 'dokuwiki'
51
# @param [Array<String>] args the arguments passed when the command is
52
# called
53
# @see #wiki
54
#
55
def cmd_dokuwiki(*args)
56
wiki('dokuwiki', *args)
57
end
58
59
#
60
# Outputs database entries as Mediawiki formatted text by passing the
61
# arguments to the wiki method with a wiki_type of 'mediawiki'
62
# @param [Array<String>] args the arguments passed when the command is
63
# called
64
# @see #wiki
65
#
66
def cmd_mediawiki(*args)
67
wiki('mediawiki', *args)
68
end
69
70
#
71
# This method parses arguments passed from the wiki output commands
72
# and then formats and displays or saves text according to the
73
# provided wiki type
74
#
75
# @param [String] wiki_type selects the wiki markup lanuguage output to
76
# use, it can be:
77
# * dokuwiki
78
# * mediawiki
79
#
80
# @param [Array<String>] args the arguments passed when the command is
81
# called
82
#
83
def wiki(wiki_type, *args)
84
# Create a table options hash
85
tbl_opts = {}
86
# Set some default options for the table hash
87
tbl_opts[:hosts] = []
88
tbl_opts[:links] = false
89
tbl_opts[:wiki_type] = wiki_type
90
tbl_opts[:heading_size] = 5
91
case wiki_type
92
when 'dokuwiki'
93
tbl_opts[:namespace] = 'notes:targets:hosts:'
94
else
95
tbl_opts[:namespace] = ''
96
end
97
98
# Get the table we should be looking at
99
command = args.shift
100
if command.nil? || !['creds', 'hosts', 'loot', 'services', 'vulns'].include?(command.downcase)
101
usage(wiki_type)
102
return
103
end
104
105
# Parse the rest of the arguments
106
while (arg = args.shift)
107
case arg
108
when '-o', '--output'
109
tbl_opts[:file_name] = next_opt(args)
110
when '-h', '--help'
111
usage(wiki_type)
112
return
113
when '-l', '-L', '--link', '--links'
114
tbl_opts[:links] = true
115
when '-n', '-N', '--namespace'
116
tbl_opts[:namespace] = next_opt(args)
117
when '-p', '-P', '--port', '--ports'
118
tbl_opts[:ports] = next_opts(args)
119
tbl_opts[:ports].map!(&:to_i)
120
when '-s', '-S', '--search'
121
tbl_opts[:search] = next_opt(args)
122
when '-i', '-I', '--heading-size'
123
heading_size = next_opt(args)
124
tbl_opts[:heading_size] = heading_size.to_i unless heading_size.nil?
125
else
126
# Assume it is a host
127
rw = Rex::Socket::RangeWalker.new(arg)
128
if rw.valid?
129
rw.each do |ip|
130
tbl_opts[:hosts] << ip
131
end
132
else
133
print_warning "#{arg} is an invalid hostname"
134
end
135
end
136
end
137
138
# Output the table
139
if respond_to? "#{command}_to_table", true
140
table = send "#{command}_to_table", tbl_opts
141
if table.respond_to? "to_#{wiki_type}", true
142
if tbl_opts[:file_name]
143
print_status("Wrote the #{command} table to a file as a #{wiki_type} formatted table")
144
File.open(tbl_opts[:file_name], 'wb') do |f|
145
f.write(table.send("to_#{wiki_type}"))
146
end
147
else
148
print_line table.send "to_#{wiki_type}"
149
end
150
return
151
end
152
end
153
usage(wiki_type)
154
end
155
156
#
157
# Gets the next set of arguments when parsing command options
158
#
159
# *Note:* This will modify the provided argument list
160
#
161
# @param [Array] args the list of unparsed arguments
162
# @return [Array] the unique list of items before the next '-' in the
163
# provided array
164
#
165
def next_opts(args)
166
opts = []
167
while (opt = args.shift)
168
if opt =~ /^-/
169
args.unshift opt
170
break
171
end
172
opts.concat(opt.split(','))
173
end
174
return opts.uniq
175
end
176
177
#
178
# Gets the next argument when parsing command options
179
#
180
# *Note:* This will modify the provided argument list
181
#
182
# @param [Array] args the list of unparsed arguments
183
# @return [String, nil] the argument or nil if the argument starts with a '-'
184
#
185
def next_opt(args)
186
return nil if args[0] =~ /^-/
187
188
args.shift
189
end
190
191
#
192
# Outputs the help message
193
#
194
# @param [String] cmd_name the type of the wiki output command to display
195
# help for
196
#
197
def usage(cmd_name = '<wiki cmd>')
198
print_line "Usage: #{cmd_name} <table> [options] [IP1 IP2,IPn]"
199
print_line
200
print_line 'The first argument must be the type of table to retrieve:'
201
print_line ' creds, hosts, loot, services, vulns'
202
print_line
203
print_line 'OPTIONS:'
204
print_line ' -l,--link Enables links for host addresses'
205
print_line ' -n,--namespace <ns> Changes the default namespace for host links'
206
print_line ' -o,--output <file> Write output to a file'
207
print_line ' -p,--port <ports> Only return results that relate to given ports'
208
print_line ' -s,--search <search> Only show results that match the provided text'
209
print_line ' -i,--heading-size <1-6> Changes the heading size'
210
print_line ' -h,--help Displays this menu'
211
print_line
212
end
213
214
#
215
# Outputs credentials in the database (within the current workspace) as a Rex table object
216
# @param [Hash] opts
217
# @option opts [Array<String>] :hosts contains list of hosts used to limit results
218
# @option opts [Array<Integer>] :ports contains list of ports used to limit results
219
# @option opts [String] :search limits results to those containing a provided string
220
# @return [Rex::Text::Table] table containing credentials
221
#
222
def creds_to_table(opts = {})
223
tbl = Rex::Text::Table.new({ 'Columns' => ['host', 'port', 'user', 'pass', 'type', 'proof', 'active?'] })
224
tbl.header = 'Credentials'
225
tbl.headeri = opts[:heading_size]
226
framework.db.creds.each do |cred|
227
if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? cred.service.host.address)
228
next
229
end
230
if !opts[:ports].nil? && opts[:ports].none? { |p| cred.service.port.eql? p }
231
next
232
end
233
234
address = cred.service.host.address
235
address = to_wikilink(address, opts[:namespace]) if opts[:links]
236
row = [
237
address,
238
cred.service.port,
239
cred.user,
240
cred.pass,
241
cred.ptype,
242
cred.proof,
243
cred.active
244
]
245
if opts[:search]
246
tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }
247
else
248
tbl << row
249
end
250
end
251
return tbl
252
end
253
254
#
255
# Outputs host information stored in the database (within the current
256
# workspace) as a Rex table object
257
# @param [Hash] opts
258
# @option opts [Array<String>] :hosts contains list of hosts used to limit results
259
# @option opts [Array<String>] :ports contains list of ports used to limit results
260
# @option opts [String] :search limits results to those containing a provided string
261
# @return [Rex::Text::Table] table containing credentials
262
#
263
def hosts_to_table(opts = {})
264
tbl = Rex::Text::Table.new({ 'Columns' => ['address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments'] })
265
tbl.header = 'Hosts'
266
tbl.headeri = opts[:heading_size]
267
framework.db.hosts.each do |host|
268
if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? host.address)
269
next
270
end
271
if !opts[:ports].nil? && (host.services.map { |s| s[:port] }).none? { |p| opts[:ports].include? p }
272
next
273
end
274
275
address = host.address
276
address = to_wikilink(address, opts[:namespace]) if opts[:links]
277
row = [
278
address,
279
host.mac,
280
host.name,
281
host.os_name,
282
host.os_flavor,
283
host.os_sp,
284
host.purpose,
285
host.info,
286
host.comments
287
]
288
if opts[:search]
289
tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }
290
else
291
tbl << row
292
end
293
end
294
return tbl
295
end
296
297
#
298
# Outputs loot information stored in the database (within the current
299
# workspace) as a Rex table object
300
# @param [Hash] opts
301
# @option opts [Array<String>] :hosts contains list of hosts used to limit results
302
# @option opts [Array<String>] :ports contains list of ports used to limit results
303
# @option opts [String] :search limits results to those containing a provided string
304
# @return [Rex::Text::Table] table containing credentials
305
#
306
def loot_to_table(opts = {})
307
tbl = Rex::Text::Table.new({ 'Columns' => ['host', 'service', 'type', 'name', 'content', 'info', 'path'] })
308
tbl.header = 'Loot'
309
tbl.headeri = opts[:heading_size]
310
framework.db.loots.each do |loot|
311
if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? loot.host.address)
312
next
313
end
314
if !(opts[:ports].nil? || opts[:ports].empty?) && (loot.service.nil? || loot.service.port.nil? || !opts[:ports].include?(loot.service.port))
315
next
316
end
317
318
if loot.service
319
svc = (loot.service.name || "#{loot.service.port}/#{loot.service.proto}")
320
end
321
address = loot.host.address
322
address = to_wikilink(address, opts[:namespace]) if opts[:links]
323
row = [
324
address,
325
svc || '',
326
loot.ltype,
327
loot.name,
328
loot.content_type,
329
loot.info,
330
loot.path
331
]
332
if opts[:search]
333
tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }
334
else
335
tbl << row
336
end
337
end
338
return tbl
339
end
340
341
#
342
# Outputs service information stored in the database (within the current
343
# workspace) as a Rex table object
344
# @param [Hash] opts
345
# @option opts [Array<String>] :hosts contains list of hosts used to limit results
346
# @option opts [Array<String>] :ports contains list of ports used to limit results
347
# @option opts [String] :search limits results to those containing a provided string
348
# @return [Rex::Text::Table] table containing credentials
349
#
350
def services_to_table(opts = {})
351
tbl = Rex::Text::Table.new({ 'Columns' => ['host', 'port', 'proto', 'name', 'state', 'info'] })
352
tbl.header = 'Services'
353
tbl.headeri = opts[:heading_size]
354
framework.db.services.each do |service|
355
if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? service.host.address)
356
next
357
end
358
if !(opts[:ports].nil? || opts[:ports].empty?) && opts[:ports].none? { |p| service[:port].eql? p }
359
next
360
end
361
362
address = service.host.address
363
address = to_wikilink(address, opts[:namespace]) if opts[:links]
364
row = [
365
address,
366
service.port,
367
service.proto,
368
service.name,
369
service.state,
370
service.info
371
]
372
if opts[:search]
373
tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }
374
else
375
tbl << row
376
end
377
end
378
return tbl
379
end
380
381
#
382
# Outputs vulnerability information stored in the database (within the current
383
# workspace) as a Rex table object
384
# @param [Hash] opts
385
# @option opts [Array<String>] :hosts contains list of hosts used to limit results
386
# @option opts [Array<String>] :ports contains list of ports used to limit results
387
# @option opts [String] :search limits results to those containing a provided string
388
# @return [Rex::Text::Table] table containing credentials
389
#
390
def vulns_to_table(opts = {})
391
tbl = Rex::Text::Table.new({ 'Columns' => ['Title', 'Host', 'Port', 'Info', 'Detail Count', 'Attempt Count', 'Exploited At', 'Updated At'] })
392
tbl.header = 'Vulns'
393
tbl.headeri = opts[:heading_size]
394
framework.db.vulns.each do |vuln|
395
if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? vuln.host.address)
396
next
397
end
398
if !(opts[:ports].nil? || opts[:ports].empty?) && opts[:ports].none? { |p| vuln.service.port.eql? p }
399
next
400
end
401
402
address = vuln.host.address
403
address = to_wikilink(address, opts[:namespace]) if opts[:links]
404
row = [
405
vuln.name,
406
address,
407
(vuln.service ? vuln.service.port : ''),
408
vuln.info,
409
vuln.vuln_detail_count,
410
vuln.vuln_attempt_count,
411
vuln.exploited_at,
412
vuln.updated_at,
413
]
414
if opts[:search]
415
tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }
416
else
417
tbl << row
418
end
419
end
420
return tbl
421
end
422
423
#
424
# Converts a value to a wiki link
425
# @param [String] text value to convert to a link
426
# @param [String] namespace optional namespace to set for the link
427
# @return [String] the formatted wiki link
428
def to_wikilink(text, namespace = '')
429
return '[[' + namespace + text + ']]'
430
end
431
432
end
433
434
#
435
# Plugin Initialization
436
#
437
438
#
439
# Constructs a new instance of the plugin and registers the console
440
# dispatcher. It also extends Rex by adding the following methods:
441
# * Rex::Text::Table.to_dokuwiki
442
# * Rex::Text::Table.to_mediawiki
443
#
444
def initialize(framework, opts)
445
super
446
447
# Extend Rex::Text::Table class so it can output wiki formats
448
add_dokuwiki_to_rex
449
add_mediawiki_to_rex
450
451
# Add the console dispatcher
452
add_console_dispatcher(WikiCommandDispatcher)
453
end
454
455
#
456
# The cleanup routine removes the methods added to Rex by the plugin
457
# initialization and then removes the console dispatcher
458
#
459
def cleanup
460
# Cleanup methods added to Rex::Text::Table
461
Rex::Text::Table.class_eval { undef :to_dokuwiki }
462
Rex::Text::Table.class_eval { undef :to_mediawiki }
463
# Deregister the console dispatcher
464
remove_console_dispatcher('Wiki')
465
end
466
467
#
468
# Returns the plugin's name.
469
#
470
def name
471
'wiki'
472
end
473
474
#
475
# This method returns a brief description of the plugin. It should be no
476
# more than 60 characters, but there are no hard limits.
477
#
478
def desc
479
'Outputs stored database values from the current workspace into DokuWiki or MediaWiki format'
480
end
481
482
#
483
# The following methods are added here to keep the initialize method
484
# readable
485
#
486
487
#
488
# Extends Rex tables to be able to create Dokuwiki tables
489
#
490
def add_dokuwiki_to_rex
491
Rex::Text::Table.class_eval do
492
def to_dokuwiki
493
str = prefix.dup
494
# Print the header if there is one. Use headeri to determine wiki paragraph level
495
if header
496
level = '=' * headeri
497
str << level + header + level + "\n"
498
end
499
# Add the column names to the top of the table
500
columns.each do |col|
501
str << '^ ' + col.to_s + ' '
502
end
503
str << "^\n" unless columns.count.eql? 0
504
# Fill out the rest of the table with rows
505
rows.each do |row|
506
row.each do |val|
507
cell = val.to_s
508
cell = "<nowiki>#{cell}</nowiki>" if cell.include? '|'
509
str << '| ' + cell + ' '
510
end
511
str << "|\n" unless rows.count.eql? 0
512
end
513
return str
514
end
515
end
516
end
517
518
#
519
# Extends Rex tables to be able to create Mediawiki tables
520
#
521
def add_mediawiki_to_rex
522
Rex::Text::Table.class_eval do
523
def to_mediawiki
524
str = prefix.dup
525
# Print the header if there is one. Use headeri to determine wiki
526
# headline level. Mediawiki does headlines a bit backwards so that
527
# the header level isn't limited. This results in the need to 'flip'
528
# the headline length to standardize it.
529
if header
530
if headeri <= 6
531
level = '=' * (-headeri + 7)
532
str << "#{level} #{header} #{level}"
533
else
534
str << header.to_s
535
end
536
str << "\n"
537
end
538
# Setup the table with some standard formatting options
539
str << "{|class=\"wikitable\"\n"
540
# Output formatted column names as the first row
541
unless columns.count.eql? 0
542
str << '!'
543
str << columns.join('!!')
544
str << "\n"
545
end
546
# Add the rows to the table
547
unless rows.count.eql? 0
548
rows.each do |row|
549
str << "|-\n|"
550
# Try and prevent formatting tags from causing problems
551
bad = ['&', '<', '>', '"', "'", '/']
552
r = row.join('|| ')
553
r.each_char do |c|
554
if bad.include? c
555
str << Rex::Text.html_encode(c)
556
else
557
str << c
558
end
559
end
560
str << "\n"
561
end
562
end
563
# Finish up the table
564
str << '|}'
565
return str
566
end
567
end
568
end
569
570
end
571
end
572
573