nexpose 0.2.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ticket_listing
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::parse(summary)
13
+ tickets << TicketSummary.parse(summary)
38
14
  end
39
15
  end
40
16
  tickets
41
17
  end
42
18
 
43
- alias_method :tickets, :ticket_listing
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(id, name)
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['id'].to_i,
191
- xml.attributes['name'])
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
- ticket.priority = xml.attributes['priority']
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::parse(xml.attributes['created-on'])
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 users
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 vuln_listing(full = false)
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::parse(vuln)
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, :vuln_listing
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::parse(vuln)
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 id, title, and severity.
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 credentialed when all of its checks
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::parse(xml.attributes['added'])
107
- vuln.modified = Date::parse(xml.attributes['modified'])
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::parse(xml.attributes['published']) if xml.attributes['published']
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
- module NexposeAPI
168
- include XMLUtils
169
-
170
- ###################
171
- # VULN EXCEPTIONS #
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
- xml = make_xml('VulnerabilityExceptionDeleteRequest', {'exception-id' => exception_id})
491
- r = execute xml, '1.2'
492
- r.success
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