nexpose 0.2.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/lib/nexpose/ticket.rb
CHANGED
@@ -1,46 +1,22 @@
|
|
1
1
|
module Nexpose
|
2
2
|
|
3
|
-
module Ticket
|
4
|
-
|
5
|
-
module State
|
6
|
-
OPEN = 'O'
|
7
|
-
ASSIGNED = 'A'
|
8
|
-
MODIFIED = 'M'
|
9
|
-
FIXED = 'X'
|
10
|
-
PARTIAL = 'P'
|
11
|
-
REJECTED_FIX = 'R'
|
12
|
-
PRIORITIZED = 'Z'
|
13
|
-
NOT_REPRODUCIBLE = 'F'
|
14
|
-
NOT_ISSUE = 'I'
|
15
|
-
CLOSED = 'C'
|
16
|
-
UNKNOWN = 'U'
|
17
|
-
end
|
18
|
-
|
19
|
-
module Priority
|
20
|
-
LOW = 'low'
|
21
|
-
MODERATE = 'moderate'
|
22
|
-
NORMAL = 'normal'
|
23
|
-
HIGH = 'high'
|
24
|
-
CRITICAL = 'critical'
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
3
|
module NexposeAPI
|
29
4
|
include XMLUtils
|
30
5
|
|
31
|
-
def
|
6
|
+
def list_tickets
|
7
|
+
# TODO: Should take in filters as arguments.
|
32
8
|
xml = make_xml('TicketListingRequest')
|
33
9
|
r = execute(xml, '1.2')
|
34
10
|
tickets = []
|
35
11
|
if r.success
|
36
12
|
r.res.elements.each('TicketListingResponse/TicketSummary') do |summary|
|
37
|
-
tickets << TicketSummary
|
13
|
+
tickets << TicketSummary.parse(summary)
|
38
14
|
end
|
39
15
|
end
|
40
16
|
tickets
|
41
17
|
end
|
42
18
|
|
43
|
-
alias_method :tickets, :
|
19
|
+
alias_method :tickets, :list_tickets
|
44
20
|
|
45
21
|
# Deletes a Nexpose ticket.
|
46
22
|
#
|
@@ -48,6 +24,7 @@ module Nexpose
|
|
48
24
|
# @return [Boolean] Whether or not the ticket deletion succeeded.
|
49
25
|
#
|
50
26
|
def delete_ticket(ticket)
|
27
|
+
# TODO: Take Ticket object, too, and pull out IDs.
|
51
28
|
delete_tickets([ticket])
|
52
29
|
end
|
53
30
|
|
@@ -57,98 +34,14 @@ module Nexpose
|
|
57
34
|
# @return [Boolean] Whether or not the ticket deletions succeeded.
|
58
35
|
#
|
59
36
|
def delete_tickets(tickets)
|
37
|
+
# TODO: Take Ticket objects, too, and pull out IDs.
|
60
38
|
xml = make_xml('TicketDeleteRequest')
|
61
39
|
tickets.each do |id|
|
62
|
-
xml.add_element('Ticket', {'id' => id})
|
40
|
+
xml.add_element('Ticket', { 'id' => id })
|
63
41
|
end
|
64
42
|
|
65
43
|
(execute xml, '1.2').success
|
66
44
|
end
|
67
|
-
|
68
|
-
alias_method :ticket_delete, :delete_tickets
|
69
|
-
|
70
|
-
#
|
71
|
-
# Create a Nexpose ticket
|
72
|
-
#
|
73
|
-
# ticket_info: A hash of the data to be used to create a ticket in Nexpose:
|
74
|
-
# :name => The name of the ticket (Required)
|
75
|
-
# :device_id => The Nexpose device ID for the device being ticketed (Required)
|
76
|
-
# :assigned_to => The Nexpose user to whom this ticket is assigned (Required)
|
77
|
-
# :priority => "low,moderate,normal,high,critical" (Required)
|
78
|
-
#
|
79
|
-
# :vulnerabilities => An array of Nexpose vuln IDs. This is NOT the same as vuln ID. (Required)
|
80
|
-
# :comments => An array of comments to accompany this ticket
|
81
|
-
#
|
82
|
-
# @return The ticket ID if the ticket creation was successful, {@code false} otherwise
|
83
|
-
#
|
84
|
-
def create_ticket(ticket_info)
|
85
|
-
ticket_name = ticket_info[:name]
|
86
|
-
unless ticket_name
|
87
|
-
raise ArgumentError.new 'Ticket name is required'
|
88
|
-
end
|
89
|
-
|
90
|
-
device_id = ticket_info[:device_id]
|
91
|
-
unless device_id
|
92
|
-
raise ArgumentError.new 'Device ID is required'
|
93
|
-
end
|
94
|
-
|
95
|
-
assigned_to = ticket_info[:assigned_to]
|
96
|
-
unless assigned_to
|
97
|
-
raise ArgumentError.new 'Assignee name is required'
|
98
|
-
end
|
99
|
-
|
100
|
-
priority = ticket_info[:priority]
|
101
|
-
unless priority
|
102
|
-
raise ArgumentError.new 'Ticket priority is required'
|
103
|
-
end
|
104
|
-
|
105
|
-
vulnerabilities = ticket_info[:vulnerabilities]
|
106
|
-
if not vulnerabilities or vulnerabilities.count < 1
|
107
|
-
raise ArgumentError.new 'Vulnerabilities are required'
|
108
|
-
end
|
109
|
-
|
110
|
-
comments = ticket_info[:comments]
|
111
|
-
base_xml = make_xml 'TicketCreateRequest'
|
112
|
-
|
113
|
-
required_attributes = {
|
114
|
-
'name' => ticket_name,
|
115
|
-
'priority' => priority,
|
116
|
-
'device-id' => device_id,
|
117
|
-
'assigned-to' => assigned_to
|
118
|
-
}
|
119
|
-
|
120
|
-
create_request_xml = REXML::Element.new 'TicketCreate'
|
121
|
-
create_request_xml.add_attributes required_attributes
|
122
|
-
|
123
|
-
# Add vulnerabilities
|
124
|
-
vulnerabilities_xml = REXML::Element.new 'Vulnerabilities'
|
125
|
-
vulnerabilities.each do |vuln_id|
|
126
|
-
vulnerabilities_xml.add_element 'Vulnerability', {'id' => vuln_id.downcase}
|
127
|
-
end
|
128
|
-
create_request_xml.add_element vulnerabilities_xml
|
129
|
-
|
130
|
-
# Add comments
|
131
|
-
if comments and comments.count > 0
|
132
|
-
comments_xml = REXML::Element.new 'Comments'
|
133
|
-
comments.each do |comment|
|
134
|
-
comment_xml = REXML::Element.new 'Comment'
|
135
|
-
comment_xml.add_text comment
|
136
|
-
comments_xml.add_element comment_xml
|
137
|
-
end
|
138
|
-
|
139
|
-
create_request_xml.add_element comments_xml
|
140
|
-
end
|
141
|
-
|
142
|
-
base_xml.add_element create_request_xml
|
143
|
-
r = execute base_xml, '1.2'
|
144
|
-
if r.success
|
145
|
-
r.res.elements.each('TicketCreateResponse') do |group|
|
146
|
-
return group.attributes['id'].to_i
|
147
|
-
end
|
148
|
-
else
|
149
|
-
false
|
150
|
-
end
|
151
|
-
end
|
152
45
|
end
|
153
46
|
|
154
47
|
# Summary of ticket information returned from a ticket listing request.
|
@@ -182,21 +75,182 @@ module Nexpose
|
|
182
75
|
# The current status of the ticket.
|
183
76
|
attr_accessor :state
|
184
77
|
|
185
|
-
def initialize(
|
78
|
+
def initialize(name, id)
|
186
79
|
@id, @name = id, name
|
187
80
|
end
|
188
81
|
|
189
82
|
def self.parse(xml)
|
190
|
-
ticket = new(xml.attributes['
|
191
|
-
xml.attributes['
|
83
|
+
ticket = new(xml.attributes['name'],
|
84
|
+
xml.attributes['id'].to_i)
|
192
85
|
ticket.device_id = xml.attributes['device-id'].to_i
|
193
86
|
ticket.assigned_to = xml.attributes['assigned-to']
|
194
|
-
|
87
|
+
lookup = Ticket::Priority.constants.reduce({}) { |a, e| a[Ticket::Priority.const_get(e)] = e; a }
|
88
|
+
ticket.priority = lookup[xml.attributes['priority']]
|
195
89
|
ticket.author = xml.attributes['author']
|
196
|
-
ticket.created_on = DateTime
|
90
|
+
ticket.created_on = DateTime.parse(xml.attributes['created-on']).to_time
|
91
|
+
ticket.created_on -= ticket.created_on.gmt_offset
|
197
92
|
lookup = Ticket::State.constants.reduce({}) { |a, e| a[Ticket::State.const_get(e)] = e; a }
|
198
93
|
ticket.state = lookup[xml.attributes['state']]
|
199
94
|
ticket
|
200
95
|
end
|
96
|
+
|
97
|
+
module State
|
98
|
+
OPEN = 'O'
|
99
|
+
ASSIGNED = 'A'
|
100
|
+
MODIFIED = 'M'
|
101
|
+
FIXED = 'X'
|
102
|
+
PARTIAL = 'P'
|
103
|
+
REJECTED_FIX = 'R'
|
104
|
+
PRIORITIZED = 'Z'
|
105
|
+
NOT_REPRODUCIBLE = 'F'
|
106
|
+
NOT_ISSUE = 'I'
|
107
|
+
CLOSED = 'C'
|
108
|
+
UNKNOWN = 'U'
|
109
|
+
end
|
110
|
+
|
111
|
+
module Priority
|
112
|
+
LOW = 'low'
|
113
|
+
MODERATE = 'moderate'
|
114
|
+
NORMAL = 'normal'
|
115
|
+
HIGH = 'high'
|
116
|
+
CRITICAL = 'critical'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Ticket < TicketSummary
|
121
|
+
|
122
|
+
# List of vulnerabilities (by ID) this ticket addresses.
|
123
|
+
attr_accessor :vulnerabilities
|
124
|
+
|
125
|
+
# Array of comments about the ticket.
|
126
|
+
attr_accessor :comments
|
127
|
+
|
128
|
+
# History of events on this ticket.
|
129
|
+
attr_accessor :history
|
130
|
+
|
131
|
+
def initialize(name, id = nil)
|
132
|
+
@id, @name = id, name
|
133
|
+
@priority = Priority::NORMAL
|
134
|
+
@vulnerabilities = []
|
135
|
+
@comments = []
|
136
|
+
@history = []
|
137
|
+
end
|
138
|
+
|
139
|
+
# Save this ticket to a Nexpose console.
|
140
|
+
#
|
141
|
+
# @param [Connection] connection Connection to console where ticket exists.
|
142
|
+
# @return [Fixnum] Unique ticket ID assigned to this ticket.
|
143
|
+
#
|
144
|
+
def save(connection)
|
145
|
+
xml = connection.make_xml('TicketCreateRequest')
|
146
|
+
xml.add_element(to_xml)
|
147
|
+
|
148
|
+
response = connection.execute(xml, '1.2')
|
149
|
+
@id = response.attributes['id'].to_i if response.success
|
150
|
+
end
|
151
|
+
|
152
|
+
# Delete this ticket from the system.
|
153
|
+
#
|
154
|
+
# @param [Connection] connection Connection to console where ticket exists.
|
155
|
+
# @return [Boolean] Whether the ticket was successfully delete.
|
156
|
+
#
|
157
|
+
def delete(connection)
|
158
|
+
connection.delete_ticket(@id)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Load existing ticket data.
|
162
|
+
#
|
163
|
+
# @param [Connection] connection Connection to console where ticket exists.
|
164
|
+
# @param [Fixnum] id Ticket ID of an existing ticket.
|
165
|
+
# @return [Ticket] Ticket populated with current state.
|
166
|
+
#
|
167
|
+
def self.load(connection, id)
|
168
|
+
# TODO: Load multiple tickets in a single request, as supported by API.
|
169
|
+
xml = connection.make_xml('TicketDetailsRequest')
|
170
|
+
xml.add_element('Ticket', { 'id' => id })
|
171
|
+
response = connection.execute(xml, '1.2')
|
172
|
+
response.res.elements.each('//TicketInfo') do |info|
|
173
|
+
return parse_details(info)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_xml
|
178
|
+
xml = REXML::Element.new('TicketCreate')
|
179
|
+
xml.add_attributes({ 'name' => @name,
|
180
|
+
'priority' => @priority,
|
181
|
+
'device-id' => @device_id,
|
182
|
+
'assigned-to' => @assigned_to })
|
183
|
+
|
184
|
+
vuln_xml = REXML::Element.new('Vulnerabilities')
|
185
|
+
@vulnerabilities.each do |vuln_id|
|
186
|
+
vuln_xml.add_element('Vulnerability', { 'id' => vuln_id.downcase })
|
187
|
+
end
|
188
|
+
xml.add_element(vuln_xml)
|
189
|
+
|
190
|
+
unless @comments.empty?
|
191
|
+
comments_xml = REXML::Element.new('Comments')
|
192
|
+
@comments.each do |comment|
|
193
|
+
comment_xml = REXML::Element.new('Comment')
|
194
|
+
comment_xml.add_text(comment)
|
195
|
+
comments_xml.add_element(comment_xml)
|
196
|
+
end
|
197
|
+
xml.add_element(comments_xml)
|
198
|
+
end
|
199
|
+
|
200
|
+
xml
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.parse_details(xml)
|
204
|
+
ticket = parse(xml)
|
205
|
+
|
206
|
+
xml.elements.each('Vulnerabilities/Vulnerability') do |vuln|
|
207
|
+
ticket.vulnerabilities << vuln.attributes['id']
|
208
|
+
end
|
209
|
+
|
210
|
+
xml.elements.each('TicketHistory/Entry') do |entry|
|
211
|
+
ticket.history << Event.parse(entry)
|
212
|
+
end
|
213
|
+
|
214
|
+
ticket.comments = ticket.history.select { |h| h.description == 'Added comment' }.map { |e| e.comment }
|
215
|
+
|
216
|
+
ticket
|
217
|
+
end
|
218
|
+
|
219
|
+
class Event
|
220
|
+
|
221
|
+
# Date and time of the ticket event.
|
222
|
+
attr_reader :created_on
|
223
|
+
# The login name of the person responsible for the event.
|
224
|
+
attr_reader :author
|
225
|
+
# The status of the ticket at the time the event was recorded.
|
226
|
+
attr_reader :state
|
227
|
+
# Description of the ticket event.
|
228
|
+
attr_accessor :description
|
229
|
+
# Comment on the ticket event.
|
230
|
+
attr_accessor :comment
|
231
|
+
|
232
|
+
def initialize(state, author, created)
|
233
|
+
@state, @author, @created = state, author, created
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.parse(xml)
|
237
|
+
author = xml.attributes['author']
|
238
|
+
created_on = DateTime.parse(xml.attributes['created-on']).to_time
|
239
|
+
created_on -= created_on.gmt_offset
|
240
|
+
|
241
|
+
event = REXML::XPath.first(xml, 'Event')
|
242
|
+
lookup = Ticket::State.constants.reduce({}) { |a, e| a[Ticket::State.const_get(e)] = e; a }
|
243
|
+
state = lookup[event.attributes['state']]
|
244
|
+
desc = event.text
|
245
|
+
|
246
|
+
event = new(state, author, created_on)
|
247
|
+
|
248
|
+
comment = REXML::XPath.first(xml, 'Comment')
|
249
|
+
event.comment = comment.text if comment
|
250
|
+
|
251
|
+
event.description = desc if desc
|
252
|
+
event
|
253
|
+
end
|
254
|
+
end
|
201
255
|
end
|
202
256
|
end
|
data/lib/nexpose/user.rb
CHANGED
@@ -7,7 +7,7 @@ module Nexpose
|
|
7
7
|
#
|
8
8
|
# @return [Array[UserSummary]] Array of users.
|
9
9
|
#
|
10
|
-
def
|
10
|
+
def list_users
|
11
11
|
r = execute(make_xml('UserListingRequest'))
|
12
12
|
arr = []
|
13
13
|
if r.success
|
@@ -18,7 +18,12 @@ module Nexpose
|
|
18
18
|
arr
|
19
19
|
end
|
20
20
|
|
21
|
+
alias_method :users, :list_users
|
22
|
+
|
21
23
|
# Retrieve the User ID based upon the user's login name.
|
24
|
+
#
|
25
|
+
# @param [String] user_name User name to search for.
|
26
|
+
#
|
22
27
|
def get_user_id(user_name)
|
23
28
|
users.find { |user| user.name.eql? user_name }
|
24
29
|
end
|
@@ -35,7 +40,9 @@ module Nexpose
|
|
35
40
|
end
|
36
41
|
|
37
42
|
# Summary only returned by API when issuing a listing request.
|
43
|
+
#
|
38
44
|
class UserSummary
|
45
|
+
|
39
46
|
attr_reader :id, :auth_source, :auth_module, :name, :full_name, :email
|
40
47
|
attr_reader :is_admin, :is_disabled, :is_locked, :site_count, :group_count
|
41
48
|
|
@@ -176,6 +183,7 @@ module Nexpose
|
|
176
183
|
end
|
177
184
|
|
178
185
|
class UserAuthenticator
|
186
|
+
|
179
187
|
attr_reader :id, :auth_source, :auth_module, :external
|
180
188
|
|
181
189
|
def initialize(id, auth_module, auth_source, external = false)
|
@@ -189,13 +197,13 @@ module Nexpose
|
|
189
197
|
# * *Returns* : An array of known user authenticator sources.
|
190
198
|
def self.list(connection)
|
191
199
|
r = connection.execute('<UserAuthenticatorListingRequest session-id="' + connection.session_id + '" />', '1.1')
|
200
|
+
modules = []
|
192
201
|
if r.success
|
193
|
-
modules = []
|
194
202
|
r.res.elements.each('UserAuthenticatorListingResponse/AuthenticatorSummary') do |summary|
|
195
203
|
modules << UserAuthenticator.new(summary.attributes['id'], summary.attributes['authModule'], summary.attributes['authSource'], ('1'.eql? summary.attributes['external']))
|
196
204
|
end
|
197
|
-
modules
|
198
205
|
end
|
206
|
+
modules
|
199
207
|
end
|
200
208
|
end
|
201
209
|
end
|
data/lib/nexpose/vuln.rb
CHANGED
@@ -9,15 +9,15 @@ module Nexpose
|
|
9
9
|
# It can take twice a long to retrieve full summary information.
|
10
10
|
# @return [Array[Vulnerability|VulnerabilitySummary]] Collection of all known vulnerabilities.
|
11
11
|
#
|
12
|
-
def
|
12
|
+
def list_vulns(full = false)
|
13
13
|
xml = make_xml('VulnerabilityListingRequest')
|
14
|
-
# TODO Add a flag to do stream parsing of the XML to improve performance.
|
14
|
+
# TODO: Add a flag to do stream parsing of the XML to improve performance.
|
15
15
|
response = execute(xml, '1.2')
|
16
16
|
vulns = []
|
17
17
|
if response.success
|
18
18
|
response.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |vuln|
|
19
19
|
if full
|
20
|
-
vulns << VulnerabilitySummary
|
20
|
+
vulns << VulnerabilitySummary.parse(vuln)
|
21
21
|
else
|
22
22
|
vulns << Vulnerability.new(vuln.attributes['id'],
|
23
23
|
vuln.attributes['title'],
|
@@ -28,7 +28,7 @@ module Nexpose
|
|
28
28
|
vulns
|
29
29
|
end
|
30
30
|
|
31
|
-
alias_method :vulns, :
|
31
|
+
alias_method :vulns, :list_vulns
|
32
32
|
|
33
33
|
# Retrieve details for a vulnerability.
|
34
34
|
#
|
@@ -36,17 +36,34 @@ module Nexpose
|
|
36
36
|
# @return [VulnerabilityDetail] Details of the requested vulnerability.
|
37
37
|
#
|
38
38
|
def vuln_details(vuln_id)
|
39
|
-
xml = make_xml('VulnerabilityDetailsRequest', {'vuln-id' => vuln_id})
|
39
|
+
xml = make_xml('VulnerabilityDetailsRequest', { 'vuln-id' => vuln_id })
|
40
40
|
response = execute(xml, '1.2')
|
41
41
|
if response.success
|
42
42
|
response.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |vuln|
|
43
|
-
return VulnerabilityDetail
|
43
|
+
return VulnerabilityDetail.parse(vuln)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
# Search for Vulnerability Checks.
|
49
|
+
#
|
50
|
+
# @param [String] search_term Search terms to search for.
|
51
|
+
# @param [Boolean] partial_words Allow partial word matches.
|
52
|
+
# @param [Boolean] all_words All words must be present.
|
53
|
+
# @return [Array[VulnCheck]] List of matching Vulnerability Checks.
|
54
|
+
#
|
55
|
+
def find_vuln_check(search_term, partial_words = true, all_words = true)
|
56
|
+
uri = "/ajax/vulnck_synopsis.txml?phrase=#{URI.encode(search_term)}"
|
57
|
+
uri += '&wholeWords=1' unless partial_words
|
58
|
+
uri += '&allWords=1' if all_words
|
59
|
+
data = DataTable._get_dyn_table(self, uri)
|
60
|
+
data.map do |vuln|
|
61
|
+
VulnCheck.new(vuln)
|
62
|
+
end
|
63
|
+
end
|
47
64
|
end
|
48
65
|
|
49
|
-
# Basic vulnerability information. Only includes
|
66
|
+
# Basic vulnerability information. Only includes ID, title, and severity.
|
50
67
|
#
|
51
68
|
class Vulnerability
|
52
69
|
|
@@ -60,7 +77,25 @@ module Nexpose
|
|
60
77
|
attr_reader :severity
|
61
78
|
|
62
79
|
def initialize(id, title, severity)
|
63
|
-
@id, @title, @severity = id, title, severity
|
80
|
+
@id, @title, @severity = id, title, severity.to_i
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Vulnerability Check information.
|
85
|
+
#
|
86
|
+
class VulnCheck < Vulnerability
|
87
|
+
|
88
|
+
attr_reader :check_id
|
89
|
+
attr_reader :categories
|
90
|
+
attr_reader :check_type
|
91
|
+
|
92
|
+
def initialize(json)
|
93
|
+
@id = json['Vuln ID']
|
94
|
+
@check_id = json['Vuln Check ID']
|
95
|
+
@title = json['Vulnerability']
|
96
|
+
@severity = json['Severity'].to_i
|
97
|
+
@check_type = json['Check Type']
|
98
|
+
@categories = json['Category'].split(/, */)
|
64
99
|
end
|
65
100
|
end
|
66
101
|
|
@@ -68,14 +103,14 @@ module Nexpose
|
|
68
103
|
#
|
69
104
|
class VulnerabilitySummary < Vulnerability
|
70
105
|
|
71
|
-
# PCI severity value for the vulnerability on a scale of 1 to 5.
|
106
|
+
# PCI severity value for the vulnerability on a scale of 1 to 5.
|
72
107
|
attr_accessor :pci_severity
|
73
108
|
|
74
109
|
# Whether all checks for the vulnerability are safe.
|
75
110
|
# Unsafe checks may cause denial of service or otherwise disrupt system performance.
|
76
111
|
attr_accessor :safe
|
77
112
|
|
78
|
-
# A vulnerability is considered
|
113
|
+
# A vulnerability is considered "credentialed" when all of its checks
|
79
114
|
# require credentials or if the check depends on previous authentication
|
80
115
|
# during a scan.
|
81
116
|
attr_accessor :credentials
|
@@ -103,12 +138,12 @@ module Nexpose
|
|
103
138
|
|
104
139
|
vuln.pci_severity = xml.attributes['pciSeverity'].to_i
|
105
140
|
vuln.safe = xml.attributes['safe'] == 'true' # or xml.attributes['safe'] == '1'
|
106
|
-
vuln.added = Date
|
107
|
-
vuln.modified = Date
|
141
|
+
vuln.added = Date.parse(xml.attributes['added'])
|
142
|
+
vuln.modified = Date.parse(xml.attributes['modified'])
|
108
143
|
vuln.credentials = xml.attributes['requiresCredentials'] == 'true'
|
109
144
|
|
110
145
|
# These three fields are optional in the XSD.
|
111
|
-
vuln.published = Date
|
146
|
+
vuln.published = Date.parse(xml.attributes['published']) if xml.attributes['published']
|
112
147
|
vuln.cvss_vector = xml.attributes['cvssVector'] if xml.attributes['cvssVector']
|
113
148
|
vuln.cvss_score = xml.attributes['cvssScore'].to_f if xml.attributes['cvssScore']
|
114
149
|
vuln
|
@@ -164,332 +199,40 @@ module Nexpose
|
|
164
199
|
end
|
165
200
|
end
|
166
201
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
###################
|
173
|
-
|
174
|
-
#-----------------------------------------------------------------------
|
175
|
-
# Returns an array of vulnerability exceptions and their associated
|
176
|
-
# attributes.
|
177
|
-
#
|
178
|
-
# @param status - (optional) The status of the vulnerability exception:
|
179
|
-
# "Under Review", "Approved", "Rejected"
|
180
|
-
#-----------------------------------------------------------------------
|
181
|
-
def vuln_exception_listing(status = nil)
|
182
|
-
option = {}
|
183
|
-
|
184
|
-
if status && !status.empty?
|
185
|
-
if status =~ /Under Review|Approved|Rejected/
|
186
|
-
option['status'] = status
|
187
|
-
else
|
188
|
-
raise ArgumentError.new 'The vulnerability status passed in is invalid!'
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
xml = make_xml('VulnerabilityExceptionListingRequest', option)
|
193
|
-
r = execute xml, '1.2'
|
194
|
-
|
195
|
-
if r.success
|
196
|
-
res = []
|
197
|
-
r.res.elements.each('//VulnerabilityException') do |ve|
|
198
|
-
submitter_comment = ve.elements['submitter-comment']
|
199
|
-
reviewer_comment = ve.elements['reviewer-comment']
|
200
|
-
res << {
|
201
|
-
:vuln_id => ve.attributes['vuln-id'],
|
202
|
-
:exception_id => ve.attributes['exception-id'],
|
203
|
-
:submitter => ve.attributes['submitter'],
|
204
|
-
:reviewer => ve.attributes['reviewer'],
|
205
|
-
:status => ve.attributes['status'],
|
206
|
-
:reason => ve.attributes['reason'],
|
207
|
-
:scope => ve.attributes['scope'],
|
208
|
-
:device_id => ve.attributes['device-id'],
|
209
|
-
:port_no => ve.attributes['port-no'],
|
210
|
-
:expiration_date => ve.attributes['expiration-date'],
|
211
|
-
:vuln_key => ve.attributes['vuln-key'],
|
212
|
-
:submitter_comment => submitter_comment.nil? ? '' : submitter_comment.text,
|
213
|
-
:reviewer_comment => reviewer_comment.nil? ? '' : reviewer_comment.text
|
214
|
-
}
|
215
|
-
end
|
216
|
-
res
|
217
|
-
else
|
218
|
-
false
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
#-------------------------------------------------------------------------------------------------------------------
|
223
|
-
# Creates a vulnerability exception.
|
224
|
-
#
|
225
|
-
# @param input - data used to create the vulnerability exception:
|
226
|
-
# :vuln_id - The Nexpose vulnerability ID.
|
227
|
-
# :reason - The reason for the exception
|
228
|
-
# values - "False Positive", "Compensating Control", "Acceptable Use", "Acceptable Risk", "Other"
|
229
|
-
# :scope - The scope type (NOTE: The case is important)
|
230
|
-
# values - "All Instances", "All Instances on a Specific Asset", "Specific Instance of a specific Asset"
|
231
|
-
# :comment - A user comment
|
232
|
-
# :device-id - Used for specific instances related to "All Instances on a Specific Asset" AND "Specific Instance of Specific Asset"
|
233
|
-
# :port - All assets on this port related to "Specific Instance of a specific Asset"
|
234
|
-
# :vuln-key - The vulnerability key related to the "Specific Instance of a specific Asset"
|
235
|
-
#
|
236
|
-
# @returns exception-id - The Id associated with this create request
|
237
|
-
#-------------------------------------------------------------------------------------------------------------------
|
238
|
-
def vuln_exception_create(input)
|
239
|
-
options = {}
|
240
|
-
|
241
|
-
if input.nil?
|
242
|
-
raise ArgumentError.new 'The input element cannot be null'
|
243
|
-
end
|
244
|
-
|
245
|
-
vuln_id = input[:vuln_id]
|
246
|
-
unless vuln_id
|
247
|
-
raise ArgumentError.new 'The vulnerability ID is required'
|
248
|
-
end
|
249
|
-
options['vuln-id'] = vuln_id
|
250
|
-
|
251
|
-
reason = input[:reason]
|
252
|
-
if reason.nil? || reason.empty?
|
253
|
-
raise ArgumentError.new 'The reason is required'
|
254
|
-
end
|
255
|
-
|
256
|
-
unless reason =~ /False Positive|Compensating Control|Acceptable Use|Acceptable Risk|Other/
|
257
|
-
raise ArgumentError.new 'The reason type is invalid'
|
258
|
-
end
|
259
|
-
options['reason'] = reason
|
260
|
-
|
261
|
-
scope = input[:scope]
|
262
|
-
if scope.nil? || scope.empty?
|
263
|
-
raise ArgumentError.new 'The scope is required'
|
264
|
-
end
|
265
|
-
|
266
|
-
# For scope case matters.
|
267
|
-
unless scope =~ /All Instances|All Instances on a Specific Asset|Specific Instance of Specific Asset/
|
268
|
-
raise ArgumentError.new 'The scope type is invalid'
|
269
|
-
end
|
270
|
-
|
271
|
-
if scope =~ /All Instances on a Specific Asset|Specific Instance of Specific Asset/
|
272
|
-
device_id = input[:device_id]
|
273
|
-
vuln_key = input[:vuln_key]
|
274
|
-
port = input[:port]
|
275
|
-
if device_id
|
276
|
-
options['device-id'] = device_id
|
277
|
-
end
|
278
|
-
|
279
|
-
if scope =~ /All Instances on a Specific Asset/ && (vuln_key || port)
|
280
|
-
raise ArgumentError.new 'Vulnerability key or port cannot be used with the scope specified'
|
281
|
-
end
|
282
|
-
|
283
|
-
if vuln_key
|
284
|
-
options['vuln-key'] = vuln_key
|
285
|
-
end
|
286
|
-
|
287
|
-
if port
|
288
|
-
options['port-no'] = port
|
289
|
-
end
|
290
|
-
end
|
291
|
-
options['scope'] = scope
|
292
|
-
|
293
|
-
xml = make_xml('VulnerabilityExceptionCreateRequest', options)
|
294
|
-
|
295
|
-
comment = input[:comment]
|
296
|
-
if comment && !comment.empty?
|
297
|
-
comment_xml = make_xml('comment', {}, comment, false)
|
298
|
-
xml.add_element comment_xml
|
299
|
-
else
|
300
|
-
raise ArgumentError.new 'The comment cannot be empty'
|
301
|
-
end
|
302
|
-
|
303
|
-
r = execute xml, '1.2'
|
304
|
-
if r.success
|
305
|
-
r.res.elements.each('//VulnerabilityExceptionCreateResponse') do |vecr|
|
306
|
-
return vecr.attributes['exception-id']
|
307
|
-
end
|
308
|
-
else
|
309
|
-
false
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
#-------------------------------------------------------------------------------------------------------------------
|
314
|
-
# Resubmit a vulnerability exception.
|
315
|
-
#
|
316
|
-
# @param input - data used to create the vulnerability exception:
|
317
|
-
# :vuln_id - The Nexpose vulnerability ID. (required)
|
318
|
-
# :reason - The reason for the exception (optional)
|
319
|
-
# values - "False Positive", "Compensating Control", "Acceptable Use", "Acceptable Risk", "Other"
|
320
|
-
# :comment - A user comment (required)
|
321
|
-
#-------------------------------------------------------------------------------------------------------------------
|
322
|
-
def vuln_exception_resubmit(input)
|
323
|
-
options = {}
|
324
|
-
|
325
|
-
if input.nil?
|
326
|
-
raise ArgumentError.new 'The input element cannot be null'
|
327
|
-
end
|
328
|
-
|
329
|
-
exception_id = input[:exception_id]
|
330
|
-
unless exception_id
|
331
|
-
raise ArgumentError.new 'The exception ID is required'
|
332
|
-
end
|
333
|
-
options['exception-id'] = exception_id
|
334
|
-
|
335
|
-
reason = input[:reason]
|
336
|
-
if !reason.nil? && !reason.empty?
|
337
|
-
unless reason =~ /False Positive|Compensating Control|Acceptable Use|Acceptable Risk|Other/
|
338
|
-
raise ArgumentError.new 'The reason type is invalid'
|
339
|
-
end
|
340
|
-
options['reason'] = reason
|
341
|
-
|
342
|
-
end
|
343
|
-
|
344
|
-
xml = make_xml('VulnerabilityExceptionResubmitRequest', options)
|
345
|
-
|
346
|
-
comment = input[:comment]
|
347
|
-
if comment && !comment.empty?
|
348
|
-
comment_xml = make_xml('comment', {}, comment, false)
|
349
|
-
xml.add_element comment_xml
|
350
|
-
end
|
351
|
-
|
352
|
-
r = execute xml, '1.2'
|
353
|
-
r.success
|
354
|
-
end
|
355
|
-
|
356
|
-
#-------------------------------------------------------------------------------------------------------------------
|
357
|
-
# Allows a previously submitted exception that has not been approved to be withdrawn.
|
358
|
-
#
|
359
|
-
# @param exception_id - The exception id returned after the vuln exception was submitted for creation.
|
360
|
-
#-------------------------------------------------------------------------------------------------------------------
|
361
|
-
def vuln_exception_recall(exception_id)
|
362
|
-
xml = make_xml('VulnerabilityExceptionRecallRequest', {'exception-id' => exception_id})
|
363
|
-
r = execute xml, '1.2'
|
364
|
-
r.success
|
365
|
-
end
|
366
|
-
|
367
|
-
|
368
|
-
#-------------------------------------------------------------------------------------------------------------------
|
369
|
-
# Allows a submitted vulnerability exception to be approved.
|
370
|
-
#
|
371
|
-
# @param input:
|
372
|
-
# :exception_id - The exception id returned after the vuln exception was submitted for creation.
|
373
|
-
# :comment - An optional comment
|
374
|
-
#-------------------------------------------------------------------------------------------------------------------
|
375
|
-
def vuln_exception_approve(input)
|
376
|
-
exception_id = input[:exception_id]
|
377
|
-
unless exception_id
|
378
|
-
raise ArgumentError.new 'Exception Id is required'
|
379
|
-
end
|
380
|
-
|
381
|
-
xml = make_xml('VulnerabilityExceptionApproveRequest', {'exception-id' => exception_id})
|
382
|
-
comment = input[:comment]
|
383
|
-
if comment && !comment.empty?
|
384
|
-
comment_xml = make_xml('comment', {}, comment, false)
|
385
|
-
xml.add_element comment_xml
|
386
|
-
end
|
387
|
-
|
388
|
-
r = execute xml, '1.2'
|
389
|
-
r.success
|
390
|
-
end
|
391
|
-
|
392
|
-
#-------------------------------------------------------------------------------------------------------------------
|
393
|
-
# Rejects a submitted vulnerability exception to be approved.
|
394
|
-
#
|
395
|
-
# @param input:
|
396
|
-
# :exception_id - The exception id returned after the vuln exception was submitted for creation.
|
397
|
-
# :comment - An optional comment
|
398
|
-
#-------------------------------------------------------------------------------------------------------------------
|
399
|
-
def vuln_exception_reject(input)
|
400
|
-
exception_id = input[:exception_id]
|
401
|
-
unless exception_id
|
402
|
-
raise ArgumentError.new 'Exception Id is required'
|
403
|
-
end
|
404
|
-
|
405
|
-
xml = make_xml('VulnerabilityExceptionRejectRequest', {'exception-id' => exception_id})
|
406
|
-
comment = input[:comment]
|
407
|
-
if comment && !comment.empty?
|
408
|
-
comment_xml = make_xml('comment', {}, comment, false)
|
409
|
-
xml.add_element comment_xml
|
410
|
-
end
|
411
|
-
|
412
|
-
r = execute xml, '1.2'
|
413
|
-
r.success
|
414
|
-
end
|
415
|
-
|
416
|
-
#-------------------------------------------------------------------------------------------------------------------
|
417
|
-
# Updates a vulnerability exception comment.
|
418
|
-
#
|
419
|
-
# @param input:
|
420
|
-
# :exception_id - The exception id returned after the vuln exception was submitted for creation.
|
421
|
-
# :submitter_comment - The submitter comment
|
422
|
-
# :reviewer_comment - The reviewer comment
|
423
|
-
#-------------------------------------------------------------------------------------------------------------------
|
424
|
-
def vuln_exception_update_comment(input)
|
425
|
-
exception_id = input[:exception_id]
|
426
|
-
unless exception_id
|
427
|
-
raise ArgumentError.new 'Exception Id is required'
|
428
|
-
end
|
429
|
-
|
430
|
-
xml = make_xml('VulnerabilityExceptionUpdateCommentRequest', {'exception-id' => exception_id})
|
431
|
-
submitter_comment = input[:submitter_comment]
|
432
|
-
if submitter_comment && !submitter_comment.empty?
|
433
|
-
comment_xml = make_xml('submitter-comment', {}, submitter_comment, false)
|
434
|
-
xml.add_element comment_xml
|
435
|
-
end
|
436
|
-
|
437
|
-
reviewer_comment = input[:reviewer_comment]
|
438
|
-
if reviewer_comment && !reviewer_comment.empty?
|
439
|
-
comment_xml = make_xml('reviewer-comment', {}, reviewer_comment, false)
|
440
|
-
xml.add_element comment_xml
|
441
|
-
end
|
442
|
-
|
443
|
-
r = execute xml, '1.2'
|
444
|
-
r.success
|
445
|
-
end
|
446
|
-
|
447
|
-
#-------------------------------------------------------------------------------------------------------------------
|
448
|
-
# Update the expiration date for a vulnerability exception.
|
449
|
-
#
|
450
|
-
# @param input
|
451
|
-
# :exception_id - The exception id returned after the vulnerability exception was submitted for creation.
|
452
|
-
# :expiration_date - The new expiration date format: YYYY-MM-DD
|
453
|
-
#-------------------------------------------------------------------------------------------------------------------
|
454
|
-
def vuln_exception_update_expiration_date(input)
|
455
|
-
exception_id = input[:exception_id]
|
456
|
-
unless exception_id
|
457
|
-
raise ArgumentError.new 'Exception Id is required'
|
458
|
-
end
|
459
|
-
|
460
|
-
expiration_date = input[:expiration_date]
|
461
|
-
if expiration_date && !expiration_date.empty? && expiration_date =~ /\A\d{4}-(\d{2})-(\d{2})\z/
|
462
|
-
if $1.to_i > 12
|
463
|
-
raise ArgumentError.new 'The expiration date month value is invalid'
|
464
|
-
end
|
465
|
-
if $2.to_i > 31
|
466
|
-
raise ArgumentError.new 'The expiration date day value is invalid'
|
467
|
-
end
|
468
|
-
else
|
469
|
-
raise ArgumentError.new 'Expiration date is invalid'
|
470
|
-
end
|
471
|
-
|
472
|
-
options = {}
|
473
|
-
options['exception-id'] = exception_id
|
474
|
-
options['expiration-date'] = expiration_date
|
475
|
-
xml = make_xml('VulnerabilityExceptionUpdateExpirationDateRequest', options)
|
476
|
-
r = execute xml, '1.2'
|
477
|
-
r.success
|
478
|
-
end
|
479
|
-
|
480
|
-
#-------------------------------------------------------------------------------------------------------------------
|
481
|
-
# Deletes a submitted vulnerability exception to be approved.
|
482
|
-
#
|
483
|
-
# @param exception_id - The exception id returned after the vuln exception was submitted for creation.
|
484
|
-
#-------------------------------------------------------------------------------------------------------------------
|
485
|
-
def vuln_exception_delete(exception_id)
|
486
|
-
unless exception_id
|
487
|
-
raise ArgumentError.new 'Exception Id is required'
|
488
|
-
end
|
202
|
+
# Vulnerability finding information pulled from AJAX requests.
|
203
|
+
# Data uses a numeric, console-specific vuln ID, which may need to be
|
204
|
+
# cross-referenced to the String ID to be used elsewhere.
|
205
|
+
#
|
206
|
+
class VulnFinding
|
489
207
|
|
490
|
-
|
491
|
-
|
492
|
-
|
208
|
+
# Unique, console-specific identifier of the vulnerability.
|
209
|
+
attr_reader :id
|
210
|
+
# Vulnerability title.
|
211
|
+
attr_reader :title
|
212
|
+
attr_reader :cvss_score
|
213
|
+
attr_reader :cvss_vector
|
214
|
+
attr_reader :risk
|
215
|
+
# Date this exploit was published.
|
216
|
+
attr_reader :published
|
217
|
+
attr_reader :severity
|
218
|
+
# Number of instances of this vulnerabilty finding on an asset.
|
219
|
+
attr_reader :instances
|
220
|
+
# Main published exploit module against this vulnerability, if any.
|
221
|
+
attr_reader :exploit
|
222
|
+
# Whether known malware kits exploit this vulnerability.
|
223
|
+
attr_reader :malware
|
224
|
+
|
225
|
+
def initialize(json)
|
226
|
+
@id = json['vulnID']
|
227
|
+
@title = json['title']
|
228
|
+
@cvss_vector = json['cvssBase']
|
229
|
+
@cvss_score = json['cvssScore']
|
230
|
+
@risk = json['riskScore']
|
231
|
+
@published = Time.at(json['publishedDate'] / 1000)
|
232
|
+
@severity = json['severity']
|
233
|
+
@instances = json['vulnInstanceCount']
|
234
|
+
@exploit = json['mainExploit']
|
235
|
+
@malware = json['malwareCount']
|
493
236
|
end
|
494
237
|
end
|
495
238
|
end
|