nexpose 0.2.8 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/nexpose.rb +18 -9
- data/lib/nexpose/ajax.rb +127 -0
- data/lib/nexpose/alert.rb +29 -36
- data/lib/nexpose/common.rb +13 -12
- data/lib/nexpose/connection.rb +18 -13
- data/lib/nexpose/creds.rb +16 -55
- data/lib/nexpose/dag.rb +73 -0
- data/lib/nexpose/data_table.rb +134 -0
- data/lib/nexpose/device.rb +111 -0
- data/lib/nexpose/engine.rb +194 -0
- data/lib/nexpose/filter.rb +341 -0
- data/lib/nexpose/group.rb +33 -37
- data/lib/nexpose/manage.rb +4 -0
- data/lib/nexpose/pool.rb +142 -0
- data/lib/nexpose/report.rb +72 -278
- data/lib/nexpose/report_template.rb +249 -0
- data/lib/nexpose/scan.rb +196 -54
- data/lib/nexpose/scan_template.rb +103 -0
- data/lib/nexpose/site.rb +91 -237
- data/lib/nexpose/ticket.rb +173 -119
- data/lib/nexpose/user.rb +11 -3
- data/lib/nexpose/vuln.rb +81 -338
- data/lib/nexpose/vuln_exception.rb +368 -0
- metadata +12 -4
- data/lib/nexpose/misc.rb +0 -35
- data/lib/nexpose/scan_engine.rb +0 -325
@@ -0,0 +1,249 @@
|
|
1
|
+
module Nexpose
|
2
|
+
|
3
|
+
# NexposeAPI module is mixed into the Connection object, and all methods are
|
4
|
+
# expected to be called from there.
|
5
|
+
#
|
6
|
+
module NexposeAPI
|
7
|
+
include XMLUtils
|
8
|
+
|
9
|
+
# Provide a list of all report templates the user can access on the
|
10
|
+
# Security Console.
|
11
|
+
#
|
12
|
+
# @return [Array[ReportTemplateSummary]] List of current report templates.
|
13
|
+
#
|
14
|
+
def list_report_templates
|
15
|
+
r = execute(make_xml('ReportTemplateListingRequest', {}))
|
16
|
+
templates = []
|
17
|
+
if r.success
|
18
|
+
r.res.elements.each('//ReportTemplateSummary') do |template|
|
19
|
+
templates << ReportTemplateSummary.parse(template)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
templates
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :report_templates, :list_report_templates
|
26
|
+
|
27
|
+
# Deletes an existing, custom report template.
|
28
|
+
# Cannot delete built-in templates.
|
29
|
+
#
|
30
|
+
# @param [String] template_id Unique identifier of the report template to remove.
|
31
|
+
#
|
32
|
+
def delete_report_template(template_id)
|
33
|
+
AJAX.delete(self, "/data/report/templates/#{template_id}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Data object for report template summary information.
|
38
|
+
# Not meant for use in creating new templates.
|
39
|
+
#
|
40
|
+
class ReportTemplateSummary
|
41
|
+
|
42
|
+
# The ID of the report template.
|
43
|
+
attr_reader :id
|
44
|
+
# The name of the report template.
|
45
|
+
attr_reader :name
|
46
|
+
# One of: data|document. With a data template, you can export
|
47
|
+
# comma-separated value (CSV) files with vulnerability-based data.
|
48
|
+
# With a document template, you can create PDF, RTF, HTML, or XML reports
|
49
|
+
# with asset-based information.
|
50
|
+
attr_reader :type
|
51
|
+
# The visibility (scope) of the report template. One of: global|silo
|
52
|
+
attr_reader :scope
|
53
|
+
# Whether the report template is built-in, and therefore cannot be modified.
|
54
|
+
attr_reader :built_in
|
55
|
+
# Description of the report template.
|
56
|
+
attr_reader :description
|
57
|
+
|
58
|
+
def initialize(id, name, type, scope, built_in, description)
|
59
|
+
@id = id
|
60
|
+
@name = name
|
61
|
+
@type = type
|
62
|
+
@scope = scope
|
63
|
+
@built_in = built_in
|
64
|
+
@description = description
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete(connection)
|
68
|
+
connection.delete_report_template(@id)
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.parse(xml)
|
72
|
+
description = nil
|
73
|
+
xml.elements.each('description') { |desc| description = desc.text }
|
74
|
+
ReportTemplateSummary.new(xml.attributes['id'],
|
75
|
+
xml.attributes['name'],
|
76
|
+
xml.attributes['type'],
|
77
|
+
xml.attributes['scope'],
|
78
|
+
xml.attributes['builtin'] == '1',
|
79
|
+
description)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Definition object for a report template.
|
84
|
+
#
|
85
|
+
class ReportTemplate
|
86
|
+
|
87
|
+
# The ID of the report template.
|
88
|
+
attr_accessor :id
|
89
|
+
# The name of the report template.
|
90
|
+
attr_accessor :name
|
91
|
+
# With a data template, you can export comma-separated value (CSV) files
|
92
|
+
# with vulnerability-based data. With a document template, you can create
|
93
|
+
# PDF, RTF, HTML, or XML reports with asset-based information. When you
|
94
|
+
# retrieve a report template, the type will always be visible even though
|
95
|
+
# type is implied. When ReportTemplate is sent as a request, and the type
|
96
|
+
# attribute is not provided, the type attribute defaults to document,
|
97
|
+
# allowing for backward compatibility with existing API clients.
|
98
|
+
attr_accessor :type
|
99
|
+
# The visibility (scope) of the report template.
|
100
|
+
# One of: global|silo
|
101
|
+
attr_accessor :scope
|
102
|
+
# The report template is built-in, and cannot be modified.
|
103
|
+
attr_accessor :built_in
|
104
|
+
# Description of this report template.
|
105
|
+
attr_accessor :description
|
106
|
+
|
107
|
+
# Array of report sections.
|
108
|
+
attr_accessor :sections
|
109
|
+
# Map of report properties.
|
110
|
+
attr_accessor :properties
|
111
|
+
# Array of report attributes, in the order they will be present in a report.
|
112
|
+
attr_accessor :attributes
|
113
|
+
# Display asset names with IPs.
|
114
|
+
attr_accessor :show_device_names
|
115
|
+
|
116
|
+
def initialize(name, type = 'document', id = -1, scope = 'silo', built_in = false)
|
117
|
+
@name = name
|
118
|
+
@type = type
|
119
|
+
@id = id
|
120
|
+
@scope = scope
|
121
|
+
@built_in = built_in
|
122
|
+
|
123
|
+
@sections = []
|
124
|
+
@properties = {}
|
125
|
+
@attributes = []
|
126
|
+
@show_device_names = false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Save the configuration for a report template.
|
130
|
+
def save(connection)
|
131
|
+
xml = %(<ReportTemplateSaveRequest session-id='#{connection.session_id}' scope='#{@scope}'>)
|
132
|
+
xml << to_xml
|
133
|
+
xml << '</ReportTemplateSaveRequest>'
|
134
|
+
response = connection.execute(xml)
|
135
|
+
if response.success
|
136
|
+
@id = response.attributes['template-id']
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Retrieve the configuration for a report template.
|
141
|
+
def self.load(connection, template_id)
|
142
|
+
xml = %(<ReportTemplateConfigRequest session-id='#{connection.session_id}' template-id='#{template_id}'/>)
|
143
|
+
ReportTemplate.parse(connection.execute(xml))
|
144
|
+
end
|
145
|
+
|
146
|
+
def delete(connection)
|
147
|
+
connection.delete_report_template(@id)
|
148
|
+
end
|
149
|
+
|
150
|
+
include Sanitize
|
151
|
+
|
152
|
+
def to_xml
|
153
|
+
xml = %(<ReportTemplate id='#{@id}' name='#{@name}' type='#{@type}')
|
154
|
+
xml << %( scope='#{@scope}') if @scope
|
155
|
+
xml << %( builtin='#{@built_in}') if @built_in
|
156
|
+
xml << '>'
|
157
|
+
xml << %(<description>#{@description}</description>) if @description
|
158
|
+
|
159
|
+
unless @attributes.empty?
|
160
|
+
xml << '<ReportAttributes>'
|
161
|
+
@attributes.each do |attr|
|
162
|
+
xml << %(<ReportAttribute name='#{attr}'/>)
|
163
|
+
end
|
164
|
+
xml << '</ReportAttributes>'
|
165
|
+
end
|
166
|
+
|
167
|
+
unless @sections.empty?
|
168
|
+
xml << '<ReportSections>'
|
169
|
+
properties.each_pair do |name, value|
|
170
|
+
xml << %(<property name='#{name}'>#{replace_entities(value)}</property>)
|
171
|
+
end
|
172
|
+
@sections.each { |section| xml << section.to_xml }
|
173
|
+
xml << '</ReportSections>'
|
174
|
+
end
|
175
|
+
|
176
|
+
xml << %(<Settings><showDeviceNames enabled='#{@show_device_names ? 1 : 0}' /></Settings>)
|
177
|
+
xml << '</ReportTemplate>'
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.parse(xml)
|
181
|
+
xml.res.elements.each('//ReportTemplate') do |tmp|
|
182
|
+
template = ReportTemplate.new(tmp.attributes['name'],
|
183
|
+
tmp.attributes['type'],
|
184
|
+
tmp.attributes['id'],
|
185
|
+
tmp.attributes['scope'] || 'silo',
|
186
|
+
tmp.attributes['builtin'])
|
187
|
+
tmp.elements.each('//description') do |desc|
|
188
|
+
template.description = desc.text
|
189
|
+
end
|
190
|
+
|
191
|
+
tmp.elements.each('//ReportAttributes/ReportAttribute') do |attr|
|
192
|
+
template.attributes << attr.attributes['name']
|
193
|
+
end
|
194
|
+
|
195
|
+
tmp.elements.each('//ReportSections/property') do |property|
|
196
|
+
template.properties[property.attributes['name']] = property.text
|
197
|
+
end
|
198
|
+
|
199
|
+
tmp.elements.each('//ReportSection') do |section|
|
200
|
+
template.sections << Section.parse(section)
|
201
|
+
end
|
202
|
+
|
203
|
+
tmp.elements.each('//showDeviceNames') do |show|
|
204
|
+
template.show_device_names = show.attributes['enabled'] == '1'
|
205
|
+
end
|
206
|
+
|
207
|
+
return template
|
208
|
+
end
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Section specific content to include in a report template.
|
214
|
+
#
|
215
|
+
class Section
|
216
|
+
|
217
|
+
# Name of the report section.
|
218
|
+
attr_accessor :name
|
219
|
+
# Map of properties specific to the report section.
|
220
|
+
attr_accessor :properties
|
221
|
+
|
222
|
+
def initialize(name)
|
223
|
+
@name = name
|
224
|
+
@properties = {}
|
225
|
+
end
|
226
|
+
|
227
|
+
include Sanitize
|
228
|
+
|
229
|
+
def to_xml
|
230
|
+
xml = %(<ReportSection name='#{@name}'>)
|
231
|
+
properties.each_pair do |name, value|
|
232
|
+
xml << %(<property name='#{name}'>#{replace_entities(value)}</property>)
|
233
|
+
end
|
234
|
+
xml << '</ReportSection>'
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.parse(xml)
|
238
|
+
name = xml.attributes['name']
|
239
|
+
xml.elements.each("//ReportSection[@name='#{name}']") do |elem|
|
240
|
+
section = Section.new(name)
|
241
|
+
elem.elements.each("//ReportSection[@name='#{name}']/property") do |property|
|
242
|
+
section.properties[property.attributes['name']] = property.text
|
243
|
+
end
|
244
|
+
return section
|
245
|
+
end
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
data/lib/nexpose/scan.rb
CHANGED
@@ -2,12 +2,141 @@ module Nexpose
|
|
2
2
|
module NexposeAPI
|
3
3
|
include XMLUtils
|
4
4
|
|
5
|
+
# Perform an ad hoc scan of a single device.
|
6
|
+
#
|
7
|
+
# @param [Device] device Device to scan.
|
8
|
+
# @return [Scan] Scan launch information.
|
9
|
+
#
|
10
|
+
def scan_device(device)
|
11
|
+
scan_devices([device])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Perform an ad hoc scan of a subset of devices for a site.
|
15
|
+
# Nexpose only allows devices from a single site to be submitted per
|
16
|
+
# request.
|
17
|
+
# Method is designed to take objects from a Device listing.
|
18
|
+
#
|
19
|
+
# For example:
|
20
|
+
# devices = nsc.devices(5)
|
21
|
+
# nsc.scan_devices(devices.take(10))
|
22
|
+
#
|
23
|
+
# @param [Array[Device]] devices List of devices to scan.
|
24
|
+
# @return [Scan] Scan launch information.
|
25
|
+
#
|
26
|
+
def scan_devices(devices)
|
27
|
+
site_id = devices.map { |d| d.site_id }.uniq.first
|
28
|
+
xml = make_xml('SiteDevicesScanRequest', { 'site-id' => site_id })
|
29
|
+
elem = REXML::Element.new('Devices')
|
30
|
+
devices.each do |device|
|
31
|
+
elem.add_element('device', { 'id' => "#{device.id}" })
|
32
|
+
end
|
33
|
+
xml.add_element(elem)
|
34
|
+
|
35
|
+
_scan_ad_hoc(xml)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Perform an ad hoc scan of a single asset of a site.
|
39
|
+
#
|
40
|
+
# @param [Fixnum] site_id Site ID that the assets belong to.
|
41
|
+
# @param [HostName|IPRange] asset Asset to scan.
|
42
|
+
# @return [Scan] Scan launch information.
|
43
|
+
#
|
44
|
+
def scan_asset(site_id, asset)
|
45
|
+
scan_assets(site_id, [asset])
|
46
|
+
end
|
47
|
+
|
48
|
+
# Perform an ad hoc scan of a subset of assets for a site.
|
49
|
+
# Only assets from a single site should be submitted per request.
|
50
|
+
# Method is designed to take objects filtered from Site#assets.
|
51
|
+
#
|
52
|
+
# For example:
|
53
|
+
# site = Site.load(nsc, 5)
|
54
|
+
# nsc.scan_assets(5, site.assets.take(10))
|
55
|
+
#
|
56
|
+
# @param [Fixnum] site_id Site ID that the assets belong to.
|
57
|
+
# @param [Array[HostName|IPRange]] assets List of assets to scan.
|
58
|
+
# @return [Scan] Scan launch information.
|
59
|
+
#
|
60
|
+
def scan_assets(site_id, assets)
|
61
|
+
xml = make_xml('SiteDevicesScanRequest', { 'site-id' => site_id })
|
62
|
+
hosts = REXML::Element.new('Hosts')
|
63
|
+
assets.each { |asset| _append_asset!(hosts, asset) }
|
64
|
+
xml.add_element(hosts)
|
65
|
+
|
66
|
+
_scan_ad_hoc(xml)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Perform an ad hoc scan of a subset of IP addresses for a site.
|
70
|
+
# Only IPs from a single site can be submitted per request,
|
71
|
+
# and IP addresses must already be included in the site configuration.
|
72
|
+
# Method is designed for scanning when the targets are coming from an
|
73
|
+
# external source that does not have access to internal identfiers.
|
74
|
+
#
|
75
|
+
# For example:
|
76
|
+
# to_scan = ['192.168.2.1', '192.168.2.107']
|
77
|
+
# nsc.scan_ips(5, to_scan)
|
78
|
+
#
|
79
|
+
# @param [Fixnum] site_id Site ID that the assets belong to.
|
80
|
+
# @param [Array[String]] ip_addresses Array of IP addresses to scan.
|
81
|
+
# @return [Scan] Scan launch information.
|
82
|
+
#
|
83
|
+
def scan_ips(site_id, ip_addresses)
|
84
|
+
xml = make_xml('SiteDevicesScanRequest', { 'site-id' => site_id })
|
85
|
+
hosts = REXML::Element.new('Hosts')
|
86
|
+
ip_addresses.each do |ip|
|
87
|
+
xml.add_element('range', { 'from' => ip })
|
88
|
+
end
|
89
|
+
xml.add_element(hosts)
|
90
|
+
|
91
|
+
_scan_ad_hoc(xml)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Initiate a site scan.
|
95
|
+
#
|
96
|
+
# @param [Fixnum] site_id Site ID to scan.
|
97
|
+
# @return [Scan] Scan launch information.
|
98
|
+
#
|
99
|
+
def scan_site(site_id)
|
100
|
+
xml = make_xml('SiteScanRequest', { 'site-id' => site_id })
|
101
|
+
response = execute(xml)
|
102
|
+
Scan.parse(response.res) if response.success
|
103
|
+
end
|
104
|
+
|
105
|
+
# Utility method for appending a HostName or IPRange object into an
|
106
|
+
# XML object, in preparation for ad hoc scanning.
|
107
|
+
#
|
108
|
+
# @param [REXML::Document] xml Prepared API call to execute.
|
109
|
+
# @param [HostName|IPRange] asset Asset to append to XML.
|
110
|
+
#
|
111
|
+
def _append_asset!(xml, asset)
|
112
|
+
if asset.kind_of? Nexpose::IPRange
|
113
|
+
xml.add_element('range', { 'from' => asset.from, 'to' => asset.to })
|
114
|
+
else # Assume HostName
|
115
|
+
host = REXML::Element.new('host')
|
116
|
+
host.text = asset.host
|
117
|
+
xml.add_element(host)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Utility method for executing prepared XML and extracting Scan launch
|
122
|
+
# information.
|
123
|
+
#
|
124
|
+
# @param [REXML::Document] xml Prepared API call to execute.
|
125
|
+
# @return [Scan] Scan launch information.
|
126
|
+
#
|
127
|
+
def _scan_ad_hoc(xml)
|
128
|
+
r = execute(xml)
|
129
|
+
Scan.parse(r.res)
|
130
|
+
end
|
131
|
+
|
5
132
|
# Stop a running or paused scan.
|
6
133
|
#
|
7
134
|
# @param [Fixnum] scan_id ID of the scan to stop.
|
8
|
-
# @param [Fixnum] wait_sec Number of seconds to wait for status to be
|
9
|
-
|
10
|
-
|
135
|
+
# @param [Fixnum] wait_sec Number of seconds to wait for status to be
|
136
|
+
# updated.
|
137
|
+
#
|
138
|
+
def stop_scan(scan_id, wait_sec = 0)
|
139
|
+
r = execute(make_xml('ScanStopRequest', { 'scan-id' => scan_id }))
|
11
140
|
if r.success
|
12
141
|
so_far = 0
|
13
142
|
while so_far < wait_sec
|
@@ -20,36 +149,36 @@ module Nexpose
|
|
20
149
|
r.success
|
21
150
|
end
|
22
151
|
|
23
|
-
|
24
|
-
|
152
|
+
# Retrieve the status of a scan.
|
153
|
+
#
|
154
|
+
# @param [Fixnum] scan_id The scan ID.
|
155
|
+
# @return [String] Current status of the scan.
|
156
|
+
#
|
157
|
+
def scan_status(scan_id)
|
158
|
+
r = execute(make_xml('ScanStatusRequest', { 'scan-id' => scan_id }))
|
25
159
|
r.success ? r.attributes['status'] : nil
|
26
160
|
end
|
27
161
|
|
28
|
-
#----------------------------------------------------------------
|
29
162
|
# Resumes a scan.
|
30
163
|
#
|
31
|
-
# @param scan_id The scan ID.
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
r = execute(make_xml('ScanResumeRequest', {'scan-id' => scan_id}))
|
164
|
+
# @param [Fixnum] scan_id The scan ID.
|
165
|
+
#
|
166
|
+
def resume_scan(scan_id)
|
167
|
+
r = execute(make_xml('ScanResumeRequest', { 'scan-id' => scan_id }))
|
36
168
|
r.success ? r.attributes['success'] : nil
|
37
169
|
end
|
38
170
|
|
39
|
-
|
40
|
-
#----------------------------------------------------------------
|
41
171
|
# Pauses a scan.
|
42
172
|
#
|
43
|
-
# @param scan_id The scan ID.
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
r = execute(make_xml('ScanPauseRequest',{ 'scan-id' => scan_id}))
|
173
|
+
# @param [Fixnum] scan_id The scan ID.
|
174
|
+
#
|
175
|
+
def pause_scan(scan_id)
|
176
|
+
r = execute(make_xml('ScanPauseRequest', { 'scan-id' => scan_id }))
|
48
177
|
r.success ? r.attributes['success'] : nil
|
49
178
|
end
|
50
179
|
|
51
|
-
# Retrieve a list of current scan activities across all Scan Engines
|
52
|
-
# by Nexpose.
|
180
|
+
# Retrieve a list of current scan activities across all Scan Engines
|
181
|
+
# managed by Nexpose.
|
53
182
|
#
|
54
183
|
# @return [Array[ScanSummary]] Array of ScanSummary objects associated with
|
55
184
|
# each active scan on the engines.
|
@@ -67,10 +196,11 @@ module Nexpose
|
|
67
196
|
|
68
197
|
# Get scan statistics, including node and vulnerability breakdowns.
|
69
198
|
#
|
199
|
+
# @param [Fixnum] scan_id Scan ID to retrieve statistics for.
|
70
200
|
# @return [ScanSummary] ScanSummary object providing statistics for the scan.
|
71
201
|
#
|
72
202
|
def scan_statistics(scan_id)
|
73
|
-
r = execute(make_xml('ScanStatisticsRequest', {'scan-id' => scan_id}))
|
203
|
+
r = execute(make_xml('ScanStatisticsRequest', { 'scan-id' => scan_id }))
|
74
204
|
if r.success
|
75
205
|
ScanSummary.parse(r.res.elements['//ScanSummary'])
|
76
206
|
else
|
@@ -79,7 +209,6 @@ module Nexpose
|
|
79
209
|
end
|
80
210
|
end
|
81
211
|
|
82
|
-
# === Description
|
83
212
|
# Object that represents a summary of a scan.
|
84
213
|
#
|
85
214
|
class ScanSummary
|
@@ -129,28 +258,33 @@ module Nexpose
|
|
129
258
|
start_time = nil
|
130
259
|
unless xml.attributes['startTime'] == ''
|
131
260
|
start_time = DateTime.parse(xml.attributes['startTime'].to_s).to_time
|
261
|
+
# Timestamp is UTC, but parsed as local time.
|
262
|
+
start_time -= start_time.gmt_offset
|
132
263
|
end
|
133
264
|
|
134
265
|
# End time is often not present, since reporting on running scans.
|
135
266
|
end_time = nil
|
136
267
|
if xml.attributes['endTime']
|
137
268
|
end_time = DateTime.parse(xml.attributes['endTime'].to_s).to_time
|
269
|
+
# Timestamp is UTC, but parsed as local time.
|
270
|
+
end_time -= end_time.gmt_offset
|
138
271
|
end
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
272
|
+
ScanSummary.new(xml.attributes['scan-id'].to_i,
|
273
|
+
xml.attributes['site-id'].to_i,
|
274
|
+
xml.attributes['engine-id'].to_i,
|
275
|
+
xml.attributes['status'],
|
276
|
+
start_time,
|
277
|
+
end_time,
|
278
|
+
msg,
|
279
|
+
tasks,
|
280
|
+
nodes,
|
281
|
+
vulns)
|
149
282
|
end
|
150
283
|
|
151
284
|
# Value class to tracking task counts.
|
152
285
|
#
|
153
286
|
class Tasks
|
287
|
+
|
154
288
|
attr_reader :pending, :active, :completed
|
155
289
|
|
156
290
|
def initialize(pending, active, completed)
|
@@ -164,15 +298,16 @@ module Nexpose
|
|
164
298
|
#
|
165
299
|
def self.parse(rexml)
|
166
300
|
return nil unless rexml
|
167
|
-
|
168
|
-
|
169
|
-
|
301
|
+
Tasks.new(rexml.attributes['pending'].to_i,
|
302
|
+
rexml.attributes['active'].to_i,
|
303
|
+
rexml.attributes['completed'].to_i)
|
170
304
|
end
|
171
305
|
end
|
172
306
|
|
173
307
|
# Value class for tracking node counts.
|
174
308
|
#
|
175
309
|
class Nodes
|
310
|
+
|
176
311
|
attr_reader :live, :dead, :filtered, :unresolved, :other
|
177
312
|
|
178
313
|
def initialize(live, dead, filtered, unresolved, other)
|
@@ -186,20 +321,21 @@ module Nexpose
|
|
186
321
|
#
|
187
322
|
def self.parse(rexml)
|
188
323
|
return nil unless rexml
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
324
|
+
Nodes.new(rexml.attributes['live'].to_i,
|
325
|
+
rexml.attributes['dead'].to_i,
|
326
|
+
rexml.attributes['filtered'].to_i,
|
327
|
+
rexml.attributes['unresolved'].to_i,
|
328
|
+
rexml.attributes['other'].to_i)
|
194
329
|
end
|
195
330
|
end
|
196
331
|
|
197
332
|
# Value class for tracking vulnerability counts.
|
198
333
|
#
|
199
334
|
class Vulnerabilities
|
335
|
+
|
200
336
|
attr_reader :vuln_exploit, :vuln_version, :vuln_potential,
|
201
|
-
|
202
|
-
|
337
|
+
:not_vuln_exploit, :not_vuln_version,
|
338
|
+
:error, :disabled, :other
|
203
339
|
|
204
340
|
def initialize(vuln_exploit, vuln_version, vuln_potential,
|
205
341
|
not_vuln_exploit, not_vuln_version,
|
@@ -246,6 +382,7 @@ module Nexpose
|
|
246
382
|
# and vuln-potential.
|
247
383
|
#
|
248
384
|
class Status
|
385
|
+
|
249
386
|
attr_reader :severities, :count
|
250
387
|
|
251
388
|
def initialize(severity = nil, count = 0)
|
@@ -269,19 +406,24 @@ module Nexpose
|
|
269
406
|
end
|
270
407
|
end
|
271
408
|
|
272
|
-
#
|
273
|
-
# <scanFilter scanStop='0' scanFailed='0' scanStart='1'/>
|
274
|
-
# === Description
|
409
|
+
# Struct class for tracking scan launch information.
|
275
410
|
#
|
276
|
-
class
|
277
|
-
|
278
|
-
|
279
|
-
attr_reader :
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
@
|
411
|
+
class Scan
|
412
|
+
|
413
|
+
# The scan ID when a scan is successfully launched.
|
414
|
+
attr_reader :id
|
415
|
+
# The engine the scan was dispatched to.
|
416
|
+
attr_reader :engine
|
417
|
+
|
418
|
+
def initialize(scan_id, engine_id)
|
419
|
+
@id, @engine = scan_id, engine_id
|
420
|
+
end
|
421
|
+
|
422
|
+
def self.parse(xml)
|
423
|
+
xml.elements.each('//Scan') do |scan|
|
424
|
+
return new(scan.attributes['scan-id'].to_i,
|
425
|
+
scan.attributes['engine-id'].to_i)
|
426
|
+
end
|
285
427
|
end
|
286
428
|
end
|
287
429
|
end
|