nexpose 0.1.14 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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