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