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/nexpose.rb
Views: 11705
1
require 'English'
2
require 'nexpose'
3
4
module Msf
5
Nexpose_yaml = "#{Msf::Config.config_directory}/nexpose.yaml".freeze # location of the nexpose.yml containing saved nexpose creds
6
7
# This plugin provides integration with Rapid7 Nexpose
8
class Plugin::Nexpose < Msf::Plugin
9
class NexposeCommandDispatcher
10
include Msf::Ui::Console::CommandDispatcher
11
12
def name
13
'Nexpose'
14
end
15
16
def commands
17
{
18
'nexpose_connect' => 'Connect to a running Nexpose instance ( user:pass@host[:port] )',
19
'nexpose_save' => 'Save credentials to a Nexpose instance',
20
'nexpose_activity' => 'Display any active scan jobs on the Nexpose instance',
21
22
'nexpose_scan' => 'Launch a Nexpose scan against a specific IP range and import the results',
23
'nexpose_discover' => 'Launch a scan but only perform host and minimal service discovery',
24
'nexpose_exhaustive' => 'Launch a scan covering all TCP ports and all authorized safe checks',
25
'nexpose_dos' => 'Launch a scan that includes checks that can crash services and devices (caution)',
26
27
'nexpose_disconnect' => 'Disconnect from an active Nexpose instance',
28
29
'nexpose_sites' => 'List all defined sites',
30
'nexpose_site_devices' => 'List all discovered devices within a site',
31
'nexpose_site_import' => 'Import data from the specified site ID',
32
'nexpose_report_templates' => 'List all available report templates',
33
'nexpose_command' => 'Execute a console command on the Nexpose instance',
34
'nexpose_sysinfo' => 'Display detailed system information about the Nexpose instance'
35
36
# @TODO:
37
# nexpose_stop_scan
38
}
39
end
40
41
def nexpose_verify_db
42
if !(framework.db && framework.db.usable && framework.db.active)
43
print_error('No database has been configured, please use db_connect first')
44
return false
45
end
46
47
true
48
end
49
50
def nexpose_verify
51
return false if !nexpose_verify_db
52
53
if !@nsc
54
print_error("No active Nexpose instance has been configured, please use 'nexpose_connect'")
55
return false
56
end
57
58
true
59
end
60
61
def cmd_nexpose_save(*args)
62
# if we are logged in, save session details to nexpose.yaml
63
if args[0] == '-h'
64
print_status('Usage: ')
65
print_status(' nexpose_save')
66
return
67
end
68
69
if args[0]
70
print_status('Usage: ')
71
print_status(' nexpose_save')
72
return
73
end
74
75
group = 'default'
76
77
if ((@user && !@user.empty?) && (@host && !@host.empty?) && (@port && !@port.empty? && (@port.to_i > 0)) && (@pass && !@pass.empty?))
78
config = { group.to_s => { 'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port, 'trust_cert' => @trust_cert } }
79
::File.open(Nexpose_yaml.to_s, 'wb') { |f| f.puts YAML.dump(config) }
80
print_good("#{Nexpose_yaml} created.")
81
else
82
print_error('Missing username/password/server/port - relogin and then try again.')
83
return
84
end
85
end
86
87
def cmd_nexpose_connect(*args)
88
return if !nexpose_verify_db
89
90
if !args[0] && ::File.readable?(Nexpose_yaml.to_s)
91
lconfig = YAML.load_file(Nexpose_yaml.to_s)
92
@user = lconfig['default']['username']
93
@pass = lconfig['default']['password']
94
@host = lconfig['default']['server']
95
@port = lconfig['default']['port']
96
@trust_cert = lconfig['default']['trust_cert']
97
unless @trust_cert
98
@sslv = 'ok' # TODO: Not super-thrilled about bypassing the SSL warning...
99
end
100
nexpose_login
101
return
102
end
103
104
if (args.empty? || args[0].empty? || (args[0] == '-h'))
105
nexpose_usage
106
return
107
end
108
109
@user = @pass = @host = @port = @sslv = @trust_cert = @trust_cert_file = nil
110
111
case args.length
112
when 1, 2
113
cred, _split, targ = args[0].rpartition('@')
114
@user, @pass = cred.split(':', 2)
115
targ ||= '127.0.0.1:3780'
116
@host, @port = targ.split(':', 2)
117
@port ||= '3780'
118
unless args.length == 1
119
@trust_cert_file = args[1]
120
if File.exist? @trust_cert_file
121
@trust_cert = File.read(@trust_cert_file)
122
else
123
@sslv = @trust_cert_file
124
end
125
end
126
when 4, 5
127
@user, @pass, @host, @port, @trust_cert = args
128
unless args.length == 4
129
@trust_cert_file = @trust_cert
130
if File.exist? @trust_cert_file
131
@trust_cert = File.read(@trust_cert_file)
132
else
133
@sslv = @trust_cert_file
134
end
135
end
136
else
137
nexpose_usage
138
return
139
end
140
nexpose_login
141
end
142
143
def nexpose_usage
144
print_status('Usage: ')
145
print_status(' nexpose_connect username:password@host[:port] <ssl-confirm || trusted_cert_file>')
146
print_status(' -OR- ')
147
print_status(' nexpose_connect username password host port <ssl-confirm || trusted_cert_file>')
148
end
149
150
def nexpose_login
151
if !((@user && !@user.empty?) && (@host && !@host.empty?) && (@port && !@port.empty? && (@port.to_i > 0)) && (@pass && !@pass.empty?))
152
nexpose_usage
153
return
154
end
155
156
if ((@host != 'localhost') && (@host != '127.0.0.1') && (@trust_cert.nil? && @sslv != 'ok'))
157
# consider removing this message and replacing with check on trust_store, and if trust_store is not found validate @host already has a truly trusted cert?
158
print_error('Warning: SSL connections are not verified in this release, it is possible for an attacker')
159
print_error(' with the ability to man-in-the-middle the Nexpose traffic to capture the Nexpose')
160
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
161
print_error(' as an additional parameter to this command.')
162
return
163
end
164
165
# Wrap this so a duplicate session does not prevent a new login
166
begin
167
cmd_nexpose_disconnect
168
rescue ::Interrupt
169
raise $ERROR_INFO
170
rescue ::Exception
171
end
172
173
begin
174
print_status("Connecting to Nexpose instance at #{@host}:#{@port} with username #{@user}...")
175
nsc = Nexpose::Connection.new(@host, @user, @pass, @port, nil, nil, @trust_cert)
176
nsc.login
177
rescue ::Nexpose::APIError => e
178
print_error("Connection failed: #{e.reason}")
179
return
180
end
181
182
@nsc = nsc
183
nexpose_compatibility_check
184
nsc
185
end
186
187
def cmd_nexpose_activity(*_args)
188
return if !nexpose_verify
189
190
scans = @nsc.scan_activity || []
191
case scans.length
192
when 0
193
print_status('There are currently no active scan jobs on this Nexpose instance')
194
when 1
195
print_status('There is 1 active scan job on this Nexpose instance')
196
else
197
print_status("There are currently #{scans.length} active scan jobs on this Nexpose instance")
198
end
199
200
scans.each do |scan|
201
print_status(" Scan ##{scan.scan_id} is running on Engine ##{scan.engine_id} against site ##{scan.site_id} since #{scan.start_time}")
202
end
203
end
204
205
def cmd_nexpose_sites(*_args)
206
return if !nexpose_verify
207
208
sites = @nsc.list_sites || []
209
case sites.length
210
when 0
211
print_status('There are currently no active sites on this Nexpose instance')
212
end
213
214
sites.each do |site|
215
print_status(" Site ##{site.id} '#{site.name}' Risk Factor: #{site.risk_factor} Risk Score: #{site.risk_score}")
216
end
217
end
218
219
def cmd_nexpose_site_devices(*args)
220
return if !nexpose_verify
221
222
site_id = args.shift
223
if !site_id
224
print_error('No site ID was specified')
225
return
226
end
227
228
devices = @nsc.list_site_devices(site_id) || []
229
case devices.length
230
when 0
231
print_status('There are currently no devices within this site')
232
end
233
234
devices.each do |device|
235
print_status(" Host: #{device.address} ID: #{device.id} Risk Factor: #{device.risk_factor} Risk Score: #{device.risk_score}")
236
end
237
end
238
239
def cmd_nexpose_report_templates(*_args)
240
return if !nexpose_verify
241
242
res = @nsc.list_report_templates || []
243
244
res.each do |report|
245
print_status(" Template: #{report.id} Name: '#{report.name}' Description: #{report.description}")
246
end
247
end
248
249
def cmd_nexpose_command(*args)
250
return if !nexpose_verify
251
252
if args.empty?
253
print_error('No command was specified')
254
return
255
end
256
257
res = @nsc.console_command(args.join(' ')) || ''
258
259
print_status('Command Output')
260
print_line(res)
261
print_line('')
262
end
263
264
def cmd_nexpose_sysinfo(*_args)
265
return if !nexpose_verify
266
267
res = @nsc.system_information
268
269
print_status('System Information')
270
res.each_pair do |k, v|
271
print_status(" #{k}: #{v}")
272
end
273
end
274
275
def nexpose_compatibility_check
276
res = @nsc.console_command('ver')
277
if res !~ /^(NSC|Console) Version ID:\s*4[89]0\s*$/m
278
print_error('')
279
print_error('Warning: This version of Nexpose has not been tested with Metasploit!')
280
print_error('')
281
end
282
end
283
284
def cmd_nexpose_site_import(*args)
285
site_id = args.shift
286
if !site_id
287
print_error('No site ID was specified')
288
return
289
end
290
291
msfid = Time.now.to_i
292
293
report_formats = ['raw-xml-v2', 'ns-xml']
294
report_format = report_formats.shift
295
296
report = Nexpose::ReportConfig.build(@nsc, site_id, "Metasploit Export #{msfid}", 'pentest-audit', report_format, true)
297
report.delivery = Nexpose::Delivery.new(true)
298
299
begin
300
report.format = report_format
301
report.save(@nsc)
302
rescue ::Exception => e
303
report_format = report_formats.shift
304
if report_format
305
retry
306
end
307
raise e
308
end
309
310
print_status('Generating the export data file...')
311
last_report = nil
312
until last_report
313
last_report = @nsc.last_report(report.id)
314
select(nil, nil, nil, 1.0)
315
end
316
url = last_report.uri
317
318
print_status('Downloading the export data...')
319
data = @nsc.download(url)
320
321
# Delete the temporary report ID
322
@nsc.delete_report_config(report.id)
323
324
print_status('Importing Nexpose data...')
325
process_nexpose_data(report_format, data)
326
end
327
328
def cmd_nexpose_discover(*args)
329
args << '-h' if args.empty?
330
args << '-t'
331
args << 'aggressive-discovery'
332
cmd_nexpose_scan(*args)
333
end
334
335
def cmd_nexpose_exhaustive(*args)
336
args << '-h' if args.empty?
337
args << '-t'
338
args << 'exhaustive-audit'
339
cmd_nexpose_scan(*args)
340
end
341
342
def cmd_nexpose_dos(*args)
343
args << '-h' if args.empty?
344
args << '-t'
345
args << 'dos-audit'
346
cmd_nexpose_scan(*args)
347
end
348
349
def cmd_nexpose_scan(*args)
350
opts = Rex::Parser::Arguments.new(
351
'-h' => [ false, 'This help menu'],
352
'-t' => [ true, 'The scan template to use (default:pentest-audit options:full-audit,exhaustive-audit,discovery,aggressive-discovery,dos-audit)'],
353
'-c' => [ true, 'Specify credentials to use against these targets (format is type:user:pass'],
354
'-n' => [ true, 'The maximum number of IPs to scan at a time (default is 32)'],
355
'-s' => [ true, 'The directory to store the raw XML files from the Nexpose instance (optional)'],
356
'-P' => [ false, 'Leave the scan data on the server when it completes (this counts against the maximum licensed IPs)'],
357
'-v' => [ false, 'Display diagnostic information about the scanning process'],
358
'-d' => [ false, 'Scan hosts based on the contents of the existing database'],
359
'-I' => [ true, 'Only scan systems with an address within the specified range'],
360
'-E' => [ true, 'Exclude hosts in the specified range from the scan']
361
)
362
363
opt_template = 'pentest-audit'
364
opt_maxaddrs = 32
365
opt_verbose = false
366
opt_savexml = nil
367
opt_preserve = false
368
opt_rescandb = false
369
opt_addrinc = nil
370
opt_addrexc = nil
371
opt_scanned = []
372
opt_credentials = []
373
374
opt_ranges = []
375
376
opts.parse(args) do |opt, _idx, val|
377
case opt
378
when '-h'
379
print_line('Usage: nexpose_scan [options] <Target IP Ranges>')
380
print_line(opts.usage)
381
return
382
when '-t'
383
opt_template = val
384
when '-n'
385
opt_maxaddrs = val.to_i
386
when '-s'
387
opt_savexml = val
388
when '-c'
389
if (val =~ /^([^:]+):([^:]+):(.+)/)
390
type = Regexp.last_match(1)
391
user = Regexp.last_match(2)
392
pass = Regexp.last_match(3)
393
msfid = Time.now.to_i
394
newcreds = Nexpose::SiteCredentials.for_service("Metasploit Site Credential #{msfid}", nil, nil, nil, nil, type)
395
newcreds.user_name = user
396
newcreds.password = pass
397
opt_credentials << newcreds
398
else
399
print_error("Unrecognized Nexpose scan credentials: #{val}")
400
return
401
end
402
when '-v'
403
opt_verbose = true
404
when '-P'
405
opt_preserve = true
406
when '-d'
407
opt_rescandb = true
408
when '-I'
409
opt_addrinc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val)
410
when '-E'
411
opt_addrexc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val)
412
else
413
opt_ranges << val
414
end
415
end
416
417
return if !nexpose_verify
418
419
# Include all database hosts as scan targets if specified
420
if opt_rescandb
421
print_status('Loading scan targets from the active database...') if opt_verbose
422
framework.db.hosts.each do |host|
423
next if host.state != ::Msf::HostState::Alive
424
425
opt_ranges << host.address
426
end
427
end
428
429
possible_files = opt_ranges # don't allow DOS by circular reference
430
possible_files.each do |file|
431
next unless ::File.readable? file
432
433
print_status "Parsing ranges from #{file}"
434
range_list = ::File.open(file, 'rb') { |f| f.read f.stat.size }
435
range_list.each_line { |subrange| opt_ranges << subrange }
436
opt_ranges.delete(file)
437
end
438
439
opt_ranges = opt_ranges.join(' ')
440
441
if opt_ranges.strip.empty?
442
print_line('Usage: nexpose_scan [options] <Target IP Ranges>')
443
print_line(opts.usage)
444
return
445
end
446
447
if opt_verbose
448
print_status("Creating a new scan using template #{opt_template} and #{opt_maxaddrs} concurrent IPs against #{opt_ranges}")
449
end
450
451
range_inp = ::Msf::OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(opt_ranges)
452
range = ::Rex::Socket::RangeWalker.new(range_inp)
453
include_range = opt_addrinc ? ::Rex::Socket::RangeWalker.new(opt_addrinc) : nil
454
exclude_range = opt_addrexc ? ::Rex::Socket::RangeWalker.new(opt_addrexc) : nil
455
456
completed = 0
457
total = range.num_ips
458
count = 0
459
460
print_status("Scanning #{total} addresses with template #{opt_template} in sets of #{opt_maxaddrs}")
461
462
while (completed < total)
463
count += 1
464
queue = []
465
466
while ((ip = range.next_ip) && (queue.length < opt_maxaddrs))
467
468
if (exclude_range && exclude_range.include?(ip))
469
print_status(" >> Skipping host #{ip} due to exclusion") if opt_verbose
470
next
471
end
472
473
if (include_range && !include_range.include?(ip))
474
print_status(" >> Skipping host #{ip} due to inclusion filter") if opt_verbose
475
next
476
end
477
478
opt_scanned << ip
479
queue << ip
480
end
481
482
break if queue.empty?
483
484
print_status("Scanning #{queue[0]}-#{queue[-1]}...") if opt_verbose
485
486
msfid = Time.now.to_i
487
488
# Create a temporary site
489
site = Nexpose::Site.new(nil, opt_template)
490
site.name = "Metasploit-#{msfid}"
491
site.description = 'Autocreated by the Metasploit Framework'
492
site.included_addresses = queue
493
site.site_credentials = opt_credentials
494
site.save(@nsc)
495
496
print_status(" >> Created temporary site ##{site.id}") if opt_verbose
497
498
report_formats = ['raw-xml-v2', 'ns-xml']
499
report_format = report_formats.shift
500
501
report = Nexpose::ReportConfig.build(@nsc, site.id, site.name, opt_template, report_format, true)
502
report.delivery = Nexpose::Delivery.new(true)
503
504
begin
505
report.format = report_format
506
report.save(@nsc, true)
507
rescue ::Exception => e
508
report_format = report_formats.shift
509
if report_format
510
retry
511
end
512
raise e
513
end
514
515
print_status(" >> Created temporary report configuration ##{report.id}") if opt_verbose
516
517
# Run the scan
518
begin
519
res = site.scan(@nsc)
520
rescue Nexpose::APIError => e
521
nexpose_error_message = e.message
522
nexpose_error_message.gsub!(/NexposeAPI: Action failed: /, '')
523
print_error nexpose_error_message.to_s
524
return
525
end
526
527
sid = res.id
528
529
print_status(" >> Scan has been launched with ID ##{sid}") if opt_verbose
530
531
rep = true
532
begin
533
prev = nil
534
while true
535
info = @nsc.scan_statistics(sid)
536
break if info.status != 'running'
537
538
stat = "Found #{info.nodes.live} devices and #{info.nodes.dead} unresponsive"
539
if (stat != prev) && opt_verbose
540
print_status(" >> #{stat}")
541
end
542
prev = stat
543
select(nil, nil, nil, 5.0)
544
end
545
print_status(" >> Scan has been completed with ID ##{sid}") if opt_verbose
546
rescue ::Interrupt
547
rep = false
548
print_status(" >> Terminating scan ID ##{sid} due to console interrupt") if opt_verbose
549
@nsc.stop_scan(sid)
550
break
551
end
552
553
# Wait for the automatic report generation to complete
554
if rep
555
print_status(' >> Waiting on the report to generate...') if opt_verbose
556
last_report = nil
557
until last_report
558
last_report = @nsc.last_report(report.id)
559
select(nil, nil, nil, 1.0)
560
end
561
url = last_report.uri
562
563
print_status(' >> Downloading the report data from Nexpose...') if opt_verbose
564
data = @nsc.download(url)
565
566
if opt_savexml
567
::FileUtils.mkdir_p(opt_savexml)
568
path = ::File.join(opt_savexml, "nexpose-#{msfid}-#{count}.xml")
569
print_status(" >> Saving scan data into #{path}") if opt_verbose
570
::File.open(path, 'wb') { |fd| fd.write(data) }
571
end
572
573
process_nexpose_data(report_format, data)
574
end
575
576
next if opt_preserve
577
578
# Make sure the scan has finished clean up before attempting to delete the site
579
loop do
580
info = @nsc.scan_statistics(sid)
581
break if info.status == 'stopped' || info.status == 'finished'
582
583
select(nil, nil, nil, 5.0)
584
end
585
print_status(' >> Deleting the temporary site and report...') if opt_verbose
586
begin
587
@nsc.delete_site(site.id)
588
rescue ::Nexpose::APIError => e
589
print_status(" >> Deletion of temporary site and report failed: #{e.inspect}")
590
end
591
end
592
593
print_status("Completed the scan of #{total} addresses")
594
end
595
596
def cmd_nexpose_disconnect(*_args)
597
@nsc.logout if @nsc
598
@nsc = nil
599
end
600
601
def process_nexpose_data(fmt, data)
602
case fmt
603
when 'raw-xml-v2'
604
framework.db.import({ data: data })
605
when 'ns-xml'
606
framework.db.import({ data: data })
607
else
608
print_error("Unsupported Nexpose data format: #{fmt}")
609
end
610
end
611
612
#
613
# Nexpose vuln lookup
614
#
615
def nexpose_vuln_lookup(doc, vid, refs, host, serv = nil)
616
doc.elements.each("/NexposeReport/VulnerabilityDefinitions/vulnerability[@id = '#{vid}']]") do |vulndef|
617
title = vulndef.attributes['title']
618
# pci_severity = vulndef.attributes['pciSeverity']
619
# cvss_score = vulndef.attributes['cvssScore']
620
# cvss_vector = vulndef.attributes['cvssVector']
621
622
vulndef.elements['references'].elements.each('reference') do |ref|
623
if ref.attributes['source'] == 'BID'
624
refs['BID-' + ref.text] = true
625
elsif ref.attributes['source'] == 'CVE'
626
# ref.text is CVE-$ID
627
refs[ref.text] = true
628
elsif ref.attributes['source'] == 'MS'
629
refs['MSB-MS-' + ref.text] = true
630
end
631
end
632
633
refs['NEXPOSE-' + vid.downcase] = true
634
635
vuln = framework.db.find_or_create_vuln(
636
host: host,
637
service: serv,
638
name: 'NEXPOSE-' + vid.downcase,
639
data: title
640
)
641
642
rids = []
643
refs.each_key do |r|
644
rids << framework.db.find_or_create_ref(name: r)
645
end
646
647
vuln.refs << (rids - vuln.refs)
648
end
649
end
650
651
end
652
653
#
654
# Plugin initialization
655
#
656
657
def initialize(framework, opts)
658
super
659
660
add_console_dispatcher(NexposeCommandDispatcher)
661
banner = ['0a205f5f5f5f202020202020202020202020205f20202020205f205f5f5f5f5f2020205f2020205f20202020205f5f20205f5f2020202020202020202020202020202020202020200a7c20205f205c205f5f205f205f205f5f20285f29205f5f7c207c5f5f5f20207c207c205c207c207c205f5f5f5c205c2f202f5f205f5f2020205f5f5f20205f5f5f20205f5f5f200a7c207c5f29202f205f60207c20275f205c7c207c2f205f60207c20202f202f20207c20205c7c207c2f205f205c5c20202f7c20275f205c202f205f205c2f205f5f7c2f205f205c0a7c20205f203c20285f7c207c207c5f29207c207c20285f7c207c202f202f2020207c207c5c20207c20205f5f2f2f20205c7c207c5f29207c20285f29205c5f5f205c20205f5f2f0a7c5f7c205c5f5c5f5f2c5f7c202e5f5f2f7c5f7c5c5f5f2c5f7c2f5f2f202020207c5f7c205c5f7c5c5f5f5f2f5f2f5c5f5c202e5f5f2f205c5f5f5f2f7c5f5f5f2f5c5f5f5f7c0a20202020202020202020207c5f7c20202020202020202020202020202020202020202020202020202020202020202020207c5f7c202020202020202020202020202020202020200a0a0a'].pack('H*')
662
663
# Do not use this UTF-8 encoded high-ascii art for non-UTF-8 or windows consoles
664
lang = Rex::Compat.getenv('LANG')
665
if (lang && lang =~ (/UTF-8/))
666
# Cygwin/Windows should not be reporting UTF-8 either...
667
# (! (Rex::Compat.is_windows or Rex::Compat.is_cygwin))
668
banner = ['202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29684e29684e29684202020e29684e29684202020202020202020202020e29684e29684e296842020e29684e29684e2968420202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29688e29688e29688202020e29688e2968820202020202020202020202020e29688e2968820e29684e29688e296882020202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29688e29688e29680e296882020e29688e29688202020e29684e29688e29688e29688e29688e296842020202020e29688e29688e29688e2968820202020e29688e29688e29684e29688e29688e29688e2968420202020e29684e29688e29688e29688e29688e29684202020e29684e29684e29688e29688e29688e29688e29688e29684202020e29684e29688e29688e29688e29688e2968420200a20e29688e2968820e29688e2968820e29688e296882020e29688e29688e29684e29684e29684e29684e29688e296882020202020e29688e296882020202020e29688e29688e296802020e29680e29688e296882020e29688e29688e296802020e29680e29688e296882020e29688e29688e29684e29684e29684e2968420e296802020e29688e29688e29684e29684e29684e29684e29688e29688200a20e29688e296882020e29688e29684e29688e296882020e29688e29688e29680e29680e29680e29680e29680e2968020202020e29688e29688e29688e2968820202020e29688e2968820202020e29688e296882020e29688e2968820202020e29688e29688202020e29680e29680e29680e29680e29688e29688e296842020e29688e29688e29680e29680e29680e29680e29680e29680200a20e29688e29688202020e29688e29688e296882020e29680e29688e29688e29684e29684e29684e29684e29688202020e29688e296882020e29688e29688202020e29688e29688e29688e29684e29684e29688e29688e296802020e29680e29688e29688e29684e29684e29688e29688e296802020e29688e29684e29684e29684e29684e29684e29688e296882020e29680e29688e29688e29684e29684e29684e29684e29688200a20e29680e29680202020e29680e29680e2968020202020e29680e29680e29680e29680e29680202020e29680e29680e296802020e29680e29680e296802020e29688e2968820e29680e29680e29680202020202020e29680e29680e29680e296802020202020e29680e29680e29680e29680e29680e296802020202020e29680e29680e29680e29680e2968020200a20202020202020202020202020202020202020202020202020202020202020e29688e29688202020202020202020202020202020202020202020202020202020202020202020202020200a202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a'].pack('H*')
669
end
670
print(banner)
671
print_status('Nexpose integration has been activated')
672
end
673
674
def cleanup
675
remove_console_dispatcher('Nexpose')
676
end
677
678
def name
679
'nexpose'
680
end
681
682
def desc
683
'Integrates with the Rapid7 Nexpose vulnerability management product'
684
end
685
end
686
end
687
688
module Nexpose
689
class IPRange
690
def to_json(*_args)
691
if @to.present?
692
"#{@from} - #{@to}".to_json
693
else
694
@from.to_json
695
end
696
end
697
end
698
end
699
700