nexpose 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/nexpose/scan.rb +30 -37
- data/lib/nexpose/scan_engine.rb +27 -59
- data/lib/nexpose/site.rb +81 -110
- metadata +2 -2
data/lib/nexpose/scan.rb
CHANGED
@@ -120,31 +120,27 @@ module Nexpose
|
|
120
120
|
# @return [ScanSummary] Scan summary represented by the XML.
|
121
121
|
#
|
122
122
|
def self.parse(rexml)
|
123
|
-
tasks = Tasks.parse(rexml)
|
124
|
-
nodes = Nodes.parse(rexml)
|
125
|
-
vulns = Vulnerabilities.parse(rexml)
|
126
|
-
msg = nil
|
127
|
-
rexml.elements.each("//ScanSummary/message") do |message|
|
128
|
-
msg = message.text
|
129
|
-
end
|
123
|
+
tasks = Tasks.parse(rexml.elements['tasks'])
|
124
|
+
nodes = Nodes.parse(rexml.elements['nodes'])
|
125
|
+
vulns = Vulnerabilities.parse(rexml.attributes['scan-id'], rexml)
|
126
|
+
msg = rexml.elements['message'] ? rexml.elements['message'].text : nil
|
130
127
|
|
131
|
-
rexml.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
137
|
-
return ScanSummary.new(scan.attributes['scan-id'].to_i,
|
138
|
-
scan.attributes['site-id'].to_i,
|
139
|
-
scan.attributes['engine-id'].to_i,
|
140
|
-
scan.attributes['status'],
|
141
|
-
start_time,
|
142
|
-
end_time,
|
143
|
-
msg,
|
144
|
-
tasks,
|
145
|
-
nodes,
|
146
|
-
vulns)
|
128
|
+
start_time = DateTime.parse(rexml.attributes['startTime'].to_s).to_time
|
129
|
+
# End time is often not present, since reporting on running scans.
|
130
|
+
end_time = nil
|
131
|
+
if rexml.attributes['endTime']
|
132
|
+
end_time = DateTime.parse(rexml.attributes['endTime'].to_s).to_time
|
147
133
|
end
|
134
|
+
return ScanSummary.new(rexml.attributes['scan-id'].to_i,
|
135
|
+
rexml.attributes['site-id'].to_i,
|
136
|
+
rexml.attributes['engine-id'].to_i,
|
137
|
+
rexml.attributes['status'],
|
138
|
+
start_time,
|
139
|
+
end_time,
|
140
|
+
msg,
|
141
|
+
tasks,
|
142
|
+
nodes,
|
143
|
+
vulns)
|
148
144
|
end
|
149
145
|
|
150
146
|
# Value class to tracking task counts.
|
@@ -162,11 +158,9 @@ module Nexpose
|
|
162
158
|
# @return [Tasks] Task summary represented by the XML.
|
163
159
|
#
|
164
160
|
def self.parse(rexml)
|
165
|
-
rexml.
|
166
|
-
|
167
|
-
|
168
|
-
task.attributes['completed'])
|
169
|
-
end
|
161
|
+
return Tasks.new(rexml.attributes['pending'].to_i,
|
162
|
+
rexml.attributes['active'].to_i,
|
163
|
+
rexml.attributes['completed'].to_i)
|
170
164
|
end
|
171
165
|
end
|
172
166
|
|
@@ -185,13 +179,11 @@ module Nexpose
|
|
185
179
|
# @return [Nodes] Node summary represented by the XML.
|
186
180
|
#
|
187
181
|
def self.parse(rexml)
|
188
|
-
rexml.
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
node.attributes['other'])
|
194
|
-
end
|
182
|
+
return Nodes.new(rexml.attributes['live'].to_i,
|
183
|
+
rexml.attributes['dead'].to_i,
|
184
|
+
rexml.attributes['filtered'].to_i,
|
185
|
+
rexml.attributes['unresolved'].to_i,
|
186
|
+
rexml.attributes['other'].to_i)
|
195
187
|
end
|
196
188
|
end
|
197
189
|
|
@@ -215,12 +207,13 @@ module Nexpose
|
|
215
207
|
|
216
208
|
# Parse REXML to Vulnerabilities object.
|
217
209
|
#
|
210
|
+
# @param [FixNum] scan_id Scan ID to collect vulnerability data for.
|
218
211
|
# @param [REXML::Document] rexml XML document to parse.
|
219
212
|
# @return [Vulnerabilities] Vulnerability summary represented by the XML.
|
220
213
|
#
|
221
|
-
def self.parse(rexml)
|
214
|
+
def self.parse(scan_id, rexml)
|
222
215
|
map = {}
|
223
|
-
rexml.elements.each("//ScanSummary/vulnerabilities") do |vuln|
|
216
|
+
rexml.elements.each("//ScanSummary[contains(@scan-id,'#{scan_id}')]/vulnerabilities") do |vuln|
|
224
217
|
status = map[vuln.attributes['status']]
|
225
218
|
if status && vuln.attributes['status'] =~ /^vuln-/
|
226
219
|
status.add_severity(vuln.attributes['severity'].to_i, vuln.attributes['count'].to_i)
|
data/lib/nexpose/scan_engine.rb
CHANGED
@@ -24,76 +24,48 @@ module Nexpose
|
|
24
24
|
end
|
25
25
|
arr
|
26
26
|
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# ==== Description
|
30
|
-
# Object that represents a listing of all of the scan engines available on to an NSC.
|
31
|
-
class EngineListing
|
32
|
-
# true if an error condition exists; false otherwise
|
33
|
-
attr_reader :error
|
34
|
-
# Error message string
|
35
|
-
attr_reader :error_msg
|
36
|
-
# The last XML request sent by this object
|
37
|
-
attr_reader :connection
|
38
|
-
# Array containing (EngineSummary*)
|
39
|
-
attr_reader :engines
|
40
|
-
# The number of scan engines
|
41
|
-
attr_reader :engine_count
|
42
|
-
|
43
|
-
# Constructor
|
44
|
-
# EngineListing (connection)
|
45
|
-
def initialize(connection)
|
46
|
-
@connection = connection
|
47
|
-
@engines = []
|
48
|
-
@engine_count = 0
|
49
|
-
@error = false
|
50
|
-
r = @connection.execute('<EngineListingRequest session-id="' + @connection.session_id + '"/>', '1.2')
|
51
27
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
28
|
+
# Retrieve a list of all Scan Engines managed by the Security Console.
|
29
|
+
#
|
30
|
+
# @return [Array[EngineSummary]] Array of EngineSummary objects associated with
|
31
|
+
# each engine associated with this security console.
|
32
|
+
#
|
33
|
+
def list_engines
|
34
|
+
response = execute(make_xml('EngineListingRequest'))
|
35
|
+
arr = []
|
36
|
+
if response.success
|
37
|
+
response.res.elements.each("//EngineSummary") do |engine|
|
38
|
+
arr << EngineSummary.new(engine.attributes['id'].to_i,
|
39
|
+
engine.attributes['name'],
|
40
|
+
engine.attributes['address'],
|
41
|
+
engine.attributes['port'].to_i,
|
42
|
+
engine.attributes['status'])
|
56
43
|
end
|
57
|
-
else
|
58
|
-
@error = true
|
59
|
-
@error_msg = 'EngineListingRequest Parse Error'
|
60
44
|
end
|
61
|
-
|
45
|
+
arr
|
62
46
|
end
|
47
|
+
|
48
|
+
alias_method :engines, :list_engines
|
63
49
|
end
|
64
50
|
|
65
|
-
#
|
66
|
-
# Object that represents the summary of a scan engine.
|
67
|
-
#
|
68
|
-
# ==== Examples
|
69
|
-
#
|
70
|
-
# # Create a new Nexpose Connection on the default port and Login
|
71
|
-
# nsc = Connection.new("10.1.40.10","nxadmin","password")
|
72
|
-
# nsc.login()
|
73
|
-
#
|
74
|
-
# # Get the engine listing for the connection
|
75
|
-
# enginelisting = EngineListing.new(nsc)
|
76
|
-
#
|
77
|
-
# # Print out the status of the first scan engine
|
78
|
-
# puts enginelisting.engines[0].status
|
51
|
+
# Object representing the current details of a scan engine attached to the security console.
|
79
52
|
#
|
80
53
|
class EngineSummary
|
81
|
-
|
54
|
+
|
55
|
+
# A unique ID that identifies this scan engine.
|
82
56
|
attr_reader :id
|
83
|
-
# The name of this scan engine
|
57
|
+
# The name of this scan engine.
|
84
58
|
attr_reader :name
|
85
|
-
# The hostname or IP address of the engine
|
59
|
+
# The hostname or IP address of the engine.
|
86
60
|
attr_reader :address
|
87
|
-
# The port there the engine is listening
|
61
|
+
# The port there the engine is listening.
|
88
62
|
attr_reader :port
|
89
|
-
# The engine status
|
63
|
+
# The engine status. One of: active|pending-auth|incompatible|not-responding|unknown
|
90
64
|
attr_reader :status
|
91
65
|
# A parameter that specifies whether the engine has a global
|
92
66
|
# or silo-specific scope.
|
93
67
|
attr_reader :scope
|
94
68
|
|
95
|
-
# Constructor
|
96
|
-
# EngineSummary(id, name, address, port, status, scope)
|
97
69
|
def initialize(id, name, address, port, status, scope = 'silo')
|
98
70
|
@id = id
|
99
71
|
@name = name
|
@@ -102,10 +74,6 @@ module Nexpose
|
|
102
74
|
@status = status
|
103
75
|
@scope = scope
|
104
76
|
end
|
105
|
-
|
106
|
-
def to_s
|
107
|
-
"Engine: #@name [ID: #@id] #@address:#@port, Status: #@status, Scope: #@scope"
|
108
|
-
end
|
109
77
|
end
|
110
78
|
|
111
79
|
#-------------------------------------------------------------------------------------------------------------------
|
@@ -303,10 +271,10 @@ module Nexpose
|
|
303
271
|
@scope = pool.attributes['scope']
|
304
272
|
@engines = []
|
305
273
|
r.res.elements.each('EnginePoolDetailsResponse/EnginePool/EngineSummary') do |summary|
|
306
|
-
@engines.push(EngineSummary.new(summary.attributes['id'],
|
274
|
+
@engines.push(EngineSummary.new(summary.attributes['id'].to_i,
|
307
275
|
summary.attributes['name'],
|
308
276
|
summary.attributes['address'],
|
309
|
-
summary.attributes['port'],
|
277
|
+
summary.attributes['port'].to_i,
|
310
278
|
summary.attributes['status'],
|
311
279
|
summary.attributes['scope']))
|
312
280
|
end
|
data/lib/nexpose/site.rb
CHANGED
@@ -2,60 +2,69 @@ module Nexpose
|
|
2
2
|
module NexposeAPI
|
3
3
|
include XMLUtils
|
4
4
|
|
5
|
+
# Retrieve a list of all of the assets in a site.
|
5
6
|
#
|
7
|
+
# If no site-id is specified, then return all of the assets
|
8
|
+
# for the Nexpose console, grouped by site-id.
|
6
9
|
#
|
10
|
+
# @param [FixNum] site_id Site ID to request device listing for. Optional.
|
11
|
+
# @return [Array[Device]] Array of devices associated with the site, or
|
12
|
+
# all devices on the console if no site is provided.
|
7
13
|
#
|
8
|
-
def site_device_listing(site_id)
|
9
|
-
r = execute(make_xml('SiteDeviceListingRequest', {'site-id' => site_id
|
14
|
+
def site_device_listing(site_id = nil)
|
15
|
+
r = execute(make_xml('SiteDeviceListingRequest', {'site-id' => site_id}))
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
r.res.elements.each(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
arr = []
|
18
|
+
if r.success
|
19
|
+
r.res.elements.each('//SiteDevices') do |site|
|
20
|
+
site_id = site.attributes['site-id'].to_i
|
21
|
+
site.elements.each("//SiteDevices[contains(@site-id,'#{site_id}')]/device") do |device|
|
22
|
+
arr << Device.new(device.attributes['id'].to_i,
|
23
|
+
device.attributes['address'],
|
24
|
+
site_id,
|
25
|
+
device.attributes['riskfactor'].to_f,
|
26
|
+
device.attributes['riskscore'].to_f)
|
27
|
+
end
|
22
28
|
end
|
23
|
-
res
|
24
|
-
else
|
25
|
-
false
|
26
29
|
end
|
30
|
+
arr
|
27
31
|
end
|
28
32
|
|
33
|
+
alias_method :assets, :site_device_listing
|
34
|
+
alias_method :devices, :site_device_listing
|
35
|
+
alias_method :list_devices, :site_device_listing
|
36
|
+
|
37
|
+
# Delete the specified site and all associated scan data.
|
29
38
|
#
|
30
|
-
#
|
39
|
+
# @return Whether or not the delete request succeeded.
|
31
40
|
#
|
32
41
|
def site_delete(param)
|
33
42
|
r = execute(make_xml('SiteDeleteRequest', {'site-id' => param}))
|
34
43
|
r.success
|
35
44
|
end
|
36
45
|
|
46
|
+
# Retrieve a list of all sites the user is authorized to view or manage.
|
37
47
|
#
|
38
|
-
#
|
39
|
-
#
|
48
|
+
# @return [Array[SiteSummary]] Array of SiteSummary objects.
|
49
|
+
#
|
40
50
|
def site_listing
|
41
|
-
r = execute(make_xml('SiteListingRequest'
|
42
|
-
|
51
|
+
r = execute(make_xml('SiteListingRequest'))
|
52
|
+
arr = []
|
43
53
|
if (r.success)
|
44
|
-
res = []
|
45
54
|
r.res.elements.each("//SiteSummary") do |site|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
55
|
+
arr << SiteSummary.new(site.attributes['id'].to_i,
|
56
|
+
site.attributes['name'],
|
57
|
+
site.attributes['description'],
|
58
|
+
site.attributes['riskfactor'].to_f,
|
59
|
+
site.attributes['riskscore'].to_f)
|
52
60
|
end
|
53
|
-
res
|
54
|
-
else
|
55
|
-
false
|
56
61
|
end
|
62
|
+
arr
|
57
63
|
end
|
58
64
|
|
65
|
+
alias_method :list_sites, :site_listing
|
66
|
+
alias_method :sites, :site_listing
|
67
|
+
|
59
68
|
# Retrieve a list of all previous scans of the site.
|
60
69
|
#
|
61
70
|
# @param [FixNum] site_id Site ID to request scan history for.
|
@@ -73,6 +82,18 @@ module Nexpose
|
|
73
82
|
res
|
74
83
|
end
|
75
84
|
|
85
|
+
# Retrieve the scan summary statistics for the latest completed scan
|
86
|
+
# on a site.
|
87
|
+
#
|
88
|
+
# Method will not return data on an active scan.
|
89
|
+
#
|
90
|
+
# @param [FixNum] site_id Site ID to find latest scan for.
|
91
|
+
#
|
92
|
+
def last_scan(site_id)
|
93
|
+
site_scan_history(site_id).select { |scan| scan.end_time }
|
94
|
+
.max_by { |scan| scan.end_time }
|
95
|
+
end
|
96
|
+
|
76
97
|
#-----------------------------------------------------------------------
|
77
98
|
# Starts device specific site scanning.
|
78
99
|
#
|
@@ -436,8 +457,8 @@ module Nexpose
|
|
436
457
|
r.elements.each('SiteListingResponse/SiteSummary') do |s|
|
437
458
|
site_summary = SiteSummary.new(
|
438
459
|
s.attributes['id'].to_s,
|
439
|
-
s.attributes['name']
|
440
|
-
s.attributes['description']
|
460
|
+
s.attributes['name'],
|
461
|
+
s.attributes['description'],
|
441
462
|
s.attributes['riskfactor'].to_s
|
442
463
|
)
|
443
464
|
@sites.push(site_summary)
|
@@ -450,101 +471,51 @@ module Nexpose
|
|
450
471
|
# Object that represents the summary of a Nexpose Site.
|
451
472
|
#
|
452
473
|
class SiteSummary
|
453
|
-
|
474
|
+
|
475
|
+
# The Site ID.
|
454
476
|
attr_reader :id
|
455
|
-
# The Site Name
|
456
|
-
attr_reader :
|
457
|
-
# A Description of the Site
|
477
|
+
# The Site Name.
|
478
|
+
attr_reader :name
|
479
|
+
# A Description of the Site.
|
458
480
|
attr_reader :description
|
459
|
-
# User assigned risk multiplier
|
460
|
-
attr_reader :
|
481
|
+
# User assigned risk multiplier.
|
482
|
+
attr_reader :risk_factor
|
483
|
+
# Current computed risk score for the site.
|
484
|
+
attr_reader :risk_factor
|
461
485
|
|
462
486
|
# Constructor
|
463
|
-
# SiteSummary(id,
|
464
|
-
def initialize(id,
|
487
|
+
# SiteSummary(id, name, description, riskfactor = 1)
|
488
|
+
def initialize(id, name, description, risk_factor = 1.0, risk_score = 0.0)
|
465
489
|
@id = id
|
466
|
-
@
|
490
|
+
@name = name
|
467
491
|
@description = description
|
468
|
-
@
|
469
|
-
|
470
|
-
|
471
|
-
def _set_id(id)
|
472
|
-
@id = id
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
# === Description
|
477
|
-
# Object that represents a listing of devices for a site or the entire NSC. Note that only devices which are accessible to the account used to create the connection object will be returned. This object is created and populated automatically with the instantiation of a new Site object.
|
478
|
-
#
|
479
|
-
class SiteDeviceListing
|
480
|
-
|
481
|
-
# true if an error condition exists; false otherwise
|
482
|
-
attr_reader :error
|
483
|
-
# Error message string
|
484
|
-
attr_reader :error_msg
|
485
|
-
# The last XML request sent by this object
|
486
|
-
attr_reader :request_xml
|
487
|
-
# The last XML response received by this object
|
488
|
-
attr_reader :response_xml
|
489
|
-
# The NSC Connection associated with this object
|
490
|
-
attr_reader :connection
|
491
|
-
# The Site ID. 0 if all sites are specified.
|
492
|
-
attr_reader :site_id
|
493
|
-
# //Array of (Device)*
|
494
|
-
attr_reader :devices
|
495
|
-
|
496
|
-
def initialize(connection, site_id = 0)
|
497
|
-
|
498
|
-
@site_id = site_id
|
499
|
-
@error = false
|
500
|
-
@connection = connection
|
501
|
-
@devices = []
|
502
|
-
|
503
|
-
r = nil
|
504
|
-
if (@site_id)
|
505
|
-
r = @connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '" site-id="' + "#{@site_id}" + '"/>')
|
506
|
-
if r.success
|
507
|
-
r.res.elements.each('SiteDeviceListingResponse/SiteDevices/device') do |d|
|
508
|
-
@devices.push(Device.new(d.attributes['id'], @site_id, d.attributes["address"], d.attributes["riskfactor"], d.attributes["riskscore"]))
|
509
|
-
end
|
510
|
-
end
|
511
|
-
else
|
512
|
-
r = @connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '"/>')
|
513
|
-
if r.success
|
514
|
-
r.res.elements.each('SiteDeviceListingResponse/SiteDevices') do |rr|
|
515
|
-
@sid = rr.attribute("site-id")
|
516
|
-
rr.elements.each('device') do |d|
|
517
|
-
@devices.push(Device.new(d.attributes['id'], @sid, d.attributes["address"], d.attributes['riskfactor'], d.attributes['riskscore']))
|
518
|
-
end
|
519
|
-
end
|
520
|
-
end
|
521
|
-
end
|
492
|
+
@risk_factor = risk_factor
|
493
|
+
@risk_score = risk_score
|
522
494
|
end
|
523
495
|
end
|
524
496
|
|
525
497
|
# === Description
|
526
|
-
# Object that represents a single device in
|
498
|
+
# Object that represents a single device in a Nexpose security console.
|
527
499
|
#
|
528
500
|
class Device
|
529
501
|
|
530
|
-
# A unique device ID (assigned by the
|
502
|
+
# A unique device ID (assigned automatically by the Nexpose console).
|
531
503
|
attr_reader :id
|
532
|
-
#
|
533
|
-
attr_reader :site_id
|
534
|
-
# IP Address or Hostname of this device
|
504
|
+
# IP Address or Hostname of this device.
|
535
505
|
attr_reader :address
|
536
|
-
# User assigned risk multiplier
|
537
|
-
attr_reader :
|
538
|
-
# Nexpose risk score
|
539
|
-
attr_reader :
|
506
|
+
# User assigned risk multiplier.
|
507
|
+
attr_reader :risk_factor
|
508
|
+
# Nexpose risk score.
|
509
|
+
attr_reader :risk_score
|
510
|
+
# Site ID that this device is associated with.
|
511
|
+
attr_reader :site_id
|
540
512
|
|
541
|
-
def initialize(id,
|
513
|
+
def initialize(id, address, site_id, risk_factor = 1.0, risk_score = 0.0)
|
542
514
|
@id = id
|
543
|
-
@site_id = site_id
|
544
515
|
@address = address
|
545
|
-
@
|
546
|
-
@
|
547
|
-
|
516
|
+
@site_id = site_id
|
517
|
+
@risk_factor = risk_factor
|
518
|
+
@risk_score = risk_score
|
548
519
|
end
|
549
520
|
end
|
550
521
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexpose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-02-
|
13
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: librex
|