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/console/command_dispatcher/db.rb
Views: 11784
1
# -*- coding: binary -*-
2
3
require 'json'
4
require 'rexml/document'
5
require 'metasploit/framework/data_service'
6
require 'metasploit/framework/data_service/remote/http/core'
7
8
module Msf
9
module Ui
10
module Console
11
module CommandDispatcher
12
13
class Db
14
15
require 'tempfile'
16
17
include Msf::Ui::Console::CommandDispatcher
18
include Msf::Ui::Console::CommandDispatcher::Common
19
include Msf::Ui::Console::CommandDispatcher::Db::Common
20
include Msf::Ui::Console::CommandDispatcher::Db::Analyze
21
include Msf::Ui::Console::CommandDispatcher::Db::Klist
22
23
DB_CONFIG_PATH = 'framework/database'
24
25
#
26
# The dispatcher's name.
27
#
28
def name
29
"Database Backend"
30
end
31
32
#
33
# Returns the hash of commands supported by this dispatcher.
34
#
35
def commands
36
base = {
37
"db_connect" => "Connect to an existing data service",
38
"db_disconnect" => "Disconnect from the current data service",
39
"db_status" => "Show the current data service status",
40
"db_save" => "Save the current data service connection as the default to reconnect on startup",
41
"db_remove" => "Remove the saved data service entry"
42
}
43
44
more = {
45
"workspace" => "Switch between database workspaces",
46
"hosts" => "List all hosts in the database",
47
"services" => "List all services in the database",
48
"vulns" => "List all vulnerabilities in the database",
49
"notes" => "List all notes in the database",
50
"loot" => "List all loot in the database",
51
"klist" => "List Kerberos tickets in the database",
52
"db_import" => "Import a scan result file (filetype will be auto-detected)",
53
"db_export" => "Export a file containing the contents of the database",
54
"db_nmap" => "Executes nmap and records the output automatically",
55
"db_rebuild_cache" => "Rebuilds the database-stored module cache (deprecated)",
56
"analyze" => "Analyze database information about a specific address or address range",
57
"db_stats" => "Show statistics for the database"
58
}
59
60
# Always include commands that only make sense when connected.
61
# This avoids the problem of them disappearing unexpectedly if the
62
# database dies or times out. See #1923
63
64
base.merge(more)
65
end
66
67
def deprecated_commands
68
[
69
"db_autopwn",
70
"db_driver",
71
"db_hosts",
72
"db_notes",
73
"db_services",
74
"db_vulns",
75
]
76
end
77
78
#
79
# Attempts to connect to the previously configured database, and additionally keeps track of
80
# the currently loaded data service.
81
#
82
def load_config(path = nil)
83
result = Msf::DbConnector.db_connect_from_config(framework, path)
84
85
if result[:error]
86
print_error(result[:error])
87
end
88
if result[:data_service_name]
89
@current_data_service = result[:data_service_name]
90
end
91
end
92
93
@@workspace_opts = Rex::Parser::Arguments.new(
94
[ '-h', '--help' ] => [ false, 'Help banner.'],
95
[ '-a', '--add' ] => [ true, 'Add a workspace.', '<name>'],
96
[ '-d', '--delete' ] => [ true, 'Delete a workspace.', '<name>'],
97
[ '-D', '--delete-all' ] => [ false, 'Delete all workspaces.'],
98
[ '-r', '--rename' ] => [ true, 'Rename a workspace.', '<old> <new>'],
99
[ '-l', '--list' ] => [ false, 'List workspaces.'],
100
[ '-v', '--list-verbose' ] => [ false, 'List workspaces verbosely.'],
101
[ '-S', '--search' ] => [ true, 'Search for a workspace.', '<name>']
102
)
103
104
def cmd_workspace_help
105
print_line "Usage:"
106
print_line " workspace List workspaces"
107
print_line " workspace [name] Switch workspace"
108
print_line @@workspace_opts.usage
109
end
110
111
def cmd_workspace(*args)
112
return unless active?
113
114
state = :nil
115
116
list = false
117
verbose = false
118
names = []
119
search_term = nil
120
121
@@workspace_opts.parse(args) do |opt, idx, val|
122
case opt
123
when '-h', '--help'
124
cmd_workspace_help
125
return
126
when '-a', '--add'
127
return cmd_workspace_help unless state == :nil
128
129
state = :adding
130
names << val if !val.nil?
131
when '-d', '--del'
132
return cmd_workspace_help unless state == :nil
133
134
state = :deleting
135
names << val if !val.nil?
136
when '-D', '--delete-all'
137
return cmd_workspace_help unless state == :nil
138
139
state = :delete_all
140
when '-r', '--rename'
141
return cmd_workspace_help unless state == :nil
142
143
state = :renaming
144
names << val if !val.nil?
145
when '-v', '--verbose'
146
verbose = true
147
when '-l', '--list'
148
list = true
149
when '-S', '--search'
150
search_term = val
151
else
152
names << val if !val.nil?
153
end
154
end
155
156
if state == :adding and names
157
# Add workspaces
158
wspace = nil
159
names.each do |name|
160
wspace = framework.db.workspaces(name: name).first
161
if wspace
162
print_status("Workspace '#{wspace.name}' already existed, switching to it.")
163
else
164
wspace = framework.db.add_workspace(name)
165
print_status("Added workspace: #{wspace.name}")
166
end
167
end
168
framework.db.workspace = wspace
169
print_status("Workspace: #{framework.db.workspace.name}")
170
elsif state == :deleting and names
171
ws_ids_to_delete = []
172
starting_ws = framework.db.workspace
173
names.uniq.each do |n|
174
ws = framework.db.workspaces(name: n).first
175
ws_ids_to_delete << ws.id if ws
176
end
177
if ws_ids_to_delete.count > 0
178
deleted = framework.db.delete_workspaces(ids: ws_ids_to_delete)
179
process_deleted_workspaces(deleted, starting_ws)
180
else
181
print_status("No workspaces matching the given name(s) were found.")
182
end
183
elsif state == :delete_all
184
ws_ids_to_delete = []
185
starting_ws = framework.db.workspace
186
framework.db.workspaces.each do |ws|
187
ws_ids_to_delete << ws.id
188
end
189
deleted = framework.db.delete_workspaces(ids: ws_ids_to_delete)
190
process_deleted_workspaces(deleted, starting_ws)
191
elsif state == :renaming
192
if names.length != 2
193
print_error("Wrong number of arguments to rename")
194
return
195
end
196
197
ws_to_update = framework.db.find_workspace(names.first)
198
unless ws_to_update
199
print_error("Workspace '#{names.first}' does not exist")
200
return
201
end
202
opts = {
203
id: ws_to_update.id,
204
name: names.last
205
}
206
begin
207
updated_ws = framework.db.update_workspace(opts)
208
if updated_ws
209
framework.db.workspace = updated_ws if names.first == framework.db.workspace.name
210
print_status("Renamed workspace '#{names.first}' to '#{updated_ws.name}'")
211
else
212
print_error "There was a problem updating the workspace. Setting to the default workspace."
213
framework.db.workspace = framework.db.default_workspace
214
return
215
end
216
if names.first == Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME
217
print_status("Recreated default workspace")
218
end
219
rescue => e
220
print_error "Failed to rename workspace: #{e.message}"
221
end
222
223
elsif !names.empty?
224
name = names.last
225
# Switch workspace
226
workspace = framework.db.find_workspace(name)
227
if workspace
228
framework.db.workspace = workspace
229
print_status("Workspace: #{workspace.name}")
230
else
231
print_error("Workspace not found: #{name}")
232
return
233
end
234
else
235
current_workspace = framework.db.workspace
236
237
unless verbose
238
current = nil
239
framework.db.workspaces.sort_by {|s| s.name}.each do |s|
240
if s.name == current_workspace.name
241
current = s.name
242
else
243
print_line(" #{s.name}")
244
end
245
end
246
print_line("%red* #{current}%clr") unless current.nil?
247
return
248
end
249
col_names = %w{current name hosts services vulns creds loots notes}
250
251
tbl = Rex::Text::Table.new(
252
'Header' => 'Workspaces',
253
'Columns' => col_names,
254
'SortIndex' => -1,
255
'SearchTerm' => search_term
256
)
257
258
framework.db.workspaces.each do |ws|
259
tbl << [
260
current_workspace.name == ws.name ? '*' : '',
261
ws.name,
262
framework.db.hosts(workspace: ws.name).count,
263
framework.db.services(workspace: ws.name).count,
264
framework.db.vulns(workspace: ws.name).count,
265
framework.db.creds(workspace: ws.name).count,
266
framework.db.loots(workspace: ws.name).count,
267
framework.db.notes(workspace: ws.name).count
268
]
269
end
270
271
print_line
272
print_line(tbl.to_s)
273
end
274
end
275
276
def process_deleted_workspaces(deleted_workspaces, starting_ws)
277
deleted_workspaces.each do |ws|
278
print_status "Deleted workspace: #{ws.name}"
279
if ws.name == Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME
280
framework.db.workspace = framework.db.default_workspace
281
print_status 'Recreated the default workspace'
282
elsif ws == starting_ws
283
framework.db.workspace = framework.db.default_workspace
284
print_status "Switched to workspace: #{framework.db.workspace.name}"
285
end
286
end
287
end
288
289
def cmd_workspace_tabs(str, words)
290
return [] unless active?
291
framework.db.workspaces.map(&:name) if (words & ['-a','--add']).empty?
292
end
293
294
#
295
# Tab completion for the hosts command
296
#
297
# @param str [String] the string currently being typed before tab was hit
298
# @param words [Array<String>] the previously completed words on the command line. words is always
299
# at least 1 when tab completion has reached this stage since the command itself has been completed
300
def cmd_hosts_tabs(str, words)
301
if words.length == 1
302
return @@hosts_opts.option_keys.select { |opt| opt.start_with?(str) }
303
end
304
305
case words[-1]
306
when '-c', '--columns', '-C', '--columns-until-restart'
307
return @@hosts_columns
308
when '-o', '--output'
309
return tab_complete_filenames(str, words)
310
end
311
312
if @@hosts_opts.arg_required?(words[-1])
313
return []
314
end
315
316
return @@hosts_opts.option_keys.select { |opt| opt.start_with?(str) }
317
end
318
319
def cmd_hosts_help
320
# This command does some lookups for the list of appropriate column
321
# names, so instead of putting all the usage stuff here like other
322
# help methods, just use it's "-h" so we don't have to recreating
323
# that list
324
cmd_hosts("-h")
325
end
326
327
# Changes the specified host data
328
#
329
# @param host_ranges - range of hosts to process
330
# @param host_data - hash of host data to be updated
331
def change_host_data(host_ranges, host_data)
332
if !host_data || host_data.length != 1
333
print_error("A single key-value data hash is required to change the host data")
334
return
335
end
336
attribute = host_data.keys[0]
337
338
if host_ranges == [nil]
339
print_error("In order to change the host #{attribute}, you must provide a range of hosts")
340
return
341
end
342
343
each_host_range_chunk(host_ranges) do |host_search|
344
next if host_search && host_search.empty?
345
346
framework.db.hosts(address: host_search).each do |host|
347
framework.db.update_host(host_data.merge(id: host.id))
348
framework.db.report_note(host: host.address, type: "host.#{attribute}", data: host_data[attribute])
349
end
350
end
351
end
352
353
def add_host_tag(rws, tag_name)
354
if rws == [nil]
355
print_error("In order to add a tag, you must provide a range of hosts")
356
return
357
end
358
359
opts = Hash.new()
360
opts[:workspace] = framework.db.workspace
361
opts[:tag_name] = tag_name
362
363
rws.each do |rw|
364
rw.each do |ip|
365
opts[:address] = ip
366
unless framework.db.add_host_tag(opts)
367
print_error("Host #{ip} could not be found.")
368
end
369
end
370
end
371
end
372
373
def find_host_tags(workspace, host_id)
374
opts = Hash.new()
375
opts[:workspace] = workspace
376
opts[:id] = host_id
377
378
framework.db.get_host_tags(opts)
379
end
380
381
def delete_host_tag(rws, tag_name)
382
opts = Hash.new()
383
opts[:workspace] = framework.db.workspace
384
opts[:tag_name] = tag_name
385
386
# This will be the case if no IP was passed in, and we are just trying to delete all
387
# instances of a given tag within the database.
388
if rws == [nil]
389
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
390
wspace.hosts.each do |host|
391
opts[:address] = host.address
392
framework.db.delete_host_tag(opts)
393
end
394
else
395
rws.each do |rw|
396
rw.each do |ip|
397
opts[:address] = ip
398
unless framework.db.delete_host_tag(opts)
399
print_error("Host #{ip} could not be found.")
400
end
401
end
402
end
403
end
404
end
405
406
@@hosts_columns = [ 'address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments']
407
408
@@hosts_opts = Rex::Parser::Arguments.new(
409
[ '-h', '--help' ] => [ false, 'Show this help information' ],
410
[ '-a', '--add' ] => [ true, 'Add the hosts instead of searching', '<host>' ],
411
[ '-u', '--up' ] => [ false, 'Only show hosts which are up' ],
412
[ '-R', '--rhosts' ] => [ false, 'Set RHOSTS from the results of the search' ],
413
[ '-S', '--search' ] => [ true, 'Search string to filter by', '<filter>' ],
414
[ '-i', '--info' ] => [ true, 'Change the info of a host', '<info>' ],
415
[ '-n', '--name' ] => [ true, 'Change the name of a host', '<name>' ],
416
[ '-m', '--comment' ] => [ true, 'Change the comment of a host', '<comment>' ],
417
[ '-t', '--tag' ] => [ true, 'Add or specify a tag to a range of hosts', '<tag>' ],
418
[ '-T', '--delete-tag' ] => [ true, 'Remove a tag from a range of hosts', '<tag>' ],
419
[ '-d', '--delete' ] => [ true, 'Delete the hosts instead of searching', '<hosts>' ],
420
[ '-o', '--output' ] => [ true, 'Send output to a file in csv format', '<filename>' ],
421
[ '-O', '--order' ] => [ true, 'Order rows by specified column number', '<column id>' ],
422
[ '-c', '--columns' ] => [ true, 'Only show the given columns (see list below)', '<columns>' ],
423
[ '-C', '--columns-until-restart' ] => [ true, 'Only show the given columns until the next restart (see list below)', '<columns>' ],
424
)
425
426
def cmd_hosts(*args)
427
return unless active?
428
onlyup = false
429
set_rhosts = false
430
mode = []
431
delete_count = 0
432
433
rhosts = []
434
host_ranges = []
435
search_term = nil
436
437
order_by = nil
438
info_data = nil
439
name_data = nil
440
comment_data = nil
441
tag_name = nil
442
443
output = nil
444
default_columns = [
445
'address',
446
'arch',
447
'comm',
448
'comments',
449
'created_at',
450
'cred_count',
451
'detected_arch',
452
'exploit_attempt_count',
453
'host_detail_count',
454
'info',
455
'mac',
456
'name',
457
'note_count',
458
'os_family',
459
'os_flavor',
460
'os_lang',
461
'os_name',
462
'os_sp',
463
'purpose',
464
'scope',
465
'service_count',
466
'state',
467
'updated_at',
468
'virtual_host',
469
'vuln_count',
470
'workspace_id']
471
472
default_columns << 'tags' # Special case
473
virtual_columns = [ 'svcs', 'vulns', 'workspace', 'tags' ]
474
475
col_search = @@hosts_columns
476
477
default_columns.delete_if {|v| (v[-2,2] == "id")}
478
@@hosts_opts.parse(args) do |opt, idx, val|
479
case opt
480
when '-h', '--help'
481
print_line "Usage: hosts [ options ] [addr1 addr2 ...]"
482
print_line
483
print @@hosts_opts.usage
484
print_line
485
print_line "Available columns: #{default_columns.join(", ")}"
486
print_line
487
return
488
when '-a', '--add'
489
mode << :add
490
arg_host_range(val, host_ranges)
491
when '-d', '--delete'
492
mode << :delete
493
arg_host_range(val, host_ranges)
494
when '-u', '--up'
495
onlyup = true
496
when '-o'
497
output = val
498
output = ::File.expand_path(output)
499
when '-R', '--rhosts'
500
set_rhosts = true
501
when '-S', '--search'
502
search_term = val
503
when '-i', '--info'
504
mode << :new_info
505
info_data = val
506
when '-n', '--name'
507
mode << :new_name
508
name_data = val
509
when '-m', '--comment'
510
mode << :new_comment
511
comment_data = val
512
when '-t', '--tag'
513
mode << :tag
514
tag_name = val
515
when '-T', '--delete-tag'
516
mode << :delete_tag
517
tag_name = val
518
when '-c', '-C'
519
list = val
520
if(!list)
521
print_error("Invalid column list")
522
return
523
end
524
col_search = list.strip().split(",")
525
col_search.each { |c|
526
if not default_columns.include?(c) and not virtual_columns.include?(c)
527
all_columns = default_columns + virtual_columns
528
print_error("Invalid column list. Possible values are (#{all_columns.join("|")})")
529
return
530
end
531
}
532
if opt == '-C'
533
@@hosts_columns = col_search
534
end
535
when '-O'
536
if (order_by = val.to_i - 1) < 0
537
print_error('Please specify a column number starting from 1')
538
return
539
end
540
else
541
# Anything that wasn't an option is a host to search for
542
unless (arg_host_range(val, host_ranges))
543
return
544
end
545
end
546
end
547
548
if col_search
549
col_names = col_search
550
else
551
col_names = default_columns + virtual_columns
552
end
553
554
mode << :search if mode.empty?
555
556
if mode == [:add]
557
host_ranges.each do |range|
558
range.each do |address|
559
host = framework.db.find_or_create_host(:host => address)
560
print_status("Time: #{host.created_at} Host: host=#{host.address}")
561
end
562
end
563
return
564
end
565
566
cp_hsh = {}
567
col_names.map do |col|
568
cp_hsh[col] = { 'MaxChar' => 52 }
569
end
570
# If we got here, we're searching. Delete implies search
571
tbl = Rex::Text::Table.new(
572
{
573
'Header' => "Hosts",
574
'Columns' => col_names,
575
'ColProps' => cp_hsh,
576
'SortIndex' => order_by
577
})
578
579
# Sentinel value meaning all
580
host_ranges.push(nil) if host_ranges.empty?
581
582
case
583
when mode == [:new_info]
584
change_host_data(host_ranges, info: info_data)
585
return
586
when mode == [:new_name]
587
change_host_data(host_ranges, name: name_data)
588
return
589
when mode == [:new_comment]
590
change_host_data(host_ranges, comments: comment_data)
591
return
592
when mode == [:tag]
593
begin
594
add_host_tag(host_ranges, tag_name)
595
rescue => e
596
if e.message.include?('Validation failed')
597
print_error(e.message)
598
else
599
raise e
600
end
601
end
602
return
603
when mode == [:delete_tag]
604
begin
605
delete_host_tag(host_ranges, tag_name)
606
rescue => e
607
if e.message.include?('Validation failed')
608
print_error(e.message)
609
else
610
raise e
611
end
612
end
613
return
614
end
615
616
matched_host_ids = []
617
each_host_range_chunk(host_ranges) do |host_search|
618
next if host_search && host_search.empty?
619
620
framework.db.hosts(address: host_search, non_dead: onlyup, search_term: search_term).each do |host|
621
matched_host_ids << host.id
622
columns = col_names.map do |n|
623
# Deal with the special cases
624
if virtual_columns.include?(n)
625
case n
626
when "svcs"; host.service_count
627
when "vulns"; host.vuln_count
628
when "workspace"; host.workspace.name
629
when "tags"
630
found_tags = find_host_tags(framework.db.workspace, host.id)
631
tag_names = found_tags.map(&:name).join(', ')
632
tag_names
633
end
634
# Otherwise, it's just an attribute
635
else
636
host[n] || ""
637
end
638
end
639
640
tbl << columns
641
if set_rhosts
642
addr = (host.scope.to_s != "" ? host.address + '%' + host.scope : host.address)
643
rhosts << addr
644
end
645
end
646
647
if mode == [:delete]
648
result = framework.db.delete_host(ids: matched_host_ids)
649
delete_count += result.size
650
end
651
end
652
653
if output
654
print_status("Wrote hosts to #{output}")
655
::File.open(output, "wb") { |ofd|
656
ofd.write(tbl.to_csv)
657
}
658
else
659
print_line
660
print_line(tbl.to_s)
661
end
662
663
# Finally, handle the case where the user wants the resulting list
664
# of hosts to go into RHOSTS.
665
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
666
667
print_status("Deleted #{delete_count} hosts") if delete_count > 0
668
end
669
670
#
671
# Tab completion for the services command
672
#
673
# @param str [String] the string currently being typed before tab was hit
674
# @param words [Array<String>] the previously completed words on the command line. words is always
675
# at least 1 when tab completion has reached this stage since the command itself has been completed
676
def cmd_services_tabs(str, words)
677
if words.length == 1
678
return @@services_opts.option_keys.select { |opt| opt.start_with?(str) }
679
end
680
681
case words[-1]
682
when '-c', '--column'
683
return @@services_columns
684
when '-O', '--order'
685
return []
686
when '-o', '--output'
687
return tab_complete_filenames(str, words)
688
when '-p', '--port'
689
return []
690
when '-r', '--protocol'
691
return []
692
end
693
694
[]
695
end
696
697
def cmd_services_help
698
print_line "Usage: services [-h] [-u] [-a] [-r <proto>] [-p <port1,port2>] [-s <name1,name2>] [-o <filename>] [addr1 addr2 ...]"
699
print_line
700
print @@services_opts.usage
701
print_line
702
print_line "Available columns: #{@@services_columns.join(", ")}"
703
print_line
704
end
705
706
@@services_columns = [ 'created_at', 'info', 'name', 'port', 'proto', 'state', 'updated_at' ]
707
708
@@services_opts = Rex::Parser::Arguments.new(
709
[ '-a', '--add' ] => [ false, 'Add the services instead of searching.' ],
710
[ '-d', '--delete' ] => [ false, 'Delete the services instead of searching.' ],
711
[ '-U', '--update' ] => [ false, 'Update data for existing service.' ],
712
[ '-u', '--up' ] => [ false, 'Only show services which are up.' ],
713
[ '-c', '--column' ] => [ true, 'Only show the given columns.', '<col1,col2>' ],
714
[ '-p', '--port' ] => [ true, 'Search for a list of ports.', '<ports>' ],
715
[ '-r', '--protocol' ] => [ true, 'Protocol type of the service being added [tcp|udp].', '<protocol>' ],
716
[ '-s', '--name' ] => [ true, 'Name of the service to add.', '<name>' ],
717
[ '-o', '--output' ] => [ true, 'Send output to a file in csv format.', '<filename>' ],
718
[ '-O', '--order' ] => [ true, 'Order rows by specified column number.', '<column id>' ],
719
[ '-R', '--rhosts' ] => [ false, 'Set RHOSTS from the results of the search.' ],
720
[ '-S', '--search' ] => [ true, 'Search string to filter by.', '<filter>' ],
721
[ '-h', '--help' ] => [ false, 'Show this help information.' ]
722
)
723
724
def db_connection_info(framework)
725
unless framework.db.connection_established?
726
return "#{framework.db.driver} selected, no connection"
727
end
728
729
cdb = ''
730
if framework.db.driver == 'http'
731
cdb = framework.db.name
732
else
733
::ApplicationRecord.connection_pool.with_connection do |conn|
734
if conn.respond_to?(:current_database)
735
cdb = conn.current_database
736
end
737
end
738
end
739
740
if cdb.empty?
741
output = "Connected Database Name could not be extracted. DB Connection type: #{framework.db.driver}."
742
else
743
output = "Connected to #{cdb}. Connection type: #{framework.db.driver}."
744
end
745
746
output
747
end
748
749
def cmd_db_stats(*args)
750
return unless active?
751
print_line "Session Type: #{db_connection_info(framework)}"
752
753
current_workspace = framework.db.workspace
754
example_workspaces = ::Mdm::Workspace.order(id: :desc)
755
ordered_workspaces = ([current_workspace] + example_workspaces).uniq.sort_by(&:id)
756
757
tbl = Rex::Text::Table.new(
758
'Indent' => 2,
759
'Header' => "Database Stats",
760
'Columns' =>
761
[
762
"IsTarget",
763
"ID",
764
"Name",
765
"Hosts",
766
"Services",
767
"Services per Host",
768
"Vulnerabilities",
769
"Vulns per Host",
770
"Notes",
771
"Creds",
772
"Kerberos Cache"
773
],
774
'SortIndex' => 1,
775
'ColProps' => {
776
'IsTarget' => {
777
'Stylers' => [Msf::Ui::Console::TablePrint::RowIndicatorStyler.new],
778
'ColumnStylers' => [Msf::Ui::Console::TablePrint::OmitColumnHeader.new],
779
'Width' => 2
780
}
781
}
782
)
783
784
total_hosts = 0
785
total_services = 0
786
total_vulns = 0
787
total_notes = 0
788
total_creds = 0
789
total_tickets = 0
790
791
ordered_workspaces.map do |workspace|
792
793
hosts = workspace.hosts.count
794
services = workspace.services.count
795
vulns = workspace.vulns.count
796
notes = workspace.notes.count
797
creds = framework.db.creds(workspace: workspace.name).count # workspace.creds.count.to_fs(:delimited) is always 0 for whatever reason
798
kerbs = ticket_search([nil], nil, :workspace => workspace).count
799
800
total_hosts += hosts
801
total_services += services
802
total_vulns += vulns
803
total_notes += notes
804
total_creds += creds
805
total_tickets += kerbs
806
807
tbl << [
808
current_workspace.id == workspace.id,
809
workspace.id,
810
workspace.name,
811
hosts.to_fs(:delimited),
812
services.to_fs(:delimited),
813
hosts > 0 ? (services.to_f / hosts).truncate(2) : 0,
814
vulns.to_fs(:delimited),
815
hosts > 0 ? (vulns.to_f / hosts).truncate(2) : 0,
816
notes.to_fs(:delimited),
817
creds.to_fs(:delimited),
818
kerbs.to_fs(:delimited)
819
]
820
end
821
822
# total row
823
tbl << [
824
"",
825
"Total",
826
ordered_workspaces.length.to_fs(:delimited),
827
total_hosts.to_fs(:delimited),
828
total_services.to_fs(:delimited),
829
total_hosts > 0 ? (total_services.to_f / total_hosts).truncate(2) : 0,
830
total_vulns,
831
total_hosts > 0 ? (total_vulns.to_f / total_hosts).truncate(2) : 0,
832
total_notes,
833
total_creds.to_fs(:delimited),
834
total_tickets.to_fs(:delimited)
835
]
836
837
print_line tbl.to_s
838
end
839
840
def cmd_services(*args)
841
return unless active?
842
mode = :search
843
onlyup = false
844
output_file = nil
845
set_rhosts = false
846
col_search = ['port', 'proto', 'name', 'state', 'info']
847
848
names = nil
849
order_by = nil
850
proto = nil
851
host_ranges = []
852
port_ranges = []
853
rhosts = []
854
delete_count = 0
855
search_term = nil
856
opts = {}
857
858
@@services_opts.parse(args) do |opt, idx, val|
859
case opt
860
when '-a', '--add'
861
mode = :add
862
when '-d', '--delete'
863
mode = :delete
864
when '-U', '--update'
865
mode = :update
866
when '-u', '--up'
867
onlyup = true
868
when '-c'
869
list = val
870
if(!list)
871
print_error("Invalid column list")
872
return
873
end
874
col_search = list.strip().split(",")
875
col_search.each { |c|
876
if not @@services_columns.include? c
877
print_error("Invalid column list. Possible values are (#{@@services_columns.join("|")})")
878
return
879
end
880
}
881
when '-p'
882
unless (arg_port_range(val, port_ranges, true))
883
return
884
end
885
when '-r'
886
proto = val
887
if (!proto)
888
print_status("Invalid protocol")
889
return
890
end
891
proto = proto.strip
892
when '-s'
893
namelist = val
894
if (!namelist)
895
print_error("Invalid name list")
896
return
897
end
898
names = namelist.strip().split(",")
899
when '-o'
900
output_file = val
901
if (!output_file)
902
print_error("Invalid output filename")
903
return
904
end
905
output_file = ::File.expand_path(output_file)
906
when '-O'
907
if (order_by = val.to_i - 1) < 0
908
print_error('Please specify a column number starting from 1')
909
return
910
end
911
when '-R', '--rhosts'
912
set_rhosts = true
913
when '-S', '--search'
914
search_term = val
915
opts[:search_term] = search_term
916
when '-h', '--help'
917
cmd_services_help
918
return
919
else
920
# Anything that wasn't an option is a host to search for
921
unless (arg_host_range(val, host_ranges))
922
return
923
end
924
end
925
end
926
927
ports = port_ranges.flatten.uniq
928
929
if mode == :add
930
# Can only deal with one port and one service name at a time
931
# right now. Them's the breaks.
932
if ports.length != 1
933
print_error("Exactly one port required")
934
return
935
end
936
if host_ranges.empty?
937
print_error("Host address or range required")
938
return
939
end
940
host_ranges.each do |range|
941
range.each do |addr|
942
info = {
943
:host => addr,
944
:port => ports.first.to_i
945
}
946
info[:proto] = proto.downcase if proto
947
info[:name] = names.first.downcase if names and names.first
948
949
svc = framework.db.find_or_create_service(info)
950
print_status("Time: #{svc.created_at} Service: host=#{svc.host.address} port=#{svc.port} proto=#{svc.proto} name=#{svc.name}")
951
end
952
end
953
return
954
end
955
956
# If we got here, we're searching. Delete implies search
957
col_names = @@services_columns
958
if col_search
959
col_names = col_search
960
end
961
tbl = Rex::Text::Table.new({
962
'Header' => "Services",
963
'Columns' => ['host'] + col_names,
964
'SortIndex' => order_by
965
})
966
967
# Sentinel value meaning all
968
host_ranges.push(nil) if host_ranges.empty?
969
ports = nil if ports.empty?
970
matched_service_ids = []
971
972
each_host_range_chunk(host_ranges) do |host_search|
973
next if host_search && host_search.empty?
974
opts[:workspace] = framework.db.workspace
975
opts[:hosts] = {address: host_search} if !host_search.nil?
976
opts[:port] = ports if ports
977
framework.db.services(opts).each do |service|
978
979
unless service.state == 'open'
980
next if onlyup
981
end
982
983
host = service.host
984
matched_service_ids << service.id
985
986
if mode == :update
987
service.name = names.first if names
988
service.proto = proto if proto
989
service.port = ports.first if ports
990
framework.db.update_service(service.as_json.symbolize_keys)
991
end
992
993
columns = [host.address] + col_names.map { |n| service[n].to_s || "" }
994
tbl << columns
995
if set_rhosts
996
addr = (host.scope.to_s != "" ? host.address + '%' + host.scope : host.address )
997
rhosts << addr
998
end
999
end
1000
end
1001
1002
if (mode == :delete)
1003
result = framework.db.delete_service(ids: matched_service_ids)
1004
delete_count += result.size
1005
end
1006
1007
if (output_file == nil)
1008
print_line(tbl.to_s)
1009
else
1010
# create the output file
1011
::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) }
1012
print_status("Wrote services to #{output_file}")
1013
end
1014
1015
# Finally, handle the case where the user wants the resulting list
1016
# of hosts to go into RHOSTS.
1017
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
1018
1019
print_status("Deleted #{delete_count} services") if delete_count > 0
1020
1021
end
1022
1023
#
1024
# Tab completion for the vulns command
1025
#
1026
# @param str [String] the string currently being typed before tab was hit
1027
# @param words [Array<String>] the previously completed words on the command line. words is always
1028
# at least 1 when tab completion has reached this stage since the command itself has been completed
1029
def cmd_vulns_tabs(str, words)
1030
if words.length == 1
1031
return @@vulns_opts.option_keys.select { |opt| opt.start_with?(str) }
1032
end
1033
case words[-1]
1034
when '-o', '--output'
1035
return tab_complete_filenames(str, words)
1036
end
1037
end
1038
1039
def cmd_vulns_help
1040
print_line "Print all vulnerabilities in the database"
1041
print_line
1042
print_line "Usage: vulns [addr range]"
1043
print_line
1044
print @@vulns_opts.usage
1045
print_line
1046
print_line "Examples:"
1047
print_line " vulns -p 1-65536 # only vulns with associated services"
1048
print_line " vulns -p 1-65536 -s http # identified as http on any port"
1049
print_line
1050
end
1051
1052
@@vulns_opts = Rex::Parser::Arguments.new(
1053
[ '-h', '--help' ] => [ false, 'Show this help information.' ],
1054
[ '-o', '--output' ] => [ true, 'Send output to a file in csv format.', '<filename>' ],
1055
[ '-p', '--port' ] => [ true, 'List vulns matching this port spec.', '<port>' ],
1056
[ '-s', '--service' ] => [ true, 'List vulns matching these service names.', '<name>' ],
1057
[ '-R', '--rhosts' ] => [ false, 'Set RHOSTS from the results of the search.' ],
1058
[ '-S', '--search' ] => [ true, 'Search string to filter by.', '<filter>' ],
1059
[ '-i', '--info' ] => [ false, 'Display vuln information.' ],
1060
[ '-d', '--delete' ] => [ false, 'Delete vulnerabilities. Not officially supported.' ]
1061
)
1062
1063
def cmd_vulns(*args)
1064
return unless active?
1065
1066
default_columns = ['Timestamp', 'Host', 'Name', 'References']
1067
host_ranges = []
1068
port_ranges = []
1069
svcs = []
1070
rhosts = []
1071
1072
search_term = nil
1073
show_info = false
1074
set_rhosts = false
1075
output_file = nil
1076
delete_count = 0
1077
1078
mode = nil
1079
1080
@@vulns_opts.parse(args) do |opt, idx, val|
1081
case opt
1082
when '-d', '--delete' # TODO: This is currently undocumented because it's not officially supported.
1083
mode = :delete
1084
when '-h', '--help'
1085
cmd_vulns_help
1086
return
1087
when '-o', '--output'
1088
output_file = val
1089
if output_file
1090
output_file = File.expand_path(output_file)
1091
else
1092
print_error("Invalid output filename")
1093
return
1094
end
1095
when '-p', '--port'
1096
unless (arg_port_range(val, port_ranges, true))
1097
return
1098
end
1099
when '-s', '--service'
1100
service = val
1101
if (!service)
1102
print_error("Argument required for -s")
1103
return
1104
end
1105
svcs = service.split(/[\s]*,[\s]*/)
1106
when '-R', '--rhosts'
1107
set_rhosts = true
1108
when '-S', '--search'
1109
search_term = val
1110
when '-i', '--info'
1111
show_info = true
1112
else
1113
# Anything that wasn't an option is a host to search for
1114
unless (arg_host_range(val, host_ranges))
1115
return
1116
end
1117
end
1118
end
1119
1120
if show_info
1121
default_columns << 'Information'
1122
end
1123
1124
# add sentinel value meaning all if empty
1125
host_ranges.push(nil) if host_ranges.empty?
1126
# normalize
1127
ports = port_ranges.flatten.uniq
1128
svcs.flatten!
1129
tbl = Rex::Text::Table.new(
1130
'Header' => 'Vulnerabilities',
1131
'Columns' => default_columns
1132
)
1133
1134
matched_vuln_ids = []
1135
vulns = []
1136
if host_ranges.compact.empty?
1137
vulns = framework.db.vulns({:search_term => search_term})
1138
else
1139
each_host_range_chunk(host_ranges) do |host_search|
1140
next if host_search && host_search.empty?
1141
1142
vulns.concat(framework.db.vulns({:hosts => { :address => host_search }, :search_term => search_term }))
1143
end
1144
end
1145
1146
vulns.each do |vuln|
1147
reflist = vuln.refs.map {|r| r.name}
1148
if (vuln.service)
1149
# Skip this one if the user specified a port and it
1150
# doesn't match.
1151
next unless ports.empty? or ports.include? vuln.service.port
1152
# Same for service names
1153
next unless svcs.empty? or svcs.include?(vuln.service.name)
1154
else
1155
# This vuln has no service, so it can't match
1156
next unless ports.empty? and svcs.empty?
1157
end
1158
1159
matched_vuln_ids << vuln.id
1160
1161
row = []
1162
row << vuln.created_at
1163
row << vuln.host.address
1164
row << vuln.name
1165
row << reflist.join(',')
1166
if show_info
1167
row << vuln.info
1168
end
1169
tbl << row
1170
1171
if set_rhosts
1172
addr = (vuln.host.scope.to_s != "" ? vuln.host.address + '%' + vuln.host.scope : vuln.host.address)
1173
rhosts << addr
1174
end
1175
end
1176
1177
if mode == :delete
1178
result = framework.db.delete_vuln(ids: matched_vuln_ids)
1179
delete_count = result.size
1180
end
1181
1182
if output_file
1183
File.write(output_file, tbl.to_csv)
1184
print_status("Wrote vulnerability information to #{output_file}")
1185
else
1186
print_line
1187
print_line(tbl.to_s)
1188
end
1189
1190
# Finally, handle the case where the user wants the resulting list
1191
# of hosts to go into RHOSTS.
1192
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
1193
1194
print_status("Deleted #{delete_count} vulnerabilities") if delete_count > 0
1195
end
1196
1197
#
1198
# Tab completion for the notes command
1199
#
1200
# @param str [String] the string currently being typed before tab was hit
1201
# @param words [Array<String>] the previously completed words on the command line. words is always
1202
# at least 1 when tab completion has reached this stage since the command itself has been completed
1203
def cmd_notes_tabs(str, words)
1204
if words.length == 1
1205
return @@notes_opts.option_keys.select { |opt| opt.start_with?(str) }
1206
end
1207
1208
case words[-1]
1209
when '-O', '--order'
1210
return []
1211
when '-o', '--output'
1212
return tab_complete_filenames(str, words)
1213
end
1214
1215
[]
1216
end
1217
1218
def cmd_notes_help
1219
print_line "Usage: notes [-h] [-t <type1,type2>] [-n <data string>] [-a] [addr range]"
1220
print_line
1221
print @@notes_opts.usage
1222
print_line
1223
print_line "Examples:"
1224
print_line " notes --add -t apps -n 'winzip' 10.1.1.34 10.1.20.41"
1225
print_line " notes -t smb.fingerprint 10.1.1.34 10.1.20.41"
1226
print_line " notes -S 'nmap.nse.(http|rtsp)'"
1227
print_line
1228
end
1229
1230
@@notes_opts = Rex::Parser::Arguments.new(
1231
[ '-a', '--add' ] => [ false, 'Add a note to the list of addresses, instead of listing.' ],
1232
[ '-d', '--delete' ] => [ false, 'Delete the notes instead of searching.' ],
1233
[ '-h', '--help' ] => [ false, 'Show this help information.' ],
1234
[ '-n', '--note' ] => [ true, 'Set the data for a new note (only with -a).', '<note>' ],
1235
[ '-O', '--order' ] => [ true, 'Order rows by specified column number.', '<column id>' ],
1236
[ '-o', '--output' ] => [ true, 'Save the notes to a csv file.', '<filename>' ],
1237
[ '-R', '--rhosts' ] => [ false, 'Set RHOSTS from the results of the search.' ],
1238
[ '-S', '--search' ] => [ true, 'Search string to filter by.', '<filter>' ],
1239
[ '-t', '--type' ] => [ true, 'Search for a list of types, or set single type for add.', '<type1,type2>' ],
1240
[ '-u', '--update' ] => [ false, 'Update a note. Not officially supported.' ]
1241
)
1242
1243
def cmd_notes(*args)
1244
return unless active?
1245
::ApplicationRecord.connection_pool.with_connection {
1246
mode = :search
1247
data = nil
1248
types = nil
1249
set_rhosts = false
1250
1251
host_ranges = []
1252
rhosts = []
1253
search_term = nil
1254
output_file = nil
1255
delete_count = 0
1256
order_by = nil
1257
1258
@@notes_opts.parse(args) do |opt, idx, val|
1259
case opt
1260
when '-a', '--add'
1261
mode = :add
1262
when '-d', '--delete'
1263
mode = :delete
1264
when '-n', '--note'
1265
data = val
1266
if(!data)
1267
print_error("Can't make a note with no data")
1268
return
1269
end
1270
when '-t', '--type'
1271
typelist = val
1272
if(!typelist)
1273
print_error("Invalid type list")
1274
return
1275
end
1276
types = typelist.strip().split(",")
1277
when '-R', '--rhosts'
1278
set_rhosts = true
1279
when '-S', '--search'
1280
search_term = val
1281
when '-o', '--output'
1282
output_file = val
1283
output_file = ::File.expand_path(output_file)
1284
when '-O'
1285
if (order_by = val.to_i - 1) < 0
1286
print_error('Please specify a column number starting from 1')
1287
return
1288
end
1289
when '-u', '--update' # TODO: This is currently undocumented because it's not officially supported.
1290
mode = :update
1291
when '-h', '--help'
1292
cmd_notes_help
1293
return
1294
else
1295
# Anything that wasn't an option is a host to search for
1296
unless (arg_host_range(val, host_ranges))
1297
return
1298
end
1299
end
1300
end
1301
1302
if mode == :add
1303
if host_ranges.compact.empty?
1304
print_error("Host address or range required")
1305
return
1306
end
1307
1308
if types.nil? || types.size != 1
1309
print_error("Exactly one type is required")
1310
return
1311
end
1312
1313
if data.nil?
1314
print_error("Data required")
1315
return
1316
end
1317
1318
type = types.first
1319
host_ranges.each { |range|
1320
range.each { |addr|
1321
note = framework.db.find_or_create_note(host: addr, type: type, data: data)
1322
break if not note
1323
print_status("Time: #{note.created_at} Note: host=#{addr} type=#{note.ntype} data=#{note.data}")
1324
}
1325
}
1326
return
1327
end
1328
1329
if mode == :update
1330
if !types.nil? && types.size != 1
1331
print_error("Exactly one type is required")
1332
return
1333
end
1334
1335
if types.nil? && data.nil?
1336
print_error("Update requires data or type")
1337
return
1338
end
1339
end
1340
1341
note_list = []
1342
if host_ranges.compact.empty?
1343
# No host specified - collect all notes
1344
opts = {search_term: search_term}
1345
opts[:ntype] = types if mode != :update && types && !types.empty?
1346
note_list = framework.db.notes(opts)
1347
else
1348
# Collect notes of specified hosts
1349
each_host_range_chunk(host_ranges) do |host_search|
1350
next if host_search && host_search.empty?
1351
1352
opts = {hosts: {address: host_search}, workspace: framework.db.workspace, search_term: search_term}
1353
opts[:ntype] = types if mode != :update && types && !types.empty?
1354
note_list.concat(framework.db.notes(opts))
1355
end
1356
end
1357
1358
# Now display them
1359
table = Rex::Text::Table.new(
1360
'Header' => 'Notes',
1361
'Indent' => 1,
1362
'Columns' => ['Time', 'Host', 'Service', 'Port', 'Protocol', 'Type', 'Data'],
1363
'SortIndex' => order_by
1364
)
1365
1366
matched_note_ids = []
1367
note_list.each do |note|
1368
if mode == :update
1369
begin
1370
update_opts = {id: note.id}
1371
unless types.nil?
1372
note.ntype = types.first
1373
update_opts[:ntype] = types.first
1374
end
1375
1376
unless data.nil?
1377
note.data = data
1378
update_opts[:data] = data
1379
end
1380
1381
framework.db.update_note(update_opts)
1382
rescue => e
1383
elog "There was an error updating note with ID #{note.id}: #{e.message}"
1384
next
1385
end
1386
end
1387
1388
matched_note_ids << note.id
1389
1390
row = []
1391
row << note.created_at
1392
1393
if note.host
1394
host = note.host
1395
row << host.address
1396
if set_rhosts
1397
addr = (host.scope.to_s != "" ? host.address + '%' + host.scope : host.address)
1398
rhosts << addr
1399
end
1400
else
1401
row << ''
1402
end
1403
1404
if note.service
1405
row << note.service.name || ''
1406
row << note.service.port || ''
1407
row << note.service.proto || ''
1408
else
1409
row << '' # For the Service field
1410
row << '' # For the Port field
1411
row << '' # For the Protocol field
1412
end
1413
1414
row << note.ntype
1415
row << note.data.inspect
1416
table << row
1417
end
1418
1419
if mode == :delete
1420
result = framework.db.delete_note(ids: matched_note_ids)
1421
delete_count = result.size
1422
end
1423
1424
if output_file
1425
save_csv_notes(output_file, table)
1426
else
1427
print_line
1428
print_line(table.to_s)
1429
end
1430
1431
# Finally, handle the case where the user wants the resulting list
1432
# of hosts to go into RHOSTS.
1433
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
1434
1435
print_status("Deleted #{delete_count} notes") if delete_count > 0
1436
}
1437
end
1438
1439
def save_csv_notes(fpath, table)
1440
begin
1441
File.open(fpath, 'wb') do |f|
1442
f.write(table.to_csv)
1443
end
1444
print_status("Wrote notes to #{fpath}")
1445
rescue Errno::EACCES => e
1446
print_error("Unable to save notes. #{e.message}")
1447
end
1448
end
1449
1450
#
1451
# Tab completion for the loot command
1452
#
1453
# @param str [String] the string currently being typed before tab was hit
1454
# @param words [Array<String>] the previously completed words on the command line. words is always
1455
# at least 1 when tab completion has reached this stage since the command itself has been completed
1456
def cmd_loot_tabs(str, words)
1457
if words.length == 1
1458
@@loot_opts.option_keys.select { |opt| opt.start_with?(str) }
1459
end
1460
end
1461
1462
def cmd_loot_help
1463
print_line "Usage: loot [options]"
1464
print_line " Info: loot [-h] [addr1 addr2 ...] [-t <type1,type2>]"
1465
print_line " Add: loot -f [fname] -i [info] -a [addr1 addr2 ...] -t [type]"
1466
print_line " Del: loot -d [addr1 addr2 ...]"
1467
print_line
1468
print @@loot_opts.usage
1469
print_line
1470
end
1471
1472
@@loot_opts = Rex::Parser::Arguments.new(
1473
[ '-a', '--add' ] => [ false, 'Add loot to the list of addresses, instead of listing.' ],
1474
[ '-d', '--delete' ] => [ false, 'Delete *all* loot matching host and type.' ],
1475
[ '-f', '--file' ] => [ true, 'File with contents of the loot to add.', '<filename>' ],
1476
[ '-i', '--info' ] => [ true, 'Info of the loot to add.', '<info>' ],
1477
[ '-t', '--type' ] => [ true, 'Search for a list of types.', '<type1,type2>' ],
1478
[ '-h', '--help' ] => [ false, 'Show this help information.' ],
1479
[ '-S', '--search' ] => [ true, 'Search string to filter by.', '<filter>' ],
1480
[ '-u', '--update' ] => [ false, 'Update loot. Not officially supported.' ]
1481
)
1482
1483
def cmd_loot(*args)
1484
return unless active?
1485
1486
mode = :search
1487
host_ranges = []
1488
types = nil
1489
delete_count = 0
1490
search_term = nil
1491
file = nil
1492
name = nil
1493
info = nil
1494
filename = nil
1495
1496
@@loot_opts.parse(args) do |opt, idx, val|
1497
case opt
1498
when '-a', '--add'
1499
mode = :add
1500
when '-d', '--delete'
1501
mode = :delete
1502
when '-f', '--file'
1503
filename = val
1504
if(!filename)
1505
print_error("Can't make loot with no filename")
1506
return
1507
end
1508
if (!File.exist?(filename) or !File.readable?(filename))
1509
print_error("Can't read file")
1510
return
1511
end
1512
when '-i', '--info'
1513
info = val
1514
if(!info)
1515
print_error("Can't make loot with no info")
1516
return
1517
end
1518
when '-t', '--type'
1519
typelist = val
1520
if(!typelist)
1521
print_error("Invalid type list")
1522
return
1523
end
1524
types = typelist.strip().split(",")
1525
when '-S', '--search'
1526
search_term = val
1527
when '-u', '--update' # TODO: This is currently undocumented because it's not officially supported.
1528
mode = :update
1529
when '-h', '--help'
1530
cmd_loot_help
1531
return
1532
else
1533
# Anything that wasn't an option is a host to search for
1534
unless (arg_host_range(val, host_ranges))
1535
return
1536
end
1537
end
1538
end
1539
1540
tbl = Rex::Text::Table.new({
1541
'Header' => "Loot",
1542
'Columns' => [ 'host', 'service', 'type', 'name', 'content', 'info', 'path' ],
1543
# For now, don't perform any word wrapping on the loot table as it breaks the workflow of
1544
# copying paths and pasting them into applications
1545
'WordWrap' => false,
1546
})
1547
1548
# Sentinel value meaning all
1549
host_ranges.push(nil) if host_ranges.empty?
1550
1551
if mode == :add
1552
if host_ranges.compact.empty?
1553
print_error('Address list required')
1554
return
1555
end
1556
if info.nil?
1557
print_error("Info required")
1558
return
1559
end
1560
if filename.nil?
1561
print_error("Loot file required")
1562
return
1563
end
1564
if types.nil? or types.size != 1
1565
print_error("Exactly one loot type is required")
1566
return
1567
end
1568
type = types.first
1569
name = File.basename(filename)
1570
file = File.open(filename, "rb")
1571
contents = file.read
1572
host_ranges.each do |range|
1573
range.each do |host|
1574
lootfile = framework.db.find_or_create_loot(:type => type, :host => host, :info => info, :data => contents, :path => filename, :name => name)
1575
print_status("Added loot for #{host} (#{lootfile})")
1576
end
1577
end
1578
return
1579
end
1580
1581
matched_loot_ids = []
1582
loots = []
1583
if host_ranges.compact.empty?
1584
loots = loots + framework.db.loots(workspace: framework.db.workspace, search_term: search_term)
1585
else
1586
each_host_range_chunk(host_ranges) do |host_search|
1587
next if host_search && host_search.empty?
1588
1589
loots = loots + framework.db.loots(workspace: framework.db.workspace, hosts: { address: host_search }, search_term: search_term)
1590
end
1591
end
1592
1593
loots.each do |loot|
1594
row = []
1595
# TODO: This is just a temp implementation of update for the time being since it did not exist before.
1596
# It should be updated to not pass all of the attributes attached to the object, only the ones being updated.
1597
if mode == :update
1598
begin
1599
loot.info = info if info
1600
if types && types.size > 1
1601
print_error "May only pass 1 type when performing an update."
1602
next
1603
end
1604
loot.ltype = types.first if types
1605
framework.db.update_loot(loot.as_json.symbolize_keys)
1606
rescue => e
1607
elog "There was an error updating loot with ID #{loot.id}: #{e.message}"
1608
next
1609
end
1610
end
1611
row.push (loot.host && loot.host.address) ? loot.host.address : ""
1612
if (loot.service)
1613
svc = (loot.service.name ? loot.service.name : "#{loot.service.port}/#{loot.service.proto}")
1614
row.push svc
1615
else
1616
row.push ""
1617
end
1618
row.push(loot.ltype)
1619
row.push(loot.name || "")
1620
row.push(loot.content_type)
1621
row.push(loot.info || "")
1622
row.push(loot.path)
1623
1624
tbl << row
1625
matched_loot_ids << loot.id
1626
end
1627
1628
if (mode == :delete)
1629
result = framework.db.delete_loot(ids: matched_loot_ids)
1630
delete_count = result.size
1631
end
1632
1633
print_line
1634
print_line(tbl.to_s)
1635
print_status("Deleted #{delete_count} loots") if delete_count > 0
1636
end
1637
1638
# :category: Deprecated Commands
1639
def cmd_db_hosts_help; deprecated_help(:hosts); end
1640
# :category: Deprecated Commands
1641
def cmd_db_notes_help; deprecated_help(:notes); end
1642
# :category: Deprecated Commands
1643
def cmd_db_vulns_help; deprecated_help(:vulns); end
1644
# :category: Deprecated Commands
1645
def cmd_db_services_help; deprecated_help(:services); end
1646
# :category: Deprecated Commands
1647
def cmd_db_autopwn_help; deprecated_help; end
1648
# :category: Deprecated Commands
1649
def cmd_db_driver_help; deprecated_help; end
1650
1651
# :category: Deprecated Commands
1652
def cmd_db_hosts(*args); deprecated_cmd(:hosts, *args); end
1653
# :category: Deprecated Commands
1654
def cmd_db_notes(*args); deprecated_cmd(:notes, *args); end
1655
# :category: Deprecated Commands
1656
def cmd_db_vulns(*args); deprecated_cmd(:vulns, *args); end
1657
# :category: Deprecated Commands
1658
def cmd_db_services(*args); deprecated_cmd(:services, *args); end
1659
# :category: Deprecated Commands
1660
def cmd_db_autopwn(*args); deprecated_cmd; end
1661
1662
#
1663
# :category: Deprecated Commands
1664
#
1665
# This one deserves a little more explanation than standard deprecation
1666
# warning, so give the user a better understanding of what's going on.
1667
#
1668
def cmd_db_driver(*args)
1669
deprecated_cmd
1670
print_line
1671
print_line "Because Metasploit no longer supports databases other than the default"
1672
print_line "PostgreSQL, there is no longer a need to set the driver. Thus db_driver"
1673
print_line "is not useful and its functionality has been removed. Usually Metasploit"
1674
print_line "will already have connected to the database; check db_status to see."
1675
print_line
1676
cmd_db_status
1677
end
1678
1679
def cmd_db_import_tabs(str, words)
1680
tab_complete_filenames(str, words)
1681
end
1682
1683
def cmd_db_import_help
1684
print_line "Usage: db_import <filename> [file2...]"
1685
print_line
1686
print_line "Filenames can be globs like *.xml, or **/*.xml which will search recursively"
1687
print_line "Currently supported file types include:"
1688
print_line " Acunetix"
1689
print_line " Amap Log"
1690
print_line " Amap Log -m"
1691
print_line " Appscan"
1692
print_line " Burp Session XML"
1693
print_line " Burp Issue XML"
1694
print_line " CI"
1695
print_line " Foundstone"
1696
print_line " FusionVM XML"
1697
print_line " Group Policy Preferences Credentials"
1698
print_line " IP Address List"
1699
print_line " IP360 ASPL"
1700
print_line " IP360 XML v3"
1701
print_line " Libpcap Packet Capture"
1702
print_line " Masscan XML"
1703
print_line " Metasploit PWDump Export"
1704
print_line " Metasploit XML"
1705
print_line " Metasploit Zip Export"
1706
print_line " Microsoft Baseline Security Analyzer"
1707
print_line " NeXpose Simple XML"
1708
print_line " NeXpose XML Report"
1709
print_line " Nessus NBE Report"
1710
print_line " Nessus XML (v1)"
1711
print_line " Nessus XML (v2)"
1712
print_line " NetSparker XML"
1713
print_line " Nikto XML"
1714
print_line " Nmap XML"
1715
print_line " OpenVAS Report"
1716
print_line " OpenVAS XML (optional arguments -cert -dfn)"
1717
print_line " Outpost24 XML"
1718
print_line " Qualys Asset XML"
1719
print_line " Qualys Scan XML"
1720
print_line " Retina XML"
1721
print_line " Spiceworks CSV Export"
1722
print_line " Wapiti XML"
1723
print_line
1724
end
1725
1726
#
1727
# Generic import that automatically detects the file type
1728
#
1729
def cmd_db_import(*args)
1730
return unless active?
1731
openvas_cert = false
1732
openvas_dfn = false
1733
::ApplicationRecord.connection_pool.with_connection {
1734
if args.include?("-h") || ! (args && args.length > 0)
1735
cmd_db_import_help
1736
return
1737
end
1738
if args.include?("-dfn")
1739
openvas_dfn = true
1740
end
1741
if args.include?("-cert")
1742
openvas_cert = true
1743
end
1744
options = {:openvas_dfn => openvas_dfn, :openvas_cert => openvas_cert}
1745
args.each { |glob|
1746
next if (glob.include?("-cert") || glob.include?("-dfn"))
1747
files = ::Dir.glob(::File.expand_path(glob))
1748
if files.empty?
1749
print_error("No such file #{glob}")
1750
next
1751
end
1752
files.each { |filename|
1753
if (not ::File.readable?(filename))
1754
print_error("Could not read file #{filename}")
1755
next
1756
end
1757
begin
1758
warnings = 0
1759
framework.db.import_file(:filename => filename, :options => options) do |type,data|
1760
case type
1761
when :debug
1762
print_error("DEBUG: #{data.inspect}")
1763
when :vuln
1764
inst = data[1] == 1 ? "instance" : "instances"
1765
print_status("Importing vulnerability '#{data[0]}' (#{data[1]} #{inst})")
1766
when :filetype
1767
print_status("Importing '#{data}' data")
1768
when :parser
1769
print_status("Import: Parsing with '#{data}'")
1770
when :address
1771
print_status("Importing host #{data}")
1772
when :service
1773
print_status("Importing service #{data}")
1774
when :msf_loot
1775
print_status("Importing loot #{data}")
1776
when :msf_task
1777
print_status("Importing task #{data}")
1778
when :msf_report
1779
print_status("Importing report #{data}")
1780
when :pcap_count
1781
print_status("Import: #{data} packets processed")
1782
when :record_count
1783
print_status("Import: #{data[1]} records processed")
1784
when :warning
1785
print_error
1786
data.split("\n").each do |line|
1787
print_error(line)
1788
end
1789
print_error
1790
warnings += 1
1791
end
1792
end
1793
print_status("Successfully imported #{filename}")
1794
1795
print_error("Please note that there were #{warnings} warnings") if warnings > 1
1796
print_error("Please note that there was one warning") if warnings == 1
1797
1798
rescue Msf::DBImportError => e
1799
print_error("Failed to import #{filename}: #{$!}")
1800
elog("Failed to import #{filename}", error: e)
1801
dlog("Call stack: #{$@.join("\n")}", LEV_3)
1802
next
1803
rescue REXML::ParseException => e
1804
print_error("Failed to import #{filename} due to malformed XML:")
1805
print_error("#{e.class}: #{e}")
1806
elog("Failed to import #{filename}", error: e)
1807
dlog("Call stack: #{$@.join("\n")}", LEV_3)
1808
next
1809
end
1810
}
1811
}
1812
}
1813
end
1814
1815
def cmd_db_export_help
1816
# Like db_hosts and db_services, this creates a list of columns, so
1817
# use its -h
1818
cmd_db_export("-h")
1819
end
1820
1821
#
1822
# Export an XML
1823
#
1824
def cmd_db_export(*args)
1825
return unless active?
1826
::ApplicationRecord.connection_pool.with_connection {
1827
1828
export_formats = %W{xml pwdump}
1829
format = 'xml'
1830
output = nil
1831
1832
while (arg = args.shift)
1833
case arg
1834
when '-h','--help'
1835
print_line "Usage:"
1836
print_line " db_export -f <format> [filename]"
1837
print_line " Format can be one of: #{export_formats.join(", ")}"
1838
when '-f','--format'
1839
format = args.shift.to_s.downcase
1840
else
1841
output = arg
1842
end
1843
end
1844
1845
if not output
1846
print_error("No output file was specified")
1847
return
1848
end
1849
1850
if not export_formats.include?(format)
1851
print_error("Unsupported file format: #{format}")
1852
print_error("Unsupported file format: '#{format}'. Must be one of: #{export_formats.join(", ")}")
1853
return
1854
end
1855
1856
print_status("Starting export of workspace #{framework.db.workspace.name} to #{output} [ #{format} ]...")
1857
framework.db.run_db_export(output, format)
1858
print_status("Finished export of workspace #{framework.db.workspace.name} to #{output} [ #{format} ]...")
1859
}
1860
end
1861
1862
def find_nmap_path
1863
Rex::FileUtils.find_full_path("nmap") || Rex::FileUtils.find_full_path("nmap.exe")
1864
end
1865
1866
#
1867
# Import Nmap data from a file
1868
#
1869
def cmd_db_nmap(*args)
1870
return unless active?
1871
::ApplicationRecord.connection_pool.with_connection {
1872
if (args.length == 0)
1873
print_status("Usage: db_nmap [--save | [--help | -h]] [nmap options]")
1874
return
1875
end
1876
1877
save = false
1878
arguments = []
1879
while (arg = args.shift)
1880
case arg
1881
when '--save'
1882
save = true
1883
when '--help', '-h'
1884
cmd_db_nmap_help
1885
return
1886
else
1887
arguments << arg
1888
end
1889
end
1890
1891
nmap = find_nmap_path
1892
unless nmap
1893
print_error("The nmap executable could not be found")
1894
return
1895
end
1896
1897
fd = Rex::Quickfile.new(['msf-db-nmap-', '.xml'], Msf::Config.local_directory)
1898
1899
begin
1900
# When executing native Nmap in Cygwin, expand the Cygwin path to a Win32 path
1901
if(Rex::Compat.is_cygwin and nmap =~ /cygdrive/)
1902
# Custom function needed because cygpath breaks on 8.3 dirs
1903
tout = Rex::Compat.cygwin_to_win32(fd.path)
1904
arguments.push('-oX', tout)
1905
else
1906
arguments.push('-oX', fd.path)
1907
end
1908
1909
run_nmap(nmap, arguments)
1910
1911
framework.db.import_nmap_xml_file(:filename => fd.path)
1912
1913
print_status("Saved NMAP XML results to #{fd.path}") if save
1914
ensure
1915
fd.close
1916
fd.unlink unless save
1917
end
1918
}
1919
end
1920
1921
def cmd_db_nmap_help
1922
nmap = find_nmap_path
1923
unless nmap
1924
print_error("The nmap executable could not be found")
1925
return
1926
end
1927
1928
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
1929
1930
stdout.each_line do |out_line|
1931
next if out_line.strip.empty?
1932
print_status(out_line.strip)
1933
end
1934
1935
stderr.each_line do |err_line|
1936
next if err_line.strip.empty?
1937
print_error(err_line.strip)
1938
end
1939
end
1940
1941
def cmd_db_nmap_tabs(str, words)
1942
nmap = find_nmap_path
1943
unless nmap
1944
return
1945
end
1946
1947
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
1948
tabs = []
1949
stdout.each_line do |out_line|
1950
if out_line.strip.starts_with?('-')
1951
tabs.push(out_line.strip.split(':').first)
1952
end
1953
end
1954
1955
stderr.each_line do |err_line|
1956
next if err_line.strip.empty?
1957
print_error(err_line.strip)
1958
end
1959
1960
return tabs
1961
end
1962
1963
#
1964
# Database management
1965
#
1966
def db_check_driver
1967
unless framework.db.driver
1968
print_error("No database driver installed.")
1969
return false
1970
end
1971
true
1972
end
1973
1974
#
1975
# Is everything working?
1976
#
1977
def cmd_db_status(*args)
1978
return if not db_check_driver
1979
1980
if framework.db.connection_established?
1981
print_connection_info
1982
else
1983
print_status("#{framework.db.driver} selected, no connection")
1984
end
1985
end
1986
1987
1988
def cmd_db_connect_help
1989
print_line(" USAGE:")
1990
print_line(" * Postgres Data Service:")
1991
print_line(" db_connect <user:[pass]>@<host:[port]>/<database>")
1992
print_line(" Examples:")
1993
print_line(" db_connect user@metasploit3")
1994
print_line(" db_connect user:[email protected]/metasploit3")
1995
print_line(" db_connect user:[email protected]:1500/metasploit3")
1996
print_line(" db_connect -y [path/to/database.yml]")
1997
print_line(" ")
1998
print_line(" * HTTP Data Service:")
1999
print_line(" db_connect [options] <http|https>://<host:[port]>")
2000
print_line(" Examples:")
2001
print_line(" db_connect http://localhost:8080")
2002
print_line(" db_connect http://my-super-msf-data.service.com")
2003
print_line(" db_connect -c ~/cert.pem -t 6a7a74c1a5003802c955ead1bbddd4ab1b05a7f2940b4732d34bfc555bc6e1c5d7611a497b29e8f0 https://localhost:8080")
2004
print_line(" NOTE: You must be connected to a Postgres data service in order to successfully connect to a HTTP data service.")
2005
print_line(" ")
2006
print_line(" Persisting Connections:")
2007
print_line(" db_connect --name <name to save connection as> [options] <address>")
2008
print_line(" Examples:")
2009
print_line(" Saving: db_connect --name LA-server http://123.123.123.45:1234")
2010
print_line(" Connecting: db_connect LA-server")
2011
print_line(" ")
2012
print_line(" OPTIONS:")
2013
print_line(" -l,--list-services List the available data services that have been previously saved.")
2014
print_line(" -y,--yaml Connect to the data service specified in the provided database.yml file.")
2015
print_line(" -n,--name Name used to store the connection. Providing an existing name will overwrite the settings for that connection.")
2016
print_line(" -c,--cert Certificate file matching the remote data server's certificate. Needed when using self-signed SSL cert.")
2017
print_line(" -t,--token The API token used to authenticate to the remote data service.")
2018
print_line(" --skip-verify Skip validating authenticity of server's certificate (NOT RECOMMENDED).")
2019
print_line("")
2020
end
2021
2022
def cmd_db_connect(*args)
2023
return if not db_check_driver
2024
2025
opts = {}
2026
while (arg = args.shift)
2027
case arg
2028
when '-h', '--help'
2029
cmd_db_connect_help
2030
return
2031
when '-y', '--yaml'
2032
opts[:yaml_file] = args.shift
2033
when '-c', '--cert'
2034
opts[:cert] = args.shift
2035
when '-t', '--token'
2036
opts[:api_token] = args.shift
2037
when '-l', '--list-services'
2038
list_saved_data_services
2039
return
2040
when '-n', '--name'
2041
opts[:name] = args.shift
2042
if opts[:name] =~ /\/|\[|\]/
2043
print_error "Provided name contains an invalid character. Aborting connection."
2044
return
2045
end
2046
when '--skip-verify'
2047
opts[:skip_verify] = true
2048
else
2049
found_name = ::Msf::DbConnector.data_service_search(name: arg)
2050
if found_name
2051
opts = ::Msf::DbConnector.load_db_config(found_name)
2052
else
2053
opts[:url] = arg
2054
end
2055
end
2056
end
2057
2058
if !opts[:url] && !opts[:yaml_file]
2059
print_error 'A URL or saved data service name is required.'
2060
print_line
2061
cmd_db_connect_help
2062
return
2063
end
2064
2065
if opts[:url] =~ /http/
2066
new_conn_type = 'http'
2067
else
2068
new_conn_type = framework.db.driver
2069
end
2070
2071
# Currently only able to be connected to one DB at a time
2072
if framework.db.connection_established?
2073
# But the http connection still requires a local database to support AR, so we have to allow that
2074
# Don't allow more than one HTTP service, though
2075
if new_conn_type != 'http' || framework.db.get_services_metadata.count >= 2
2076
print_error('Connection already established. Only one connection is allowed at a time.')
2077
print_error('Run db_disconnect first if you wish to connect to a different data service.')
2078
print_line
2079
print_line 'Current connection information:'
2080
print_connection_info
2081
return
2082
end
2083
end
2084
2085
result = Msf::DbConnector.db_connect(framework, opts)
2086
if result[:error]
2087
print_error result[:error]
2088
return
2089
end
2090
2091
if result[:result]
2092
print_status result[:result]
2093
end
2094
if framework.db.active
2095
name = opts[:name]
2096
if !name || name.empty?
2097
if found_name
2098
name = found_name
2099
elsif result[:data_service_name]
2100
name = result[:data_service_name]
2101
else
2102
name = Rex::Text.rand_text_alphanumeric(8)
2103
end
2104
end
2105
2106
save_db_to_config(framework.db, name)
2107
@current_data_service = name
2108
end
2109
end
2110
2111
def cmd_db_disconnect_help
2112
print_line "Usage:"
2113
print_line " db_disconnect Temporarily disconnects from the currently configured dataservice."
2114
print_line " db_disconnect --clear Clears the default dataservice that msfconsole will use when opened."
2115
print_line
2116
end
2117
2118
def cmd_db_disconnect(*args)
2119
return if not db_check_driver
2120
2121
if args[0] == '-h' || args[0] == '--help'
2122
cmd_db_disconnect_help
2123
return
2124
elsif args[0] == '-c' || args[0] == '--clear'
2125
clear_default_db
2126
return
2127
end
2128
2129
previous_name = framework.db.name
2130
result = Msf::DbConnector.db_disconnect(framework)
2131
2132
if result[:error]
2133
print_error "Unable to disconnect from the data service: #{@current_data_service}"
2134
print_error result[:error]
2135
elsif result[:old_data_service_name].nil?
2136
print_error 'Not currently connected to a data service.'
2137
else
2138
print_line "Successfully disconnected from the data service: #{previous_name}."
2139
@current_data_service = result[:data_service_name]
2140
if @current_data_service
2141
print_line "Now connected to: #{@current_data_service}."
2142
end
2143
end
2144
end
2145
2146
def cmd_db_rebuild_cache(*args)
2147
print_line "This command is deprecated with Metasploit 5"
2148
end
2149
2150
def cmd_db_save_help
2151
print_line "Usage: db_save"
2152
print_line
2153
print_line "Save the current data service connection as the default to reconnect on startup."
2154
print_line
2155
end
2156
2157
def cmd_db_save(*args)
2158
while (arg = args.shift)
2159
case arg
2160
when '-h', '--help'
2161
cmd_db_save_help
2162
return
2163
end
2164
end
2165
2166
if !framework.db.active || !@current_data_service
2167
print_error "Not currently connected to a data service that can be saved."
2168
return
2169
end
2170
2171
begin
2172
Msf::Config.save(DB_CONFIG_PATH => { 'default_db' => @current_data_service })
2173
print_line "Successfully saved data service as default: #{@current_data_service}"
2174
rescue ArgumentError => e
2175
print_error e.message
2176
end
2177
end
2178
2179
def save_db_to_config(database, database_name)
2180
if database_name =~ /\/|\[|\]/
2181
raise ArgumentError, 'Data service name contains an invalid character.'
2182
end
2183
config_path = "#{DB_CONFIG_PATH}/#{database_name}"
2184
config_opts = {}
2185
if !database.is_local?
2186
begin
2187
config_opts['url'] = database.endpoint
2188
if database.https_opts
2189
config_opts['cert'] = database.https_opts[:cert] if database.https_opts[:cert]
2190
config_opts['skip_verify'] = true if database.https_opts[:skip_verify]
2191
end
2192
if database.api_token
2193
config_opts['api_token'] = database.api_token
2194
end
2195
Msf::Config.save(config_path => config_opts)
2196
rescue => e
2197
print_error "There was an error saving the data service configuration: #{e.message}"
2198
end
2199
else
2200
url = Msf::DbConnector.build_postgres_url
2201
config_opts['url'] = url
2202
Msf::Config.save(config_path => config_opts)
2203
end
2204
end
2205
2206
def cmd_db_remove_help
2207
print_line "Usage: db_remove <name>"
2208
print_line
2209
print_line "Delete the specified saved data service."
2210
print_line
2211
end
2212
2213
def cmd_db_remove(*args)
2214
if args[0] == '-h' || args[0] == '--help' || args[0].nil? || args[0].empty?
2215
cmd_db_remove_help
2216
return
2217
end
2218
delete_db_from_config(args[0])
2219
end
2220
2221
def delete_db_from_config(db_name)
2222
conf = Msf::Config.load
2223
db_path = "#{DB_CONFIG_PATH}/#{db_name}"
2224
if conf[db_path]
2225
clear_default_db if conf[DB_CONFIG_PATH]['default_db'] && conf[DB_CONFIG_PATH]['default_db'] == db_name
2226
Msf::Config.delete_group(db_path)
2227
print_line "Successfully deleted data service: #{db_name}"
2228
else
2229
print_line "Unable to locate saved data service with name #{db_name}."
2230
end
2231
end
2232
2233
def clear_default_db
2234
conf = Msf::Config.load
2235
if conf[DB_CONFIG_PATH] && conf[DB_CONFIG_PATH]['default_db']
2236
updated_opts = conf[DB_CONFIG_PATH]
2237
updated_opts.delete('default_db')
2238
Msf::Config.save(DB_CONFIG_PATH => updated_opts)
2239
print_line "Cleared the default data service."
2240
else
2241
print_line "No default data service was configured."
2242
end
2243
end
2244
2245
def db_find_tools(tools)
2246
missed = []
2247
tools.each do |name|
2248
if(! Rex::FileUtils.find_full_path(name))
2249
missed << name
2250
end
2251
end
2252
if(not missed.empty?)
2253
print_error("This database command requires the following tools to be installed: #{missed.join(", ")}")
2254
return
2255
end
2256
true
2257
end
2258
2259
#######
2260
private
2261
2262
def run_nmap(nmap, arguments, use_sudo: false)
2263
print_warning('Running Nmap with sudo') if use_sudo
2264
begin
2265
nmap_pipe = use_sudo ? ::Open3::popen3('sudo', nmap, *arguments) : ::Open3::popen3(nmap, *arguments)
2266
temp_nmap_threads = []
2267
temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1|
2268
np_1.each_line do |nmap_out|
2269
next if nmap_out.strip.empty?
2270
print_status("Nmap: #{nmap_out.strip}")
2271
end
2272
end
2273
2274
temp_nmap_threads << framework.threads.spawn("db_nmap-Stderr", false, nmap_pipe[2]) do |np_2|
2275
2276
np_2.each_line do |nmap_err|
2277
next if nmap_err.strip.empty?
2278
print_status("Nmap: '#{nmap_err.strip}'")
2279
# Check if the stderr text includes 'root', this only happens if the scan requires root privileges
2280
if nmap_err =~ /requires? root privileges/ or
2281
nmap_err.include? 'only works if you are root' or nmap_err =~ /requires? raw socket access/
2282
return run_nmap(nmap, arguments, use_sudo: true) unless use_sudo
2283
end
2284
end
2285
end
2286
2287
temp_nmap_threads.map { |t| t.join rescue nil }
2288
nmap_pipe.each { |p| p.close rescue nil }
2289
rescue ::IOError
2290
end
2291
end
2292
2293
#######
2294
2295
def print_connection_info
2296
cdb = ''
2297
if framework.db.driver == 'http'
2298
cdb = framework.db.name
2299
else
2300
::ApplicationRecord.connection_pool.with_connection do |conn|
2301
if conn.respond_to?(:current_database)
2302
cdb = conn.current_database
2303
end
2304
end
2305
end
2306
output = "Connected to #{cdb}. Connection type: #{framework.db.driver}."
2307
output += " Connection name: #{@current_data_service}." if @current_data_service
2308
print_status(output)
2309
end
2310
2311
def list_saved_data_services
2312
conf = Msf::Config.load
2313
default = nil
2314
tbl = Rex::Text::Table.new({
2315
'Header' => 'Data Services',
2316
'Columns' => ['current', 'name', 'url', 'default?'],
2317
'SortIndex' => 1
2318
})
2319
2320
conf.each_pair do |k,v|
2321
if k =~ /#{DB_CONFIG_PATH}/
2322
default = v['default_db'] if v['default_db']
2323
name = k.split('/').last
2324
next if name == 'database' # Data service information is not stored in 'framework/database', just metadata
2325
url = v['url']
2326
current = ''
2327
current = '*' if name == @current_data_service
2328
default_output = ''
2329
default_output = '*' if name == default
2330
line = [current, name, url, default_output]
2331
tbl << line
2332
end
2333
end
2334
print_line
2335
print_line tbl.to_s
2336
end
2337
2338
def print_msgs(status_msg, error_msg)
2339
status_msg.each do |s|
2340
print_status(s)
2341
end
2342
2343
error_msg.each do |e|
2344
print_error(e)
2345
end
2346
end
2347
2348
end
2349
2350
end end end end
2351
2352