nexpose 0.1.14 → 0.2.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,5 +1,13 @@
1
1
  module Nexpose
2
2
 
3
+ # Constants
4
+
5
+ module Scope
6
+
7
+ GLOBAL = 'global'
8
+ SILO = 'silo'
9
+ end
10
+
3
11
  # Configuration structure for e-mail notification.
4
12
  #
5
13
  # The send_as and send_to_acl_as attributes are optional, but one of them is
@@ -25,7 +25,7 @@ module Nexpose
25
25
 
26
26
  res = []
27
27
  if r.success
28
- r.res.elements.each('//AssetGroupSummary') do |group|
28
+ r.res.elements.each('AssetGroupListingResponse/AssetGroupSummary') do |group|
29
29
  res << AssetGroupSummary.new(group.attributes['id'].to_i,
30
30
  group.attributes['name'].to_s,
31
31
  group.attributes['description'].to_s,
@@ -36,6 +36,7 @@ module Nexpose
36
36
  end
37
37
 
38
38
  alias_method :asset_groups_listing, :asset_groups
39
+ alias_method :groups, :asset_groups
39
40
  end
40
41
 
41
42
  # Summary value object for asset group information.
@@ -127,23 +128,23 @@ module Nexpose
127
128
 
128
129
  alias_method :get, :load
129
130
 
130
- def self.parse(rexml)
131
- return nil unless rexml
132
-
133
- rexml.elements.each('//AssetGroup') do |group|
134
- asset_group = new(group.attributes['name'].to_s,
135
- group.attributes['description'].to_s,
136
- group.attributes['id'].to_i,
137
- group.attributes['riskscore'].to_f)
138
- rexml.elements.each('//Devices/device') do |dev|
139
- asset_group.devices << Device.new(dev.attributes['id'].to_i,
140
- dev.attributes['address'].to_s,
141
- dev.attributes['site-id'].to_i,
142
- dev.attributes['riskfactor'].to_f,
143
- dev.attributes['riskscore'].to_f)
144
- end
145
- return asset_group
131
+ def self.parse(xml)
132
+ puts xml
133
+ return nil unless xml
134
+
135
+ group = REXML::XPath.first(xml, 'AssetGroupConfigResponse/AssetGroup')
136
+ asset_group = new(group.attributes['name'].to_s,
137
+ group.attributes['description'].to_s,
138
+ group.attributes['id'].to_i,
139
+ group.attributes['riskscore'].to_f)
140
+ group.elements.each('Devices/device') do |dev|
141
+ asset_group.devices << Device.new(dev.attributes['id'].to_i,
142
+ dev.attributes['address'].to_s,
143
+ dev.attributes['site-id'].to_i,
144
+ dev.attributes['riskfactor'].to_f,
145
+ dev.attributes['riskscore'].to_f)
146
146
  end
147
+ asset_group
147
148
  end
148
149
  end
149
150
  end
@@ -1,27 +1,259 @@
1
1
  module Nexpose
2
2
 
3
+ # Constants
4
+ module Privilege
5
+
6
+ module Global
7
+ CREATE_REPORTS = 'CreateReports'
8
+ CONFIGURE_GLOBAL_SETTINGS = 'ConfigureGlobalSettings'
9
+ MANAGE_SITES = 'ManageSites'
10
+ MANAGE_ASSET_GROUPS = 'ManageAssetGroups'
11
+ MANAGE_DYNAMIC_ASSET_GROUPS = 'ManageDynamicAssetGroups'
12
+ MANAGE_SCAN_TEMPLATES = 'ManageScanTemplates'
13
+ MANAGE_REPORT_TEMPLATES = 'ManageReportTemplates'
14
+ GENERATE_RESTRICTED_REPORTS = 'GenerateRestrictedReports'
15
+ MANAGE_SCAN_ENGINES = 'ManageScanEngines'
16
+ SUBMIT_VULN_EXCEPTIONS = 'SubmitVulnExceptions'
17
+ APPROVE_VULN_EXCEPTIONS = 'ApproveVulnExceptions'
18
+ DELETE_VULN_EXCEPTIONS = 'DeleteVulnExceptions'
19
+ CREATE_TICKETS = 'CreateTickets'
20
+ CLOSE_TICKETS = 'CloseTickets'
21
+ TICKET_ASSIGNEE = 'TicketAssignee'
22
+ ADD_USERS_TO_SITE = 'AddUsersToSite'
23
+ ADD_USERS_TO_GROUP = 'AddUsersToGroup'
24
+ ADD_USERS_TO_REPORT = 'AddUsersToReport'
25
+ MANAGE_POLICIES = 'ManagePolicies'
26
+ end
27
+
28
+ module Site
29
+ VIEW_ASSET_DATA = 'ViewAssetData' # NOTE Duplicated between Site and AssetGroup
30
+ CONFIGURE_ALERTS = 'ConfigureAlerts'
31
+ CONFIGURE_CREDENTIALS = 'ConfigureCredentials'
32
+ CONFIGURE_ENGINES = 'ConfigureEngines'
33
+ CONFIGURE_SCAN_TEMPLATES = 'ConfigureScanTemplates'
34
+ CONFIGURE_SCHEDULE_SCANS = 'ConfigureScheduleScans'
35
+ CONFIGURE_SITE_SETTINGS = 'ConfigureSiteSettings'
36
+ CONFIGURE_TARGETS = 'ConfigureTargets'
37
+ MANUAL_SCANS = 'ManualScans'
38
+ PURGE_DATA = 'PurgeData'
39
+ end
40
+
41
+ module AssetGroup
42
+ CONFIGURE_ASSETS = 'ConfigureAssets'
43
+ VIEW_ASSET_DATA = 'ViewAssetData' # NOTE Duplicated between Site and AssetGroup
44
+ end
45
+ end
46
+
3
47
  module NexposeAPI
4
48
  include XMLUtils
5
49
 
6
50
  # Returns a summary list of all roles.
51
+ #
7
52
  def role_listing
8
53
  xml = make_xml('RoleListingRequest')
9
54
  r = execute(xml, '1.2')
55
+ roles = []
10
56
  if r.success
11
- res = []
12
57
  r.res.elements.each('RoleListingResponse/RoleSummary') do |summary|
13
- res << {
14
- :id => summary.attributes['id'],
15
- :name => summary.attributes['name'],
16
- :full_name => summary.attributes['full-name'],
17
- :description => summary.attributes['description'],
18
- :enabled => summary.attributes['enabled'],
19
- :scope => summary.attributes['scope']
20
- }
58
+ roles << RoleSummary::parse(summary)
21
59
  end
22
- res
23
60
  end
61
+ roles
24
62
  end
63
+
64
+ alias_method :roles, :role_listing
65
+
66
+ def role_delete(role, scope = Scope::SILO)
67
+ xml = %Q(<RoleDeleteRequest session-id="#{@session_id}">)
68
+ xml << %Q(<Role name="#{role}" scope="#{scope}"/>)
69
+ xml << '</RoleDeleteRequest>'
70
+ response = execute(xml, '1.2')
71
+ response.success
72
+ end
73
+
74
+ alias_method :delete_role, :role_delete
25
75
  end
26
76
 
77
+ # Role summary object encapsulating information about a role.
78
+ #
79
+ class RoleSummary
80
+
81
+ # The short name of the role. Must be unique.
82
+ attr_accessor :name
83
+
84
+ # The full name of the role. Must be unique.
85
+ attr_accessor :full_name
86
+
87
+ # The unique identifier of the role.
88
+ attr_accessor :id
89
+
90
+ # A description of the role.
91
+ attr_accessor :description
92
+
93
+ # Whether or not the role is enabled.
94
+ attr_accessor :enabled
95
+
96
+ # Specifies if the role has global or silo scope.
97
+ # @see Nexpose::Scope
98
+ attr_accessor :scope
99
+
100
+ def initialize(name, full_name, id, description, enabled = true, scope = Scope::SILO)
101
+ @name, @full_name, @id, @description, @enabled, @scope = name, full_name, id, description, enabled, scope
102
+ end
103
+
104
+ def self.parse(xml)
105
+ new(xml.attributes['name'],
106
+ xml.attributes['full-name'],
107
+ xml.attributes['id'].to_i,
108
+ xml.attributes['description'],
109
+ xml.attributes['enabled'] == 'true',
110
+ xml.attributes['scope'])
111
+ end
112
+ end
113
+
114
+ class Role < RoleSummary
115
+
116
+ # Array of all privileges which are enabled for this role.
117
+ # Note: Although the underlying XML has different requirements, this only checks for presence.
118
+ # @see Nexpose::Privilege
119
+ attr_accessor :privileges
120
+
121
+ # Flag to track whether this role exists already on the Nexpose console.
122
+ # Flag determines behavior of #save method.
123
+ attr_accessor :existing
124
+
125
+ def initialize(name, full_name, id, enabled = true, scope = Scope::SILO)
126
+ @name, @full_name, @id, @enabled, @scope = name, full_name, id, enabled, scope
127
+ @privileges = []
128
+ end
129
+
130
+ # Retrieve a detailed description of a single role.
131
+ #
132
+ # @param [Connection] nsc Nexpose connection.
133
+ # @param [String] name The short name of the role.
134
+ # @param [String] scope Whether the role has global or silo scope. @see Nexpose::Scope
135
+ # Scope doesn't appear to be required when requesting installed roles.
136
+ # @return [Role] requested role.
137
+ #
138
+ def self.load(nsc, name, scope = Scope::SILO)
139
+ xml = %Q(<RoleDetailsRequest session-id="#{nsc.session_id}">)
140
+ xml << %Q(<Role name="#{name}" scope="#{scope}"/>)
141
+ xml << '</RoleDetailsRequest>'
142
+
143
+ response = APIRequest.execute(nsc.url, xml, '1.2')
144
+ if response.success
145
+ elem = REXML::XPath.first(response.res, 'RoleDetailsResponse/Role/')
146
+ parse(elem)
147
+ end
148
+ end
149
+
150
+ alias_method :get, :load
151
+
152
+ # Create or save a Role to the Nexpose console.
153
+ #
154
+ # @param [Connection] nsc Nexpose connection.
155
+ #
156
+ def save(nsc)
157
+ if @existing
158
+ xml = %Q(<RoleUpdateRequest session-id="#{nsc.session_id}">)
159
+ xml << to_xml
160
+ xml << '</RoleUpdateRequest>'
161
+ else
162
+ xml = %Q(<RoleCreateRequest session-id="#{nsc.session_id}">)
163
+ xml << to_xml
164
+ xml << '</RoleCreateRequest>'
165
+ end
166
+
167
+ response = APIRequest.execute(nsc.url, xml, '1.2')
168
+ xml = REXML::XPath.first(response.res, 'RoleCreateResponse')
169
+ @id = xml.attributes['id'].to_i
170
+ @existing = true
171
+ response.success
172
+ end
173
+
174
+ # Copy an existing Role to build a new role off of it.
175
+ # Role will not have a valid name or full_name, so they will need to be provided before saving.
176
+ #
177
+ # @param [Connection] nsc Nexpose connection.
178
+ # @param [String] name The short name of the role which you wish to copy.
179
+ # @param [String] scope Whether the role has global or silo scope. @see Nexpose::Scope
180
+ # @return [Role] requested role.
181
+ #
182
+ def self.copy(nsc, name, scope = Scope::SILO)
183
+ role = load(nsc, name, scope)
184
+ role.name = role.full_name = nil
185
+ role.id = -1
186
+ role.existing = false
187
+ role
188
+ end
189
+
190
+ # Remove this role from the Nexpose console.
191
+ #
192
+ # @param [Connection] nsc Nexpose connection.
193
+ #
194
+ def delete(nsc)
195
+ xml = %Q(<RoleDeleteRequest session-id="#{nsc.session_id}">)
196
+ xml << %Q(<Role name="#{@name}" scope="#{@scope}"/>)
197
+ xml << '</RoleDeleteRequest>'
198
+ response = APIRequest.execute(nsc.url, xml, '1.2')
199
+ response.success
200
+ end
201
+
202
+ def self.parse(xml)
203
+ role = new(xml.attributes['name'],
204
+ xml.attributes['full-name'],
205
+ xml.attributes['id'].to_i,
206
+ xml.attributes['enabled'] == 'true',
207
+ xml.attributes['scope'])
208
+
209
+ role.description = REXML::XPath.first(xml, 'Description').text
210
+ role.existing = true
211
+
212
+ # Only grab enabled privileges.
213
+ xml.elements.each("GlobalPrivileges/child::*[@enabled='true']") do |privilege|
214
+ role.privileges << privilege.name
215
+ end
216
+ xml.elements.each("SitePrivileges/child::*[@enabled='true']") do |privilege|
217
+ role.privileges << privilege.name
218
+ end
219
+ xml.elements.each("AssetGroupPrivileges/child::*[@enabled='true']") do |privilege|
220
+ role.privileges << privilege.name
221
+ end
222
+ role
223
+ end
224
+
225
+ def to_xml
226
+ xml = %Q(<Role name="#{@name}" full-name="#{@full_name}")
227
+ xml << %Q( enabled="#{(enabled ? 'true' : 'false')}")
228
+ xml << %Q( scope="#{@scope}">)
229
+ xml << %Q(<Description>#{@description}</Description>)
230
+
231
+ xml << '<SitePrivileges>'
232
+ Privilege::Site::constants.each do |field|
233
+ as_s = Privilege::Site.const_get(field)
234
+ enabled = (privileges.member? as_s) ? 'true' : 'false'
235
+ xml << %Q(<#{as_s} enabled="#{enabled}"/>)
236
+ end
237
+ xml << '</SitePrivileges>'
238
+
239
+ xml << '<AssetGroupPrivileges>'
240
+ Privilege::AssetGroup::constants.each do |field|
241
+ as_s = Privilege::AssetGroup.const_get(field)
242
+ enabled = (privileges.member? as_s) ? 'true' : 'false'
243
+ xml << %Q(<#{as_s} enabled="#{enabled}"/>)
244
+ end
245
+ xml << '</AssetGroupPrivileges>'
246
+
247
+ xml << '<GlobalPrivileges>'
248
+ Privilege::Global::constants.each do |field|
249
+ as_s = Privilege::Global.const_get(field)
250
+ enabled = (privileges.member? as_s) ? 'true' : 'false'
251
+ xml << %Q(<#{as_s} enabled="#{enabled}"/>)
252
+ end
253
+ xml << '</GlobalPrivileges>'
254
+
255
+ xml << '</Role>'
256
+ xml
257
+ end
258
+ end
27
259
  end
@@ -119,27 +119,27 @@ module Nexpose
119
119
  # @param [REXML::Document] rexml XML document to parse.
120
120
  # @return [ScanSummary] Scan summary represented by the XML.
121
121
  #
122
- def self.parse(rexml)
123
- tasks = Tasks.parse(rexml.elements['tasks'])
124
- nodes = Nodes.parse(rexml.elements['nodes'])
125
- vulns = Vulnerabilities.parse(rexml.attributes['scan-id'], rexml)
126
- msg = rexml.elements['message'] ? rexml.elements['message'].text : nil
122
+ def self.parse(xml)
123
+ tasks = Tasks.parse(xml.elements['tasks'])
124
+ nodes = Nodes.parse(xml.elements['nodes'])
125
+ vulns = Vulnerabilities.parse(xml.attributes['scan-id'], xml)
126
+ msg = xml.elements['message'] ? xml.elements['message'].text : nil
127
127
 
128
128
  # Start time can be empty in some error conditions.
129
129
  start_time = nil
130
- unless rexml.attributes['startTime'] == ''
131
- start_time = DateTime.parse(rexml.attributes['startTime'].to_s).to_time
130
+ unless xml.attributes['startTime'] == ''
131
+ start_time = DateTime.parse(xml.attributes['startTime'].to_s).to_time
132
132
  end
133
133
 
134
134
  # End time is often not present, since reporting on running scans.
135
135
  end_time = nil
136
- if rexml.attributes['endTime']
137
- end_time = DateTime.parse(rexml.attributes['endTime'].to_s).to_time
136
+ if xml.attributes['endTime']
137
+ end_time = DateTime.parse(xml.attributes['endTime'].to_s).to_time
138
138
  end
139
- return ScanSummary.new(rexml.attributes['scan-id'].to_i,
140
- rexml.attributes['site-id'].to_i,
141
- rexml.attributes['engine-id'].to_i,
142
- rexml.attributes['status'],
139
+ return ScanSummary.new(xml.attributes['scan-id'].to_i,
140
+ xml.attributes['site-id'].to_i,
141
+ xml.attributes['engine-id'].to_i,
142
+ xml.attributes['status'],
143
143
  start_time,
144
144
  end_time,
145
145
  msg,
@@ -14,12 +14,12 @@ module Nexpose
14
14
  def site_device_listing(site_id = nil)
15
15
  r = execute(make_xml('SiteDeviceListingRequest', {'site-id' => site_id}))
16
16
 
17
- arr = []
17
+ devices = []
18
18
  if r.success
19
- r.res.elements.each('//SiteDevices') do |site|
19
+ r.res.elements.each('SiteDeviceListingResponse/SiteDevices') do |site|
20
20
  site_id = site.attributes['site-id'].to_i
21
- site.elements.each("//SiteDevices[contains(@site-id,'#{site_id}')]/device") do |device|
22
- arr << Device.new(device.attributes['id'].to_i,
21
+ site.elements.each('device') do |device|
22
+ devices << Device.new(device.attributes['id'].to_i,
23
23
  device.attributes['address'],
24
24
  site_id,
25
25
  device.attributes['riskfactor'].to_f,
@@ -27,7 +27,7 @@ module Nexpose
27
27
  end
28
28
  end
29
29
  end
30
- arr
30
+ devices
31
31
  end
32
32
 
33
33
  alias_method :assets, :site_device_listing
@@ -51,7 +51,7 @@ module Nexpose
51
51
  r = execute(make_xml('SiteListingRequest'))
52
52
  arr = []
53
53
  if (r.success)
54
- r.res.elements.each("//SiteSummary") do |site|
54
+ r.res.elements.each("SiteListingResponse/SiteSummary") do |site|
55
55
  arr << SiteSummary.new(site.attributes['id'].to_i,
56
56
  site.attributes['name'],
57
57
  site.attributes['description'],
@@ -73,13 +73,13 @@ module Nexpose
73
73
  #
74
74
  def site_scan_history(site_id)
75
75
  r = execute(make_xml('SiteScanHistoryRequest', {'site-id' => site_id}))
76
- res = []
76
+ scans = []
77
77
  if r.success
78
- r.res.elements.each("//ScanSummary") do |scan_event|
79
- res << ScanSummary.parse(scan_event)
78
+ r.res.elements.each('SiteScanHistoryResponse/ScanSummary') do |scan_event|
79
+ scans << ScanSummary.parse(scan_event)
80
80
  end
81
81
  end
82
- res
82
+ scans
83
83
  end
84
84
 
85
85
  # Retrieve the scan summary statistics for the latest completed scan
@@ -1,7 +1,72 @@
1
1
  module Nexpose
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
+
2
28
  module NexposeAPI
3
29
  include XMLUtils
4
30
 
31
+ def ticket_listing
32
+ xml = make_xml('TicketListingRequest')
33
+ r = execute(xml, '1.2')
34
+ tickets = []
35
+ if r.success
36
+ r.res.elements.each('TicketListingResponse/TicketSummary') do |summary|
37
+ tickets << TicketSummary::parse(summary)
38
+ end
39
+ end
40
+ tickets
41
+ end
42
+
43
+ alias_method :tickets, :ticket_listing
44
+
45
+ # Deletes a Nexpose ticket.
46
+ #
47
+ # @param [Fixnum] ticket Unique ID of the ticket to delete.
48
+ # @return [Boolean] Whether or not the ticket deletion succeeded.
49
+ #
50
+ def delete_ticket(ticket)
51
+ delete_tickets([ticket])
52
+ end
53
+
54
+ # Deletes a Nexpose ticket.
55
+ #
56
+ # @param [Array[Fixnum]] tickets Array of unique IDs of tickets to delete.
57
+ # @return [Boolean] Whether or not the ticket deletions succeeded.
58
+ #
59
+ def delete_tickets(tickets)
60
+ xml = make_xml('TicketDeleteRequest')
61
+ tickets.each do |id|
62
+ xml.add_element('Ticket', {'id' => id})
63
+ end
64
+
65
+ (execute xml, '1.2').success
66
+ end
67
+
68
+ alias_method :ticket_delete, :delete_tickets
69
+
5
70
  #
6
71
  # Create a Nexpose ticket
7
72
  #
@@ -84,25 +149,54 @@ module Nexpose
84
149
  false
85
150
  end
86
151
  end
152
+ end
87
153
 
88
- #
89
- # Deletes a Nexpose ticket.
90
- #
91
- # ticket_ids: An array of ticket IDs to be deleted.
92
- #
93
- # @returns {@code true} iff the call was successfull. {@code false} otherwise.
94
- #
95
- def delete_ticket ticket_ids
96
- if not ticket_ids or ticket_ids.count < 1
97
- raise ArgumentError.new 'The tickets to delete should not be null or empty'
98
- end
154
+ # Summary of ticket information returned from a ticket listing request.
155
+ # For more details, issue a ticket detail request.
156
+ #
157
+ class TicketSummary
99
158
 
100
- base_xml = make_xml 'TicketDeleteRequest'
101
- ticket_ids.each do |id|
102
- base_xml.add_element 'Ticket', {'id' => id}
103
- end
159
+ # The ID number of the ticket.
160
+ attr_accessor :id
161
+
162
+ # Ticket name.
163
+ attr_accessor :name
164
+
165
+ # The asset the ticket is created for.
166
+ attr_accessor :device_id
167
+
168
+ # The login name of person to whom the ticket is assigned.
169
+ # The user must have view asset privilege on the asset specified in the device-id attribute.
170
+ attr_accessor :assigned_to
171
+
172
+ # The relative priority of the ticket, assigned by the creator of the ticket.
173
+ # @see Nexpose::Ticket::Priority
174
+ attr_accessor :priority
175
+
176
+ # The login name of the person who created the ticket.
177
+ attr_accessor :author
178
+
179
+ # Date and time of ticket creation.
180
+ attr_accessor :created_on
181
+
182
+ # The current status of the ticket.
183
+ attr_accessor :state
184
+
185
+ def initialize(id, name)
186
+ @id, @name = id, name
187
+ end
104
188
 
105
- (execute base_xml, '1.2').success
189
+ def self.parse(xml)
190
+ ticket = new(xml.attributes['id'].to_i,
191
+ xml.attributes['name'])
192
+ ticket.device_id = xml.attributes['device-id'].to_i
193
+ ticket.assigned_to = xml.attributes['assigned-to']
194
+ ticket.priority = xml.attributes['priority']
195
+ ticket.author = xml.attributes['author']
196
+ ticket.created_on = DateTime::parse(xml.attributes['created-on'])
197
+ lookup = Ticket::State.constants.reduce({}) { |a, e| a[Ticket::State.const_get(e)] = e; a }
198
+ ticket.state = lookup[xml.attributes['state']]
199
+ ticket
106
200
  end
107
201
  end
108
202
  end
@@ -1,4 +1,169 @@
1
1
  module Nexpose
2
+ module NexposeAPI
3
+ include XMLUtils
4
+
5
+ # Retrieve summary details of all vulnerabilities.
6
+ #
7
+ # @param [Boolean] full Whether or not to gather the full summary.
8
+ # Without the flag, only id, title, and severity are returned.
9
+ # It can take twice a long to retrieve full summary information.
10
+ # @return [Array[Vulnerability|VulnerabilitySummary]] Collection of all known vulnerabilities.
11
+ #
12
+ def vuln_listing(full = false)
13
+ xml = make_xml('VulnerabilityListingRequest')
14
+ # TODO Add a flag to do stream parsing of the XML to improve performance.
15
+ response = execute(xml, '1.2')
16
+ vulns = []
17
+ if response.success
18
+ response.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |vuln|
19
+ if full
20
+ vulns << VulnerabilitySummary::parse(vuln)
21
+ else
22
+ vulns << Vulnerability.new(vuln.attributes['id'],
23
+ vuln.attributes['title'],
24
+ vuln.attributes['severity'].to_i)
25
+ end
26
+ end
27
+ end
28
+ vulns
29
+ end
30
+
31
+ alias_method :vulns, :vuln_listing
32
+
33
+ # Retrieve details for a vulnerability.
34
+ #
35
+ # @param [String] vuln_id Nexpose vulnerability ID, such as 'windows-duqu-cve-2011-3402'.
36
+ # @return [VulnerabilityDetail] Details of the requested vulnerability.
37
+ #
38
+ def vuln_details(vuln_id)
39
+ xml = make_xml('VulnerabilityDetailsRequest', {'vuln-id' => vuln_id})
40
+ response = execute(xml, '1.2')
41
+ if response.success
42
+ response.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |vuln|
43
+ return VulnerabilityDetail::parse(vuln)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # Basic vulnerability information. Only includes id, title, and severity.
50
+ #
51
+ class Vulnerability
52
+
53
+ # The unique ID string for this vulnerability
54
+ attr_reader :id
55
+
56
+ # The title of this vulnerability
57
+ attr_reader :title
58
+
59
+ # How critical the vulnerability is on a scale of 1 to 10.
60
+ attr_reader :severity
61
+
62
+ def initialize(id, title, severity)
63
+ @id, @title, @severity = id, title, severity
64
+ end
65
+ end
66
+
67
+ # Summary of a vulnerability.
68
+ #
69
+ class VulnerabilitySummary < Vulnerability
70
+
71
+ # PCI severity value for the vulnerability on a scale of 1 to 5.
72
+ attr_accessor :pci_severity
73
+
74
+ # Whether all checks for the vulnerability are safe.
75
+ # Unsafe checks may cause denial of service or otherwise disrupt system performance.
76
+ attr_accessor :safe
77
+
78
+ # A vulnerability is considered “credentialed” when all of its checks
79
+ # require credentials or if the check depends on previous authentication
80
+ # during a scan.
81
+ attr_accessor :credentials
82
+
83
+ # When this vulnerability was first included in the application.
84
+ attr_accessor :added
85
+
86
+ # The last date the vulnerability was modified.
87
+ attr_accessor :modified
88
+
89
+ # The date when the information about the vulnerability was first released.
90
+ attr_accessor :published
91
+
92
+ # How the vulnerability is exploited according to PCI standards.
93
+ attr_accessor :cvss_vector
94
+
95
+ # The computation of the Common Vulnerability Scoring System indicating
96
+ # compliance with PCI standards on a scale from 0 to 10.0.
97
+ attr_accessor :cvss_score
98
+
99
+ def self.parse_attributes(xml)
100
+ vuln = new(xml.attributes['id'],
101
+ xml.attributes['title'],
102
+ xml.attributes['severity'].to_i)
103
+
104
+ vuln.pci_severity = xml.attributes['pciSeverity'].to_i
105
+ 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'])
108
+ vuln.credentials = xml.attributes['requiresCredentials'] == 'true'
109
+
110
+ # These three fields are optional in the XSD.
111
+ vuln.published = Date::parse(xml.attributes['published']) if xml.attributes['published']
112
+ vuln.cvss_vector = xml.attributes['cvssVector'] if xml.attributes['cvssVector']
113
+ vuln.cvss_score = xml.attributes['cvssScore'].to_f if xml.attributes['cvssScore']
114
+ vuln
115
+ end
116
+
117
+ def self.parse(xml)
118
+ parse_attributes(xml)
119
+ end
120
+ end
121
+
122
+ # Details for a vulnerability.
123
+ #
124
+ class VulnerabilityDetail < VulnerabilitySummary
125
+
126
+ # The HTML Description of this vulnerability.
127
+ attr_accessor :description
128
+
129
+ # External References for this vulnerability.
130
+ # Array containing (Reference)
131
+ attr_accessor :references
132
+
133
+ # The HTML Solution for this vulnerability.
134
+ attr_accessor :solution
135
+
136
+ def initialize(id, title, severity)
137
+ @id, @title, @severity = id, title, severity
138
+ @references = []
139
+ end
140
+
141
+ def self.parse(xml)
142
+ vuln = parse_attributes(xml)
143
+
144
+ vuln.description = REXML::XPath.first(xml, 'description').text
145
+ vuln.solution = REXML::XPath.first(xml, 'solution').text
146
+
147
+ xml.elements.each('references/reference') do |ref|
148
+ vuln.references << Reference.new(ref.attributes['source'], ref.text)
149
+ end
150
+ vuln
151
+ end
152
+ end
153
+
154
+ # Reference information for a Vulnerability.
155
+ #
156
+ class Reference
157
+
158
+ attr_reader :source
159
+ attr_reader :reference
160
+
161
+ def initialize(source, reference)
162
+ @source = source
163
+ @reference = reference
164
+ end
165
+ end
166
+
2
167
  module NexposeAPI
3
168
  include XMLUtils
4
169
 
@@ -13,7 +178,7 @@ module Nexpose
13
178
  # @param status - (optional) The status of the vulnerability exception:
14
179
  # "Under Review", "Approved", "Rejected"
15
180
  #-----------------------------------------------------------------------
16
- def vuln_listing(status = nil)
181
+ def vuln_exception_listing(status = nil)
17
182
  option = {}
18
183
 
19
184
  if status && !status.empty?
@@ -328,182 +493,4 @@ module Nexpose
328
493
  r.success
329
494
  end
330
495
  end
331
-
332
- # === Description
333
- # Object that represents a listing of all of the vulnerabilities in the vulnerability database
334
- #
335
- class VulnerabilityListing
336
- # true if an error condition exists; false otherwise
337
- attr_reader :error
338
- # Error message string
339
- attr_reader :error_msg
340
- # The NSC Connection associated with this object
341
- attr_reader :connection
342
- # Array containing (VulnerabilitySummary*)
343
- attr_reader :vulnerability_summaries
344
- # The number of vulnerability definitions
345
- attr_reader :vulnerability_count
346
-
347
- # Constructor
348
- # VulnerabilityListing(connection)
349
- def initialize(connection)
350
- @error = false
351
- @vulnerability_summaries = []
352
- @connection = connection
353
-
354
- r = @connection.execute('<VulnerabilityListingRequest session-id="' + @connection.session_id + '"/>')
355
-
356
- if r.success
357
- r.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |v|
358
- @vulnerability_summaries.push(VulnerabilitySummary.new(v.attributes['id'], v.attributes['title'], v.attributes['severity']))
359
- end
360
- else
361
- @error = true
362
- @error_msg = 'VulnerabilitySummary Parse Error'
363
- end
364
- @vulnerability_count = @vulnerability_summaries.length
365
- end
366
- end
367
-
368
- # === Description
369
- # Object that represents the summary of an entry in the vulnerability database
370
- #
371
- class VulnerabilitySummary
372
-
373
- # The unique ID string for this vulnerability
374
- attr_reader :id
375
- # The title of this vulnerability
376
- attr_reader :title
377
- # The severity of this vulnerability (1 – 10)
378
- attr_reader :severity
379
-
380
- # Constructor
381
- # VulnerabilitySummary(id, title, severity)
382
- def initialize(id, title, severity)
383
- @id = id
384
- @title = title
385
- @severity = severity
386
-
387
- end
388
-
389
- end
390
-
391
- # === Description
392
- # Object that represents the details for an entry in the vulnerability database
393
- #
394
- class VulnerabilityDetail
395
- # true if an error condition exists; false otherwise
396
- attr_reader :error
397
- # Error message string
398
- attr_reader :error_msg
399
- # The NSC Connection associated with this object
400
- attr_reader :connection
401
- # The unique ID string for this vulnerability
402
- attr_reader :id
403
- # The title of this vulnerability
404
- attr_reader :title
405
- # The severity of this vulnerability (1 – 10)
406
- attr_reader :severity
407
- # The pciSeverity of this vulnerability
408
- attr_reader :pciSeverity
409
- # The CVSS score of this vulnerability
410
- attr_reader :cvssScore
411
- # The CVSS vector of this vulnerability
412
- attr_reader :cvssVector
413
- # The date this vulnerability was published
414
- attr_reader :published
415
- # The date this vulnerability was added to Nexpose
416
- attr_reader :added
417
- # The last date this vulnerability was modified
418
- attr_reader :modified
419
- # The HTML Description of this vulnerability
420
- attr_reader :description
421
- # External References for this vulnerability
422
- # Array containing (Reference)
423
- attr_reader :references
424
- # The HTML Solution for this vulnerability
425
- attr_reader :solution
426
-
427
- # Constructor
428
- # VulnerabilityListing(connection,id)
429
- def initialize(connection, id)
430
-
431
- @error = false
432
- @connection = connection
433
- @id = id
434
- @references = []
435
-
436
- r = @connection.execute('<VulnerabilityDetailsRequest session-id="' + @connection.session_id + '" vuln-id="' + @id + '"/>')
437
-
438
- if r.success
439
- r.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |v|
440
- @id = v.attributes['id']
441
- @title = v.attributes['title']
442
- @severity = v.attributes['severity']
443
- @pciSeverity = v.attributes['pciSeverity']
444
- @cvssScore = v.attributes['cvssScore']
445
- @cvssVector = v.attributes['cvssVector']
446
- @published = v.attributes['published']
447
- @added = v.attributes['added']
448
- @modified = v.attributes['modified']
449
-
450
- v.elements.each('description') do |desc|
451
- @description = desc.to_s.gsub(/\<\/?description\>/i, '')
452
- end
453
-
454
- v.elements.each('solution') do |sol|
455
- @solution = sol.to_s.gsub(/\<\/?solution\>/i, '')
456
- end
457
-
458
- v.elements.each('references/reference') do |ref|
459
- @references.push(Reference.new(ref.attributes['source'], ref.text))
460
- end
461
- end
462
- else
463
- @error = true
464
- @error_msg = 'VulnerabilitySummary Parse Error'
465
- end
466
-
467
- end
468
- end
469
-
470
- # === Description
471
- #
472
- class Reference
473
-
474
- attr_reader :source
475
- attr_reader :reference
476
-
477
- def initialize(source, reference)
478
- @source = source
479
- @reference = reference
480
- end
481
- end
482
-
483
- # TODO: review
484
- # === Description
485
- #
486
- class VulnFilter
487
-
488
- attr_reader :typeMask
489
- attr_reader :maxAlerts
490
- attr_reader :severityThreshold
491
-
492
- def initialize(type_mask, severity_threshold, max_alerts = -1)
493
- @typeMask = type_mask
494
- @maxAlerts = max_alerts
495
- @severityThreshold = severity_threshold
496
- end
497
-
498
- include Sanitize
499
-
500
- def to_xml
501
- xml = '<vuln_filter '
502
- xml << %Q{ type_mask="#{replace_entities(typeMask)}"}
503
- xml << %Q{ maxAlerts="#{replace_entities(maxAlerts)}"}
504
- xml << %Q{ severity_threshold="#{replace_entities(severityThreshold)}"}
505
- xml << '/>'
506
- xml
507
- end
508
- end
509
496
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexpose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-04-22 00:00:00.000000000 Z
14
+ date: 2013-05-06 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: librex
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ! '>='
22
22
  - !ruby/object:Gem::Version
23
- version: 0.0.32
23
+ version: 0.0.68
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,7 +28,7 @@ dependencies:
28
28
  requirements:
29
29
  - - ! '>='
30
30
  - !ruby/object:Gem::Version
31
- version: 0.0.32
31
+ version: 0.0.68
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: rex
34
34
  requirement: !ruby/object:Gem::Requirement