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.
@@ -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