nexpose_ticketing 0.0.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/{README.markdown → README.md} +43 -38
- data/bin/nexpose_jira +2 -3
- data/bin/nexpose_remedy +17 -0
- data/bin/nexpose_servicenow +17 -0
- data/lib/nexpose_ticketing.rb +3 -1
- data/lib/nexpose_ticketing/config/remedy.config +26 -0
- data/lib/nexpose_ticketing/config/remedy_wsdl/HPD_IncidentInterface_Create_WS.xml +296 -0
- data/lib/nexpose_ticketing/config/remedy_wsdl/HPD_IncidentInterface_WS.xml +675 -0
- data/lib/nexpose_ticketing/config/servicenow.config +18 -0
- data/lib/nexpose_ticketing/config/ticket_service.config +2 -2
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +6 -6
- data/lib/nexpose_ticketing/helpers/remedy_helper.rb +448 -0
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +315 -0
- data/lib/nexpose_ticketing/nx_logger.rb +35 -0
- data/lib/nexpose_ticketing/queries.rb +105 -14
- data/lib/nexpose_ticketing/ticket_repository.rb +57 -12
- data/lib/nexpose_ticketing/ticket_service.rb +56 -19
- data/nexpose_ticketing.gemspec +5 -4
- metadata +29 -4
@@ -0,0 +1,315 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'uri'
|
5
|
+
require 'csv'
|
6
|
+
require 'nexpose_ticketing/nx_logger'
|
7
|
+
|
8
|
+
# Serves as the ServiceNow interface for creating/updating issues from
|
9
|
+
# vulnelrabilities found in Nexpose.
|
10
|
+
class ServiceNowHelper
|
11
|
+
attr_accessor :servicenow_data, :options, :log
|
12
|
+
def initialize(servicenow_data, options)
|
13
|
+
@servicenow_data = servicenow_data
|
14
|
+
@options = options
|
15
|
+
@log = NexposeTicketing::NXLogger.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sends a list of tickets (in JSON format) to ServiceNow individually (each ticket in the list
|
19
|
+
# as a separate HTTP post).
|
20
|
+
#
|
21
|
+
# * *Args* :
|
22
|
+
# - +tickets+ - List of JSON-formatted ticket creates (new tickets).
|
23
|
+
#
|
24
|
+
def create_tickets(tickets)
|
25
|
+
fail 'Ticket(s) cannot be empty' if tickets.nil? || tickets.empty?
|
26
|
+
|
27
|
+
tickets.each do |ticket|
|
28
|
+
send_ticket(ticket, @servicenow_data[:servicenow_url], @servicenow_data[:redirect_limit])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sends ticket updates (in JSON format) to ServiceNow individually (each ticket in the list as a
|
33
|
+
# separate HTTP post).
|
34
|
+
#
|
35
|
+
# * *Args* :
|
36
|
+
# - +tickets+ - List of JSON-formatted ticket updates.
|
37
|
+
#
|
38
|
+
def update_tickets(tickets)
|
39
|
+
if tickets.nil? || tickets.empty?
|
40
|
+
@log.log_message("No tickets to update.")
|
41
|
+
else
|
42
|
+
tickets.each do |ticket|
|
43
|
+
send_ticket(ticket, @servicenow_data[:servicenow_url], @servicenow_data[:redirect_limit])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sends ticket closure (in JSON format) to ServiceNow individually (each ticket in the list as a
|
49
|
+
# separate HTTP post).
|
50
|
+
#
|
51
|
+
# * *Args* :
|
52
|
+
# - +tickets+ - List of JSON-formatted ticket closures.
|
53
|
+
#
|
54
|
+
def close_tickets(tickets)
|
55
|
+
if tickets.nil? || tickets.empty?
|
56
|
+
@log.log_message("No tickets to close.")
|
57
|
+
else
|
58
|
+
tickets.each do |ticket|
|
59
|
+
send_ticket(ticket, @servicenow_data[:servicenow_url], @servicenow_data[:redirect_limit])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Post an individual JSON-formatted ticket to ServiceNow. If the response from the post is a 301/
|
65
|
+
# 302 redirect, the method will attempt to resend the ticket to the response's location for up to
|
66
|
+
# [limit] times (which starts at the redirect_limit config value and is decremented with each
|
67
|
+
# redirect response.
|
68
|
+
#
|
69
|
+
# * *Args* :
|
70
|
+
# - +ticket+ - JSON-formatted ticket.
|
71
|
+
# - +url+ - URL to post the ticket to.
|
72
|
+
# - +limit+ - The amount of times to retry the send ticket request before failing.l
|
73
|
+
#
|
74
|
+
def send_ticket(ticket, url, limit)
|
75
|
+
raise ArgumentError, 'HTTP Redirect too deep' if limit == 0
|
76
|
+
|
77
|
+
uri = URI.parse(url)
|
78
|
+
headers = { 'Content-Type' => 'application/json',
|
79
|
+
'Accept' => 'application/json' }
|
80
|
+
req = Net::HTTP::Post.new(url, headers)
|
81
|
+
req.basic_auth @servicenow_data[:username], @servicenow_data[:password]
|
82
|
+
req.body = ticket
|
83
|
+
|
84
|
+
resp = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
# Setting verbose_mode to 'Y' will debug the https call(s).
|
86
|
+
resp.set_debug_output $stderr if @servicenow_data[:verbose_mode] == 'Y'
|
87
|
+
resp.use_ssl = true if uri.scheme == 'https'
|
88
|
+
# Currently, we do not verify SSL certificates (in case the local servicenow instance uses
|
89
|
+
# and unsigned or expired certificate)
|
90
|
+
resp.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
91
|
+
res = resp.start { |http| http.request(req) }
|
92
|
+
case res
|
93
|
+
when Net::HTTPSuccess then res
|
94
|
+
when Net::HTTPRedirection then send_ticket(ticket, res['location'], limit - 1)
|
95
|
+
else
|
96
|
+
@log.log_message("Error in response: #{res['error']}")
|
97
|
+
res['error']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Prepare tickets from the CSV of vulnerabilities exported from Nexpose. This method determines
|
102
|
+
# how to prepare the tickets (either by default or by IP address) based on config options.
|
103
|
+
#
|
104
|
+
# * *Args* :
|
105
|
+
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
106
|
+
#
|
107
|
+
# * *Returns* :
|
108
|
+
# - List of JSON-formated tickets for creating within ServiceNow.
|
109
|
+
#
|
110
|
+
def prepare_create_tickets(vulnerability_list)
|
111
|
+
@ticket = Hash.new(-1)
|
112
|
+
case @options[:ticket_mode]
|
113
|
+
# 'D' Default mode: IP *-* Vulnerability
|
114
|
+
when 'D'
|
115
|
+
prepare_create_tickets_default(vulnerability_list)
|
116
|
+
# 'I' IP address mode: IP address -* Vulnerability
|
117
|
+
when 'I'
|
118
|
+
prepare_create_tickets_by_ip(vulnerability_list)
|
119
|
+
else
|
120
|
+
fail 'No ticketing mode selected.'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Prepares a list of vulnerabilities into a list of JSON-formatted tickets (incidents) for
|
125
|
+
# ServiceNow. The preparation by default means that each vulnerability within Nexpose is a
|
126
|
+
# separate incident within ServiceNow. This makes for smaller, more actionalble incidents but
|
127
|
+
# could lead to a very large total number of incidents.
|
128
|
+
#
|
129
|
+
# * *Args* :
|
130
|
+
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
131
|
+
#
|
132
|
+
# * *Returns* :
|
133
|
+
# - List of JSON-formated tickets for creating within ServiceNow.
|
134
|
+
#
|
135
|
+
def prepare_create_tickets_default(vulnerability_list)
|
136
|
+
@log.log_message("Preparing tickets by default method.")
|
137
|
+
tickets = []
|
138
|
+
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
139
|
+
# ServiceNow doesn't allow new line characters in the incident short description.
|
140
|
+
summary = row['summary'].gsub(/\n/, ' ')
|
141
|
+
|
142
|
+
@log.log_message("Creating ticket with IP address: #{row['ip_address']} and summary: #{summary}")
|
143
|
+
# NXID in the work_notes is a unique identifier used to query incidents to update/resolve
|
144
|
+
# incidents as they are resolved in Nexpose.
|
145
|
+
ticket = {
|
146
|
+
'sysparm_action' => 'insert',
|
147
|
+
'caller_id' => "#{@servicenow_data[:username]}",
|
148
|
+
'category' => 'Software',
|
149
|
+
'impact' => '1',
|
150
|
+
'urgency' => '1',
|
151
|
+
'short_description' => "#{row['ip_address']} => #{summary}",
|
152
|
+
'work_notes' => "Summary: #{summary}
|
153
|
+
Fix: #{row['fix']}
|
154
|
+
----------------------------------------------------------------------------
|
155
|
+
URL: #{row['url']}
|
156
|
+
NXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
|
157
|
+
}.to_json
|
158
|
+
tickets.push(ticket)
|
159
|
+
end
|
160
|
+
tickets
|
161
|
+
end
|
162
|
+
|
163
|
+
# Prepares a list of vulnerabilities into a list of JSON-formatted tickets (incidents) for
|
164
|
+
# ServiceNow. The preparation by IP means that all vulnerabilities within Nexpose for one IP
|
165
|
+
# address are consolidated into a single ServiceNow incident. This reduces the number of incidents
|
166
|
+
# within ServiceNow but greatly increases the size of the work notes.
|
167
|
+
#
|
168
|
+
# * *Args* :
|
169
|
+
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
170
|
+
#
|
171
|
+
# * *Returns* :
|
172
|
+
# - List of JSON-formated tickets for creating within ServiceNow.
|
173
|
+
#
|
174
|
+
def prepare_create_tickets_by_ip(vulnerability_list)
|
175
|
+
@log.log_message("Preparing tickets by IP address.")
|
176
|
+
tickets = []
|
177
|
+
current_ip = -1
|
178
|
+
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
179
|
+
if current_ip == -1
|
180
|
+
current_ip = row['ip_address']
|
181
|
+
@log.log_message("Creating ticket with IP address: #{row['ip_address']}")
|
182
|
+
@ticket = {
|
183
|
+
'sysparm_action' => 'insert',
|
184
|
+
'caller_id' => "#{@servicenow_data[:username]}",
|
185
|
+
'category' => 'Software',
|
186
|
+
'impact' => '1',
|
187
|
+
'urgency' => '1',
|
188
|
+
'short_description' => "#{row['ip_address']} => Vulnerabilities",
|
189
|
+
'work_notes' => "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
190
|
+
++ New Vulnerabilities +++++++++++++++++++++++++++++++++++++
|
191
|
+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"
|
192
|
+
}
|
193
|
+
end
|
194
|
+
if current_ip == row['ip_address']
|
195
|
+
@ticket['work_notes'] +=
|
196
|
+
"==========================================
|
197
|
+
Summary: #{row['summary']}
|
198
|
+
----------------------------------------------------------------------------
|
199
|
+
Fix: #{row['fix']}"
|
200
|
+
unless row['url'].nil?
|
201
|
+
@ticket['work_notes'] +=
|
202
|
+
"----------------------------------------------------------------------------
|
203
|
+
URL: #{row['url']}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
unless current_ip == row['ip_address']
|
207
|
+
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
208
|
+
@ticket['work_notes'] += "\nNXID: #{current_ip}"
|
209
|
+
@ticket = @ticket.to_json
|
210
|
+
tickets.push(@ticket)
|
211
|
+
current_ip = -1
|
212
|
+
redo
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
216
|
+
@ticket['work_notes'] += "\nNXID: #{current_ip}"
|
217
|
+
tickets.push(@ticket.to_json) unless @ticket.nil?
|
218
|
+
tickets
|
219
|
+
end
|
220
|
+
|
221
|
+
# Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method
|
222
|
+
# currently only supports updating IP-address mode tickets in ServiceNow. The list of vulnerabilities
|
223
|
+
# are ordered by IP address and then by ticket_status, allowing the method to loop through and
|
224
|
+
# display new, old, and same vulnerabilities in that order.
|
225
|
+
#
|
226
|
+
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
227
|
+
#
|
228
|
+
# * *Returns* :
|
229
|
+
# - List of JSON-formated tickets for updating within ServiceNow.
|
230
|
+
#
|
231
|
+
def prepare_update_tickets(vulnerability_list)
|
232
|
+
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] == 'D'
|
233
|
+
@ticket = Hash.new(-1)
|
234
|
+
|
235
|
+
@log.log_message("Preparing ticket updates by IP address.")
|
236
|
+
tickets = []
|
237
|
+
current_ip = -1
|
238
|
+
ticket_status = 'New'
|
239
|
+
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
240
|
+
if current_ip == -1
|
241
|
+
current_ip = row['ip_address']
|
242
|
+
ticket_status = row['comparison']
|
243
|
+
@log.log_message("Creating ticket update with IP address: #{row['ip_address']}")
|
244
|
+
@log.log_message("Ticket status #{ticket_status}")
|
245
|
+
@ticket = {
|
246
|
+
'sysparm_action' => 'update',
|
247
|
+
'sysparm_query' => "work_notesCONTAINSNXID: #{row['ip_address']}",
|
248
|
+
'work_notes' =>
|
249
|
+
"\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
250
|
+
++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++
|
251
|
+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"
|
252
|
+
}
|
253
|
+
end
|
254
|
+
if current_ip == row['ip_address']
|
255
|
+
# If the ticket_status is different, add a a new 'header' to signify a new block of tickets.
|
256
|
+
unless ticket_status == row['comparison']
|
257
|
+
@ticket['work_notes'] +=
|
258
|
+
"\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
259
|
+
++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++
|
260
|
+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"
|
261
|
+
ticket_status = row['comparison']
|
262
|
+
end
|
263
|
+
|
264
|
+
@ticket['work_notes'] +=
|
265
|
+
"\n\n==========================================
|
266
|
+
Summary: #{row['summary']}
|
267
|
+
----------------------------------------------------------------------------
|
268
|
+
Fix: #{row['fix']}"
|
269
|
+
# Only add the URL block if data exists in the row.
|
270
|
+
unless row['url'].nil?
|
271
|
+
@ticket['work_notes'] +=
|
272
|
+
"----------------------------------------------------------------------------
|
273
|
+
URL: #{row['url']}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
unless current_ip == row['ip_address']
|
277
|
+
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
278
|
+
@ticket['work_notes'] += "\nNXID: #{current_ip}"
|
279
|
+
@ticket = @ticket.to_json
|
280
|
+
tickets.push(@ticket)
|
281
|
+
current_ip = -1
|
282
|
+
redo
|
283
|
+
end
|
284
|
+
end
|
285
|
+
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
286
|
+
@ticket['work_notes'] += "\nNXID: #{current_ip}"
|
287
|
+
tickets.push(@ticket.to_json) unless @ticket.nil?
|
288
|
+
tickets
|
289
|
+
end
|
290
|
+
|
291
|
+
# Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose. This method
|
292
|
+
# currently only supports updating default mode tickets in ServiceNow.
|
293
|
+
#
|
294
|
+
# * *Args* :
|
295
|
+
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
296
|
+
#
|
297
|
+
# * *Returns* :
|
298
|
+
# - List of JSON-formated tickets for closing within ServiceNow.
|
299
|
+
#
|
300
|
+
def prepare_close_tickets(vulnerability_list)
|
301
|
+
fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
|
302
|
+
@log.log_message("Preparing ticket closures by default method.")
|
303
|
+
tickets = []
|
304
|
+
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
305
|
+
# 'state' 7 is the "Closed" state within ServiceNow.
|
306
|
+
ticket = {
|
307
|
+
'sysparm_action' => 'update',
|
308
|
+
'sysparm_query' => "work_notesCONTAINSNXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}",
|
309
|
+
'state' => '7'
|
310
|
+
}.to_json
|
311
|
+
tickets.push(ticket)
|
312
|
+
end
|
313
|
+
tickets
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module NexposeTicketing
|
2
|
+
class NXLogger
|
3
|
+
TICKET_SERVICE_CONFIG_PATH = File.join(File.dirname(__FILE__), '/config/ticket_service.config')
|
4
|
+
LOGGER_FILE = File.join(File.dirname(__FILE__), '/log/ticket_helper.log')
|
5
|
+
|
6
|
+
attr_accessor :options
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
service_data = begin
|
10
|
+
YAML.load_file(TICKET_SERVICE_CONFIG_PATH)
|
11
|
+
rescue ArgumentError => e
|
12
|
+
raise "Could not parse YAML #{TICKET_SERVICE_CONFIG_PATH} : #{e.message}"
|
13
|
+
end
|
14
|
+
|
15
|
+
@options = service_data[:options]
|
16
|
+
setup_logging(@options[:logging_enabled])
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_logging(enabled = false)
|
20
|
+
if enabled
|
21
|
+
require 'logger'
|
22
|
+
directory = File.dirname(LOGGER_FILE)
|
23
|
+
FileUtils.mkdir_p(directory) unless File.directory?(directory)
|
24
|
+
@log = Logger.new(LOGGER_FILE, 'monthly')
|
25
|
+
@log.level = Logger::INFO
|
26
|
+
log_message('Logging enabled for helper.')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Logs a message if logging is enabled.
|
31
|
+
def log_message(message)
|
32
|
+
@log.info(message) if @options[:logging_enabled]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -6,18 +6,21 @@ module NexposeTicketing
|
|
6
6
|
module Queries
|
7
7
|
# Gets all the latests scans.
|
8
8
|
# Returns |site.id| |last_scan_id| |finished|
|
9
|
-
def
|
9
|
+
def self.last_scans
|
10
10
|
'SELECT ds.site_id, ds.last_scan_id, dsc.finished
|
11
11
|
FROM dim_site ds
|
12
12
|
JOIN dim_scan dsc ON ds.last_scan_id = dsc.scan_id'
|
13
13
|
end
|
14
14
|
|
15
15
|
# Gets all delta vulns for all sites.
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
#
|
17
|
+
# * *Returns* :
|
18
|
+
# -Returns |asset_id| |ip_address| |current_scan| |vulnerability_id||solution_id| |nexpose_id|
|
19
|
+
# |url| |summary| |fix|
|
20
|
+
#
|
21
|
+
def self.all_new_vulns
|
22
|
+
"SELECT subs.asset_id, da.ip_address, subs.current_scan, subs.vulnerability_id, davs.solution_id, ds.nexpose_id,
|
23
|
+
ds.url,proofAsText(ds.summary) as summary, proofAsText(ds.fix) as fix
|
21
24
|
FROM (SELECT fasv.asset_id, fasv.vulnerability_id, s.current_scan
|
22
25
|
FROM fact_asset_scan_vulnerability_finding fasv
|
23
26
|
JOIN
|
@@ -33,20 +36,26 @@ module NexposeTicketing
|
|
33
36
|
JOIN dim_asset da ON subs.asset_id = da.asset_id
|
34
37
|
ORDER BY da.ip_address"
|
35
38
|
end
|
36
|
-
|
37
|
-
# Gets all
|
38
|
-
#
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
|
40
|
+
# Gets all new vulnerabilities happening after a reported scan id.
|
41
|
+
#
|
42
|
+
# * *Args* :
|
43
|
+
# - +reported_scan+ - Last reported scan id.
|
44
|
+
#
|
45
|
+
# * *Returns* :
|
46
|
+
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
47
|
+
# |url| |summary| |fix|
|
48
|
+
#
|
49
|
+
def self.new_vulns_since_scan(reported_scan)
|
50
|
+
"SELECT subs.asset_id, da.ip_address, subs.current_scan, subs.vulnerability_id, davs.solution_id, ds.nexpose_id,
|
51
|
+
ds.url, proofAsText(ds.summary) as summary, proofAsText(ds.fix) as fix
|
43
52
|
FROM (SELECT fasv.asset_id, fasv.vulnerability_id, s.current_scan
|
44
53
|
FROM fact_asset_scan_vulnerability_finding fasv
|
45
54
|
JOIN
|
46
55
|
(
|
47
56
|
SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
|
48
57
|
FROM dim_asset) s
|
49
|
-
ON s.asset_id = fasv.asset_id AND (fasv.scan_id
|
58
|
+
ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{reported_scan} OR fasv.scan_id = s.current_scan)
|
50
59
|
GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
|
51
60
|
HAVING baselineComparison(fasv.scan_id, current_scan) = 'New'
|
52
61
|
) subs
|
@@ -56,5 +65,87 @@ module NexposeTicketing
|
|
56
65
|
AND subs.current_scan > #{reported_scan}
|
57
66
|
ORDER BY da.ip_address"
|
58
67
|
end
|
68
|
+
|
69
|
+
# Gets all old vulnerabilities happening after a reported scan id.
|
70
|
+
#
|
71
|
+
# * *Args* :
|
72
|
+
# - +reported_scan+ - Last reported scan id.
|
73
|
+
#
|
74
|
+
# * *Returns* :
|
75
|
+
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
76
|
+
# |url| |summary| |fix|
|
77
|
+
#
|
78
|
+
def self.old_vulns_since_scan(reported_scan)
|
79
|
+
"SELECT subs.asset_id, da.ip_address, subs.current_scan, subs.vulnerability_id, dvs.solution_id, ds.nexpose_id, ds.url,
|
80
|
+
proofAsText(ds.summary) as summary, proofAsText(ds.fix) as fix, subs.comparison
|
81
|
+
FROM (
|
82
|
+
SELECT fasv.asset_id, fasv.vulnerability_id, s.current_scan, baselineComparison(fasv.scan_id, s.current_scan) as comparison
|
83
|
+
FROM fact_asset_scan_vulnerability_finding fasv
|
84
|
+
JOIN (
|
85
|
+
SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
|
86
|
+
FROM dim_asset
|
87
|
+
) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{reported_scan} OR fasv.scan_id = s.current_scan)
|
88
|
+
GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
|
89
|
+
HAVING baselineComparison(fasv.scan_id, current_scan) = 'Old'
|
90
|
+
) subs
|
91
|
+
JOIN dim_vulnerability_solution dvs USING (vulnerability_id)
|
92
|
+
JOIN dim_solution ds USING (solution_id)
|
93
|
+
JOIN dim_asset da ON subs.asset_id = da.asset_id
|
94
|
+
AND subs.current_scan > #{reported_scan}
|
95
|
+
ORDER BY da.ip_address"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Gets all vulnerabilities happening after a reported scan id. This result set also includes the
|
99
|
+
# baseline comparision ("Old", "New", or "Same") allowing for IP-based ticket updating.
|
100
|
+
#
|
101
|
+
# * *Args* :
|
102
|
+
# - +reported_scan+ - Last reported scan id.
|
103
|
+
#
|
104
|
+
# * *Returns* :
|
105
|
+
# - Returns |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
|
106
|
+
# |url| |summary| |fix| |comparison|
|
107
|
+
#
|
108
|
+
def self.all_vulns_since_scan(reported_scan)
|
109
|
+
"SELECT subs.asset_id, da.ip_address, subs.current_scan, subs.vulnerability_id, dvs.solution_id, ds.nexpose_id, ds.url,
|
110
|
+
proofAsText(ds.summary) as summary, proofAsText(ds.fix) as fix, subs.comparison
|
111
|
+
FROM (
|
112
|
+
SELECT fasv.asset_id, fasv.vulnerability_id, s.current_scan, baselineComparison(fasv.scan_id, s.current_scan) as comparison
|
113
|
+
FROM fact_asset_scan_vulnerability_finding fasv
|
114
|
+
JOIN (
|
115
|
+
SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
|
116
|
+
FROM dim_asset
|
117
|
+
) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{reported_scan} OR fasv.scan_id = s.current_scan)
|
118
|
+
GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
|
119
|
+
HAVING baselineComparison(fasv.scan_id, current_scan) = 'Old'
|
120
|
+
) subs
|
121
|
+
JOIN dim_vulnerability_solution dvs USING (vulnerability_id)
|
122
|
+
JOIN dim_solution ds USING (solution_id)
|
123
|
+
JOIN dim_asset da ON subs.asset_id = da.asset_id
|
124
|
+
AND subs.current_scan > #{reported_scan}
|
125
|
+
|
126
|
+
UNION
|
127
|
+
|
128
|
+
SELECT subs.asset_id, da.ip_address, subs.current_scan, subs.vulnerability_id, davs.solution_id, ds.nexpose_id, ds.url,
|
129
|
+
proofAsText(ds.summary) as summary, proofAsText(ds.fix) as fix, subs.comparison
|
130
|
+
FROM
|
131
|
+
(
|
132
|
+
SELECT fasv.asset_id, fasv.vulnerability_id, s.current_scan, baselineComparison(fasv.scan_id, s.current_scan) as comparison
|
133
|
+
FROM fact_asset_scan_vulnerability_finding fasv
|
134
|
+
JOIN
|
135
|
+
(
|
136
|
+
SELECT asset_id,lastScan(asset_id) AS current_scan
|
137
|
+
FROM dim_asset
|
138
|
+
) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{reported_scan} OR fasv.scan_id = s.current_scan)
|
139
|
+
GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
|
140
|
+
HAVING baselineComparison(fasv.scan_id, current_scan) IN ('Same','New')
|
141
|
+
) subs
|
142
|
+
JOIN dim_asset_vulnerability_solution davs USING (vulnerability_id)
|
143
|
+
JOIN dim_solution ds USING (solution_id)
|
144
|
+
JOIN dim_asset da ON subs.asset_id = da.asset_id
|
145
|
+
AND subs.current_scan > #{reported_scan}
|
146
|
+
|
147
|
+
ORDER BY ip_address, comparison"
|
148
|
+
end
|
149
|
+
|
59
150
|
end
|
60
151
|
end
|