nexpose_ticketing 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -107,15 +107,15 @@ class ServiceNowHelper
107
107
  # * *Returns* :
108
108
  # - List of JSON-formated tickets for creating within ServiceNow.
109
109
  #
110
- def prepare_create_tickets(vulnerability_list, site_id)
110
+ def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
111
111
  @ticket = Hash.new(-1)
112
112
  case @options[:ticket_mode]
113
113
  # 'D' Default mode: IP *-* Vulnerability
114
114
  when 'D'
115
- prepare_create_tickets_default(vulnerability_list, site_id)
115
+ prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
116
116
  # 'I' IP address mode: IP address -* Vulnerability
117
117
  when 'I'
118
- prepare_create_tickets_by_ip(vulnerability_list, site_id)
118
+ prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
119
119
  else
120
120
  fail 'No ticketing mode selected.'
121
121
  end
@@ -133,14 +133,14 @@ class ServiceNowHelper
133
133
  # * *Returns* :
134
134
  # - List of JSON-formated tickets for creating within ServiceNow.
135
135
  #
136
- def prepare_create_tickets_default(vulnerability_list, site_id)
136
+ def prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
137
137
  @log.log_message('Preparing tickets by default method.')
138
138
  tickets = []
139
139
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
140
140
  # ServiceNow doesn't allow new line characters in the incident short description.
141
141
  summary = row['summary'].gsub(/\n/, ' ')
142
142
 
143
- @log.log_message("Creating ticket with IP address: #{row['ip_address']}, site id: #{site_id} and summary: #{summary}")
143
+ @log.log_message("Creating ticket with IP address: #{row['ip_address']}, Nexpose identifier id: #{nexpose_identifier_id} and summary: #{summary}")
144
144
  # NXID in the u_work_notes is a unique identifier used to query incidents to update/resolve
145
145
  # incidents as they are resolved in Nexpose.
146
146
 
@@ -155,7 +155,7 @@ class ServiceNowHelper
155
155
  Fix: #{row['fix']}
156
156
  ----------------------------------------------------------------------------
157
157
  URL: #{row['url']}
158
- NXID: #{site_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
158
+ NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
159
159
  }.to_json
160
160
  tickets.push(ticket)
161
161
  end
@@ -173,14 +173,14 @@ class ServiceNowHelper
173
173
  # * *Returns* :
174
174
  # - List of JSON-formated tickets for creating within ServiceNow.
175
175
  #
176
- def prepare_create_tickets_by_ip(vulnerability_list, site_id)
176
+ def prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
177
177
  @log.log_message('Preparing tickets by IP address.')
178
178
  tickets = []
179
179
  current_ip = -1
180
180
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
181
181
  if current_ip == -1
182
182
  current_ip = row['ip_address']
183
- @log.log_message("Creating ticket with IP address: #{row['ip_address']} for site with ID: #{site_id}")
183
+ @log.log_message("Creating ticket with IP address: #{row['ip_address']} for Nexpose identifier with ID: #{nexpose_identifier_id}")
184
184
  @ticket = {
185
185
  'sysparm_action' => 'insert',
186
186
  'u_caller_id' => "#{@servicenow_data[:username]}",
@@ -208,7 +208,7 @@ class ServiceNowHelper
208
208
  unless current_ip == row['ip_address']
209
209
  # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
210
210
  @log.log_message("Found new IP address. Finishing ticket with with IP address: #{current_ip} and moving onto IP #{row['ip_address']}")
211
- @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}"
211
+ @ticket['u_work_notes'] += "\nNXID: #{nexpose_identifier_id}#{current_ip}"
212
212
  @ticket = @ticket.to_json
213
213
  tickets.push(@ticket)
214
214
  current_ip = -1
@@ -216,7 +216,7 @@ class ServiceNowHelper
216
216
  end
217
217
  end
218
218
  # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
219
- @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}" unless (@ticket.size == 0)
219
+ @ticket['u_work_notes'] += "\nNXID: #{nexpose_identifier_id}#{current_ip}" unless (@ticket.size == 0)
220
220
  tickets.push(@ticket.to_json) unless @ticket.nil?
221
221
  tickets
222
222
  end
@@ -231,7 +231,7 @@ class ServiceNowHelper
231
231
  # * *Returns* :
232
232
  # - List of JSON-formated tickets for updating within ServiceNow.
233
233
  #
234
- def prepare_update_tickets(vulnerability_list, site_id)
234
+ def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
235
235
  fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] == 'D'
236
236
  @ticket = Hash.new(-1)
237
237
 
@@ -243,7 +243,7 @@ class ServiceNowHelper
243
243
  if current_ip == -1
244
244
  current_ip = row['ip_address']
245
245
  ticket_status = row['comparison']
246
- @log.log_message("Creating ticket update with IP address: #{row['ip_address']} and site ID: #{site_id}")
246
+ @log.log_message("Creating ticket update with IP address: #{row['ip_address']} and Nexpose identifier ID: #{nexpose_identifier_id}")
247
247
  @log.log_message("Ticket status #{ticket_status}")
248
248
  action = 'update'
249
249
  if ticket_status == 'New'
@@ -251,7 +251,7 @@ class ServiceNowHelper
251
251
  end
252
252
  @ticket = {
253
253
  'sysparm_action' => action,
254
- 'sysparm_query' => "u_work_notesCONTAINSNXID: #{site_id}#{row['ip_address']}",
254
+ 'sysparm_query' => "u_work_notesCONTAINSNXID: #{nexpose_identifier_id}#{row['ip_address']}",
255
255
  'u_work_notes' =>
256
256
  "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
257
257
  ++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++
@@ -282,7 +282,7 @@ class ServiceNowHelper
282
282
  end
283
283
  unless current_ip == row['ip_address']
284
284
  # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
285
- @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}"
285
+ @ticket['u_work_notes'] += "\nNXID: #{nexpose_identifier_id}#{current_ip}"
286
286
  @ticket = @ticket.to_json
287
287
  tickets.push(@ticket)
288
288
  current_ip = -1
@@ -290,7 +290,7 @@ class ServiceNowHelper
290
290
  end
291
291
  end
292
292
  # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
293
- @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}" unless (@ticket.size == 0)
293
+ @ticket['u_work_notes'] += "\nNXID: #{nexpose_identifier_id}#{current_ip}" unless (@ticket.size == 0)
294
294
  tickets.push(@ticket.to_json) unless @ticket.nil?
295
295
  tickets
296
296
  end
@@ -305,7 +305,7 @@ class ServiceNowHelper
305
305
  # * *Returns* :
306
306
  # - List of JSON-formated tickets for closing within ServiceNow.
307
307
  #
308
- def prepare_close_tickets(vulnerability_list, site_id)
308
+ def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
309
309
  @log.log_message("Preparing ticket closures for mode #{@options[:ticket_mode]}.")
310
310
  tickets = []
311
311
  @nxid = nil
@@ -313,13 +313,13 @@ class ServiceNowHelper
313
313
  case @options[:ticket_mode]
314
314
  # 'D' Default mode: IP *-* Vulnerability
315
315
  when 'D'
316
- @nxid = "#{site_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
316
+ @nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
317
317
  # 'I' IP address mode: IP address -* Vulnerability
318
318
  when 'I'
319
- @nxid = "#{site_id}#{row['ip_address']}"
319
+ @nxid = "#{nexpose_identifier_id}#{row['ip_address']}"
320
320
  # 'V' Vulnerability mode: Vulnerability -* IP address
321
321
  ## when 'V'
322
- ## @nxid = "#{site_id}#{row['asset_id']}#{row['vulnerability_id']}"
322
+ ## @nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}"
323
323
  else
324
324
  fail 'Could not close tickets - do not understand the ticketing mode!'
325
325
  end
@@ -22,8 +22,25 @@ module NexposeTicketing
22
22
  return riskString
23
23
  end
24
24
 
25
+ # Formats SQL query for filtering per asset based on user config options.
26
+ #
27
+ # * *Args* :
28
+ # - +options+ - User configured options for the ticketing service.
29
+ #
30
+ # * *Returns* :
31
+ # - Returns String - Formatted SQL string for inserting into queries.
32
+ #
25
33
 
26
- # Gets all the latests scans.
34
+ def self.createAssetString(options)
35
+ if options[:tag_run] && options[:nexpose_item]
36
+ assetString = "WHERE asset_id = #{options[:nexpose_item]}"
37
+ else
38
+ assetString = ""
39
+ end
40
+ return assetString
41
+ end
42
+
43
+ # Gets all the latest scans for sites.
27
44
  # Returns |site.id| |last_scan_id| |finished|
28
45
  def self.last_scans
29
46
  'SELECT ds.site_id, ds.last_scan_id, dsc.finished
@@ -31,6 +48,14 @@ module NexposeTicketing
31
48
  JOIN dim_scan dsc ON ds.last_scan_id = dsc.scan_id'
32
49
  end
33
50
 
51
+ # Gets all the latest scans for tags.
52
+ # Returns |tag.id| |asset.id| |last_scan_id| |finished|
53
+ def self.last_tag_scans
54
+ 'select dta.tag_id, dta.asset_id, fa.last_scan_id, fa.scan_finished
55
+ from dim_tag_asset dta
56
+ join fact_asset fa using (asset_id)
57
+ order by dta.tag_id, dta.asset_id, fa.last_scan_id, fa.scan_finished'
58
+ end
34
59
 
35
60
  # Gets all delta vulns for all sites sorted by IP.
36
61
  #
@@ -47,7 +72,7 @@ module NexposeTicketing
47
72
  JOIN
48
73
  (
49
74
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
50
- FROM dim_asset) s
75
+ FROM dim_asset #{createAssetString(options)}) s
51
76
  ON s.asset_id = fasv.asset_id AND (fasv.scan_id = s.baseline_scan OR fasv.scan_id = s.current_scan)
52
77
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan, fasv.scan_id
53
78
  HAVING NOT baselineComparison(fasv.scan_id, current_scan) = 'Old'
@@ -77,7 +102,7 @@ module NexposeTicketing
77
102
  JOIN
78
103
  (
79
104
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
80
- FROM dim_asset) s
105
+ FROM dim_asset #{createAssetString(options)}) s
81
106
  ON s.asset_id = fasv.asset_id AND (fasv.scan_id = s.baseline_scan OR fasv.scan_id = s.current_scan)
82
107
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan, fasv.scan_id
83
108
  HAVING NOT baselineComparison(fasv.scan_id, current_scan) = 'Old'
@@ -87,7 +112,7 @@ module NexposeTicketing
87
112
  JOIN dim_asset da ON subs.asset_id = da.asset_id
88
113
  JOIN dim_vulnerability dv ON subs.vulnerability_id = dv.vulnerability_id
89
114
  JOIN fact_asset fa ON fa.asset_id = da.asset_id
90
- #{createRiskString(options[:riskScore])}
115
+ #{createRiskString(options[:riskScore])}
91
116
  ORDER BY subs.vulnerability_id, subs.asset_id, davs.solution_id"
92
117
  end
93
118
 
@@ -108,7 +133,7 @@ module NexposeTicketing
108
133
  JOIN
109
134
  (
110
135
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
111
- FROM dim_asset) s
136
+ FROM dim_asset #{createAssetString(options)}) s
112
137
  ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
113
138
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
114
139
  HAVING baselineComparison(fasv.scan_id, current_scan) = 'New'
@@ -140,7 +165,7 @@ module NexposeTicketing
140
165
  JOIN
141
166
  (
142
167
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
143
- FROM dim_asset) s
168
+ FROM dim_asset #{createAssetString(options)}) s
144
169
  ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
145
170
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
146
171
  HAVING baselineComparison(fasv.scan_id, current_scan) = 'New'
@@ -172,7 +197,7 @@ module NexposeTicketing
172
197
  FROM fact_asset_scan_vulnerability_finding fasv
173
198
  JOIN (
174
199
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
175
- FROM dim_asset
200
+ FROM dim_asset #{createAssetString(options)}
176
201
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
177
202
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
178
203
  HAVING baselineComparison(fasv.scan_id, current_scan) = 'Old'
@@ -205,7 +230,7 @@ module NexposeTicketing
205
230
  FROM fact_asset_scan_vulnerability_finding fasv
206
231
  JOIN (
207
232
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
208
- FROM dim_asset
233
+ FROM dim_asset #{createAssetString(options)}
209
234
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
210
235
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
211
236
  HAVING baselineComparison(fasv.scan_id, current_scan) = 'Old'
@@ -228,7 +253,7 @@ module NexposeTicketing
228
253
  JOIN
229
254
  (
230
255
  SELECT asset_id,lastScan(asset_id) AS current_scan
231
- FROM dim_asset
256
+ FROM dim_asset #{createAssetString(options)}
232
257
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
233
258
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
234
259
  HAVING baselineComparison(fasv.scan_id, current_scan) IN ('Same','New')
@@ -261,7 +286,7 @@ module NexposeTicketing
261
286
  FROM fact_asset_scan_vulnerability_finding fasv
262
287
  JOIN (
263
288
  SELECT asset_id, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
264
- FROM dim_asset
289
+ FROM dim_asset #{createAssetString(options)}
265
290
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
266
291
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
267
292
  HAVING baselineComparison(fasv.scan_id, current_scan) = 'Old'
@@ -285,7 +310,7 @@ module NexposeTicketing
285
310
  JOIN
286
311
  (
287
312
  SELECT asset_id,lastScan(asset_id) AS current_scan
288
- FROM dim_asset
313
+ FROM dim_asset #{createAssetString(options)}
289
314
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
290
315
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.current_scan
291
316
  HAVING baselineComparison(fasv.scan_id, current_scan) IN ('Same','New')
@@ -317,7 +342,7 @@ module NexposeTicketing
317
342
  FROM fact_asset_scan_vulnerability_finding fasv
318
343
  JOIN (
319
344
  SELECT asset_id, ip_address, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
320
- FROM dim_asset
345
+ FROM dim_asset #{createAssetString(options)}
321
346
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
322
347
  GROUP BY fasv.asset_id, fasv.vulnerability_id, s.ip_address, s.current_scan
323
348
  HAVING baselineComparison(fasv.scan_id, current_scan) = 'Old'
@@ -329,7 +354,7 @@ module NexposeTicketing
329
354
  JOIN
330
355
  (
331
356
  SELECT asset_id, ip_address, lastScan(asset_id) AS current_scan
332
- FROM dim_asset
357
+ FROM dim_asset #{createAssetString(options)}
333
358
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
334
359
  GROUP BY s.ip_address, s.current_scan
335
360
  HAVING baselineComparison(fasv.scan_id, current_scan) IN ('Same','New')
@@ -354,7 +379,7 @@ module NexposeTicketing
354
379
  FROM fact_asset_scan_vulnerability_finding fasv
355
380
  JOIN (
356
381
  SELECT asset_id, ip_address, previousScan(asset_id) AS baseline_scan, lastScan(asset_id) AS current_scan
357
- FROM dim_asset
382
+ FROM dim_asset #{createAssetString(options)}
358
383
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
359
384
  JOIN fact_asset fa ON fa.asset_id = fasv.asset_id
360
385
  #{createRiskString(options[:riskScore])}
@@ -368,7 +393,7 @@ module NexposeTicketing
368
393
  JOIN
369
394
  (
370
395
  SELECT da.asset_id, da.ip_address, lastScan(da.asset_id) AS current_scan, fa.riskscore
371
- FROM dim_asset da
396
+ FROM dim_asset da #{createAssetString(options)}
372
397
  JOIN fact_asset fa ON fa.asset_id = da.asset_id
373
398
  #{createRiskString(options[:riskScore])}
374
399
  ) s ON s.asset_id = fasv.asset_id AND (fasv.scan_id >= #{options[:scan_id]} OR fasv.scan_id = s.current_scan)
@@ -28,7 +28,7 @@ module NexposeTicketing
28
28
  @nsc.sites
29
29
  end
30
30
 
31
- # Reads the site scan history from disk.
31
+ # Reads a nexpose identifier (tag ID, site ID etc) scan history from disk.
32
32
  #
33
33
  # * *Args* :
34
34
  # - +csv_file_name+ - CSV File name.
@@ -37,11 +37,11 @@ module NexposeTicketing
37
37
  # - A hash with site_ids => last_scan_id
38
38
  #
39
39
  def read_last_scans(csv_file_name)
40
- file_site_histories = Hash.new(-1)
40
+ file_identifier_histories = Hash.new(-1)
41
41
  CSV.foreach(csv_file_name, headers: true) do |row|
42
- file_site_histories[row['site_id']] = row['last_scan_id']
42
+ file_identifier_histories[row[0]] = row[1]
43
43
  end
44
- file_site_histories
44
+ file_identifier_histories
45
45
  end
46
46
 
47
47
  # Saves the last scan info to disk.
@@ -51,7 +51,7 @@ module NexposeTicketing
51
51
  #
52
52
  def save_last_scans(csv_file_name, options = {}, saved_file = nil)
53
53
  current_scan_state = load_last_scans(options)
54
- save_scans_to_file(csv_file_name, current_scan_state, saved_file)
54
+ save_to_file(csv_file_name, current_scan_state, saved_file)
55
55
  end
56
56
 
57
57
  # Loads the last scan info to memory.
@@ -60,32 +60,132 @@ module NexposeTicketing
60
60
  # - +csv_file_name+ - CSV File name.
61
61
  #
62
62
  def load_last_scans(options = {}, report_config = Nexpose::AdhocReportConfig.new(nil, 'sql'))
63
- sites = Array(options[:sites])
64
63
  report_config.add_filter('version', '1.2.0')
65
- report_config.add_filter('query', Queries.last_scans)
64
+ sites = Array(options[:sites])
65
+ tags = Array(options[:tags])
66
+ if(options[:tag_run])
67
+ report_config.add_filter('query', Queries.last_tag_scans)
68
+ tags.each do |tag|
69
+ report_config.add_filter('tag', tag)
70
+ end
71
+ else
72
+ report_config.add_filter('query', Queries.last_scans)
73
+ end
66
74
 
67
75
  report_output = report_config.generate(@nsc, @timeout)
68
76
  csv_output = CSV.parse(report_output.chomp, headers: :first_row)
69
77
 
70
78
  #We only care about sites we are monitoring.
71
79
  trimmed_csv = []
72
- trimmed_csv << report_output.lines.first
73
- csv_output.each do |row|
74
- if sites.include? row[0].to_s
75
- trimmed_csv << row
80
+ if(options[:tag_run])
81
+ trimmed_csv << 'tag_id, last_scan_fingerprint'
82
+ current_tag_id = nil
83
+ tag_finger_print = ''
84
+ csv_output.each do |row|
85
+ if (tags.include? row[0].to_s) && (row[0].to_i != current_tag_id)
86
+ if(current_tag_id.nil?)
87
+ #Initial run
88
+ current_tag_id = row[0].to_i
89
+ else
90
+ #New tag ID, finish off the old fingerprint and start on the new one
91
+ trimmed_csv << CSV::Row.new('tag_id, last_scan_fingerprint'.split(','), "#{current_tag_id},#{Digest::MD5::hexdigest(tag_finger_print)}".split(','))
92
+ tag_finger_print.clear
93
+ current_tag_id = row[0].to_i
94
+ end
95
+ end
96
+
97
+ if(current_tag_id == row[0].to_i)
98
+ #yield current_tag_id, row[1].to_s, row[2].to_s if block_given?
99
+ tag_finger_print << row[1].to_s
100
+ tag_finger_print << row[2].to_s
101
+ end
102
+ end
103
+ unless tag_finger_print.empty?
104
+ trimmed_csv << CSV::Row.new('tag_id, last_scan_fingerprint'.split(','), "#{current_tag_id},#{Digest::MD5::hexdigest(tag_finger_print)}".split(','))
105
+ end
106
+ else
107
+ trimmed_csv << report_output.lines.first
108
+ csv_output.each do |row|
109
+ if sites.include? row[0].to_s
110
+ trimmed_csv << row
111
+ end
76
112
  end
77
113
  end
78
114
  trimmed_csv
79
115
  end
80
116
 
117
+ # Parses user-configured vulnerability filter categories and returns aforementioned categories in a
118
+ # format used by the Nexpose::AdhocReportConfig class.
119
+ #
120
+ # * *Args* :
121
+ # - +options+ - A Hash with site(s), reported_scan_id and severity level.
122
+ #
123
+ # * *Returns* :
124
+ # - Returns String @vulnerability_categories
125
+ #
126
+ def createVulnerabilityFilter(options = {})
127
+ @vulnerability_categories = nil
128
+ if options.has_key?(:vulnerabilityCategories)
129
+ if not options[:vulnerabilityCategories].nil? and not options[:vulnerabilityCategories].empty?
130
+ @vulnerability_categories = options[:vulnerabilityCategories].strip.split(',').map {|category| "include:#{category}"}.join(',')
131
+ end
132
+ end
133
+ @vulnerability_categories
134
+ end
135
+
136
+ def read_tag_asset_list(cvs_file_name)
137
+ file_identifier_histories = Hash.new(-1)
138
+ CSV.foreach(cvs_file_name, headers: true) do |row|
139
+ file_identifier_histories[row[0]] = row[1]
140
+ end
141
+ file_identifier_histories
142
+ end
143
+
144
+ def generate_tag_asset_list(options = {}, report_config = Nexpose::AdhocReportConfig.new(nil, 'sql'))
145
+ report_config.add_filter('version', '1.2.0')
146
+ tags = Array(options[:tags])
147
+ report_config.add_filter('query', Queries.last_tag_scans)
148
+ tags.each do |tag|
149
+ report_config.add_filter('tag', tag)
150
+ end
151
+
152
+ report_output = report_config.generate(@nsc, @timeout)
153
+ csv_output = CSV.parse(report_output.chomp, headers: :first_row)
154
+ trimmed_csv = []
155
+ trimmed_csv << 'asset_id, last_scan_id'
156
+ current_tag_id = nil
157
+ csv_output.each do |row|
158
+ if (tags.include? row[0].to_s) && (row[0].to_i != current_tag_id)
159
+ if(current_tag_id.nil?)
160
+ #Initial run
161
+ current_tag_id = row[0].to_i
162
+ else
163
+ #New tag ID, finish off the previous tag asset list and start on the new one
164
+ save_to_file(options[:csv_file], trimmed_csv)
165
+ current_tag_id = row[0].to_i
166
+ trimmed_csv = []
167
+ end
168
+ end
169
+
170
+ if(current_tag_id == row[0].to_i)
171
+ trimmed_csv << "#{row[1].to_s},#{row[2].to_s}"
172
+ end
173
+ end
174
+ save_to_file(options[:csv_file], trimmed_csv) if trimmed_csv.any?
175
+ end
176
+
81
177
  # Saves CSV scan information to disk
82
178
  #
83
179
  # * *Args* :
84
180
  # - +csv_file_name+ - CSV File name.
85
181
  #
86
- def save_scans_to_file(csv_file_name, trimmed_csv, saved_file = nil)
182
+ def save_to_file(csv_file_name, trimmed_csv, saved_file = nil)
87
183
  saved_file.open(csv_file_name, 'w') { |file| file.puts(trimmed_csv) } unless saved_file.nil?
88
184
  if saved_file.nil?
185
+ dir = File.dirname(csv_file_name)
186
+ unless File.directory?(dir)
187
+ FileUtils.mkdir_p(dir)
188
+ end
89
189
  File.open(csv_file_name, 'w') { |file| file.puts(trimmed_csv) }
90
190
  end
91
191
  end
@@ -93,131 +193,143 @@ module NexposeTicketing
93
193
  # Gets the last scan information from nexpose sans the CSV headers.
94
194
  #
95
195
  # * *Returns* :
96
- # - A hash with site_ids => last_scan_id
196
+ # - A hash with nexpose_ids (site ID or tag ID) => last_scan_id
97
197
  #
98
198
  def last_scans(options = {})
99
- nexpose_sites = Hash.new(-1)
199
+ nexpose_ids= Hash.new(-1)
100
200
  trimmed_csv = load_last_scans(options)
101
201
  trimmed_csv.drop(1).each do |row|
102
- nexpose_sites[row['site_id']] = row['last_scan_id'].to_i
202
+ nexpose_ids[row[0]] = row[1]
103
203
  end
104
- nexpose_sites
204
+ nexpose_ids
105
205
  end
106
206
 
107
207
  # Gets all the vulnerabilities for a new site or fresh install.
108
208
  #
109
209
  # * *Args* :
110
- # - +site_options+ - A Hash with site(s) and severity level.
111
- # - +site_to_query+ - Override for user-configured site options
210
+ # - +options+ - A Hash with user configuration information.
211
+ # - +nexpose_item_override+ - Override for user-configured tag/site options
112
212
  #
113
213
  # * *Returns* :
114
214
  # - Returns CSV |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
115
215
  # |url| |summary| |fix|
116
216
  #
117
- def all_vulns(options = {}, site_override = nil)
118
- if site_override.nil?
119
- sites = Array(options[:sites])
120
- else
121
- sites = Array(site_override)
122
- end
217
+ def all_vulns(options = {}, nexpose_item_override = nil)
123
218
  report_config = @report_helper.generate_sql_report_config()
124
219
  severity = options[:severity].nil? ? 0 : options[:severity]
125
220
  report_config.add_filter('version', '1.2.0')
126
221
  if options[:ticket_mode] == 'V'
127
222
  report_config.add_filter('query', Queries.all_new_vulns_by_vuln_id(options))
128
223
  else
129
- report_config.add_filter('query', Queries.all_new_vulns(options))
224
+ report_config.add_filter('query', Queries.all_new_vulns(options))
130
225
  end
131
- unless sites.nil? || sites.empty?
132
- Array(sites).each do |site_id|
133
- report_config.add_filter('site', site_id)
226
+
227
+ if nexpose_item_override.nil?
228
+ if(options[:tag_run])
229
+ nexpose_items = Array(options[:tags])
230
+ else
231
+ nexpose_items = Array(options[:sites])
134
232
  end
233
+ else
234
+ nexpose_items = Array(nexpose_item_override)
135
235
  end
236
+
237
+ if options[:tag_run]
238
+ unless nexpose_items.nil? || nexpose_items.empty?
239
+ nexpose_items.each do |tag_id|
240
+ report_config.add_filter('tag', tag_id)
241
+ end
242
+ end
243
+ else
244
+ unless nexpose_items.nil? || nexpose_items.empty?
245
+ nexpose_items.each do |site_id|
246
+ report_config.add_filter('site', site_id)
247
+ end
248
+ end
249
+ end
250
+
136
251
  report_config.add_filter('vuln-severity', severity)
137
252
 
138
253
  vuln_filter_cats = createVulnerabilityFilter(options)
139
- vuln_filer_tags = createTagFilters(options)
140
254
 
141
255
  if not vuln_filter_cats.nil? and not vuln_filter_cats.empty?
142
256
  report_config.add_filter('vuln-categories', vuln_filter_cats)
143
257
  end
144
258
 
145
- if not vuln_filer_tags.nil? and not vuln_filer_tags.empty?
146
- vuln_filer_tags.map {|tag| report_config.add_filter('tag', tag.id) }
147
- end
148
-
149
259
  @report_helper.save_generate_cleanup_report_config(report_config)
150
260
  end
151
261
 
152
262
  # Gets the new vulns from base scan reported_scan_id and the newest / latest scan from a site.
153
263
  #
154
264
  # * *Args* :
155
- # - +site_options+ - A Hash with site(s), reported_scan_id and severity level.
265
+ # - +options+ - A Hash with user configuration information.
156
266
  #
157
267
  # * *Returns* :
158
268
  # - Returns CSV |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
159
269
  # |url| |summary| |fix|
160
270
  #
161
- def new_vulns_sites(site_options = {})
271
+ def new_vulns(options = {})
162
272
  report_config = @report_helper.generate_sql_report_config()
163
- site = site_options[:site_id]
164
- reported_scan_id = site_options[:scan_id]
165
- fail 'Site cannot be null or empty' if site.nil? || reported_scan_id.nil?
166
- severity = site_options[:severity].nil? ? 0 : site_options[:severity]
273
+ nexpose_item = options[:nexpose_item]
274
+ reported_scan_id = options[:scan_id]
275
+ fail 'Nexpose item cannot be null or empty' if nexpose_item.nil? || reported_scan_id.nil?
276
+ severity = options[:severity].nil? ? 0 : options[:severity]
167
277
  report_config.add_filter('version', '1.2.0')
168
- if site_options[:ticket_mode] == 'V'
169
- report_config.add_filter('query', Queries.new_vulns_by_vuln_id_since_scan(site_options))
278
+ if options[:ticket_mode] == 'V'
279
+ report_config.add_filter('query', Queries.new_vulns_by_vuln_id_since_scan(options))
170
280
  else
171
- report_config.add_filter('query', Queries.new_vulns_since_scan(site_options))
281
+ report_config.add_filter('query', Queries.new_vulns_since_scan(options))
172
282
  end
173
- report_config.add_filter('site', site)
283
+
284
+ if(options[:tag_run])
285
+ report_config.add_filter('tag', options[:tag])
286
+ else
287
+ report_config.add_filter('site', nexpose_item)
288
+ end
289
+
174
290
  report_config.add_filter('vuln-severity', severity)
175
291
 
176
- vuln_filter_cats = createVulnerabilityFilter(site_options)
177
- vuln_filer_tags = createTagFilters(site_options)
292
+ vuln_filter_cats = createVulnerabilityFilter(options)
178
293
 
179
294
  if not vuln_filter_cats.nil? and not vuln_filter_cats.empty?
180
295
  report_config.add_filter('vuln-categories', vuln_filter_cats)
181
296
  end
182
297
 
183
- if not vuln_filer_tags.nil? and not vuln_filer_tags.empty?
184
- vuln_filer_tags.map {|tag| report_config.add_filter('tag', tag.id) }
185
- end
186
-
187
298
  @report_helper.save_generate_cleanup_report_config(report_config)
188
299
  end
189
300
 
190
301
  # Gets the old vulns from base scan reported_scan_id and the newest / latest scan from a site.
191
302
  #
192
303
  # * *Args* :
193
- # - +site_options+ - A Hash with site(s), reported_scan_id and severity level.
304
+ # - +options+ - A Hash with user configuration information.
194
305
  #
195
306
  # * *Returns* :
196
307
  # - Returns CSV |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
197
308
  # |url| |summary| |fix|
198
309
  #
199
- def old_vulns_sites(site_options = {})
310
+ def old_vulns(options = {})
200
311
  report_config = @report_helper.generate_sql_report_config()
201
- site = site_options[:site_id]
202
- reported_scan_id = site_options[:scan_id]
203
- fail 'Site cannot be null or empty' if site.nil? || reported_scan_id.nil?
204
- severity = site_options[:severity].nil? ? 0 : site_options[:severity]
312
+ nexpose_item = options[:nexpose_item]
313
+ reported_scan_id = options[:scan_id]
314
+ fail 'Nexpose item cannot be null or empty' if nexpose_item.nil? || reported_scan_id.nil?
315
+ severity = options[:severity].nil? ? 0 : options[:severity]
205
316
  report_config.add_filter('version', '1.2.0')
206
- report_config.add_filter('query', Queries.old_vulns_since_scan(site_options))
207
- report_config.add_filter('site', site)
317
+ report_config.add_filter('query', Queries.old_vulns_since_scan(options))
318
+
319
+ if(options[:tag_run])
320
+ report_config.add_filter('tag', options[:tag])
321
+ else
322
+ report_config.add_filter('site', nexpose_item)
323
+ end
324
+
208
325
  report_config.add_filter('vuln-severity', severity)
209
326
 
210
- vuln_filter_cats = createVulnerabilityFilter(site_options)
211
- vuln_filer_tags = createTagFilters(site_options)
327
+ vuln_filter_cats = createVulnerabilityFilter(options)
212
328
 
213
329
  if not vuln_filter_cats.nil? and not vuln_filter_cats.empty?
214
330
  report_config.add_filter('vuln-categories', vuln_filter_cats)
215
331
  end
216
332
 
217
- if not vuln_filer_tags.nil? and not vuln_filer_tags.empty?
218
- vuln_filer_tags.map {|tag| report_config.add_filter('tag', tag.id) }
219
- end
220
-
221
333
  @report_helper.save_generate_cleanup_report_config(report_config)
222
334
  end
223
335
 
@@ -225,37 +337,38 @@ module NexposeTicketing
225
337
  # Based on IP address (for 'I' mode) or vuln ID ('V' mode).
226
338
  #
227
339
  # * *Args* :
228
- # - +site_options+ - A Hash with site(s), reported_scan_id and severity level.
340
+ # - +options+ - A Hash with user configuration information.
229
341
  #
230
342
  # * *Returns* :
231
343
  # - Returns CSV |asset_id| |ip_address| |current_scan| |vulnerability_id| |comparison|
232
344
  #
233
- def tickets_to_close(site_options = {})
345
+ def tickets_to_close(options = {})
234
346
  report_config = @report_helper.generate_sql_report_config()
235
- site = site_options[:site_id]
236
- reported_scan_id = site_options[:scan_id]
237
- fail 'Site cannot be null or empty' if site.nil? || reported_scan_id.nil?
238
- severity = site_options[:severity].nil? ? 0 : site_options[:severity]
347
+ nexpose_item = options[:nexpose_item]
348
+ reported_scan_id = options[:scan_id]
349
+ fail 'Nexpose item cannot be null or empty' if nexpose_item.nil? || reported_scan_id.nil?
350
+ severity = options[:severity].nil? ? 0 : options[:severity]
239
351
  report_config.add_filter('version', '1.2.0')
240
- if site_options[:ticket_mode] == 'V'
241
- report_config.add_filter('query', Queries.old_tickets_by_vuln_id(site_options))
352
+ if options[:ticket_mode] == 'V'
353
+ report_config.add_filter('query', Queries.old_tickets_by_vuln_id(options))
242
354
  else
243
- report_config.add_filter('query', Queries.old_tickets_by_ip(site_options))
355
+ report_config.add_filter('query', Queries.old_tickets_by_ip(options))
244
356
  end
245
- report_config.add_filter('site', site)
357
+
358
+ if(options[:tag_run])
359
+ report_config.add_filter('tag', options[:tag])
360
+ else
361
+ report_config.add_filter('site', nexpose_item)
362
+ end
363
+
246
364
  report_config.add_filter('vuln-severity', severity)
247
365
 
248
- vuln_filter_cats = createVulnerabilityFilter(site_options)
249
- vuln_filer_tags = createTagFilters(site_options)
366
+ vuln_filter_cats = createVulnerabilityFilter(options)
250
367
 
251
368
  if not vuln_filter_cats.nil? and not vuln_filter_cats.empty?
252
369
  report_config.add_filter('vuln-categories', vuln_filter_cats)
253
370
  end
254
371
 
255
- if not vuln_filer_tags.nil? and not vuln_filer_tags.empty?
256
- vuln_filer_tags.map {|tag| report_config.add_filter('tag', tag.id) }
257
- end
258
-
259
372
  @report_helper.save_generate_cleanup_report_config(report_config)
260
373
  end
261
374
 
@@ -264,85 +377,39 @@ module NexposeTicketing
264
377
  # used for IP-based issue updating. Includes the baseline comparision value ('Old','New', or 'Same').
265
378
  #
266
379
  # * *Args* :
267
- # - +site_options+ - A Hash with site(s), reported_scan_id and severity level.
380
+ # - +options+ - A Hash with user configuration information.
268
381
  #
269
382
  # * *Returns* :
270
383
  # - Returns CSV |asset_id| |ip_address| |current_scan| |vulnerability_id| |solution_id| |nexpose_id|
271
384
  # |url| |summary| |fix| |comparison|
272
385
  #
273
- def all_vulns_sites(site_options = {})
386
+ def all_vulns_since(options = {})
274
387
  report_config = @report_helper.generate_sql_report_config()
275
- site = site_options[:site_id]
276
- reported_scan_id = site_options[:scan_id]
277
- fail 'Site cannot be null or empty' if site.nil? || reported_scan_id.nil?
278
- severity = site_options[:severity].nil? ? 0 : site_options[:severity]
388
+ nexpose_item = options[:nexpose_item]
389
+ reported_scan_id = options[:scan_id]
390
+ fail 'Nexpose item cannot be null or empty' if nexpose_item.nil? || reported_scan_id.nil?
391
+ severity = options[:severity].nil? ? 0 : options[:severity]
279
392
  report_config.add_filter('version', '1.2.0')
280
- if site_options[:ticket_mode] == 'V'
281
- report_config.add_filter('query', Queries.all_vulns_by_vuln_id_since_scan(site_options))
393
+ if options[:ticket_mode] == 'V'
394
+ report_config.add_filter('query', Queries.all_vulns_by_vuln_id_since_scan(options))
395
+ else
396
+ report_config.add_filter('query', Queries.all_vulns_since_scan(options))
397
+ end
398
+
399
+ if(options[:tag_run])
400
+ report_config.add_filter('tag', options[:tag])
282
401
  else
283
- report_config.add_filter('query', Queries.all_vulns_since_scan(site_options))
402
+ report_config.add_filter('site', nexpose_item)
284
403
  end
285
- report_config.add_filter('site', site)
286
404
  report_config.add_filter('vuln-severity', severity)
287
405
 
288
- vuln_filter_cats = createVulnerabilityFilter(site_options)
289
- vuln_filer_tags = createTagFilters(site_options)
406
+ vuln_filter_cats = createVulnerabilityFilter(options)
290
407
 
291
408
  if not vuln_filter_cats.nil? and not vuln_filter_cats.empty?
292
409
  report_config.add_filter('vuln-categories', vuln_filter_cats)
293
410
  end
294
411
 
295
- if not vuln_filer_tags.nil? and not vuln_filer_tags.empty?
296
- vuln_filer_tags.map {|tag| report_config.add_filter('tag', tag.id) }
297
- end
298
-
299
412
  @report_helper.save_generate_cleanup_report_config(report_config)
300
413
  end
301
-
302
-
303
- # Parses user-configured vulnerability filter categories and returns aforementioned categories in a
304
- # format used by the Nexpose::AdhocReportConfig class.
305
- #
306
- # * *Args* :
307
- # - +site_options+ - A Hash with site(s), reported_scan_id and severity level.
308
- #
309
- # * *Returns* :
310
- # - Returns String @vulnerability_categories
311
- #
312
- def createVulnerabilityFilter(options = {})
313
- @vulnerability_categories = nil
314
- if options.has_key?(:vulnerabilityCategories)
315
- if not options[:vulnerabilityCategories].nil? and not options[:vulnerabilityCategories].empty?
316
- @vulnerability_categories = options[:vulnerabilityCategories].strip.split(',').map {|category| "include:#{category}"}.join(',')
317
- end
318
- end
319
- @vulnerability_categories
320
- end
321
-
322
- # Parses user-configured tags and returns aforementioned tags in an array containing
323
- # strings in the format used by the Nexpose::AdhocReportConfig class.
324
- #
325
- # * *Args* :
326
- # - +site_options+ - A Hash with site(s), reported_scan_id and severity level.
327
- #
328
- # * *Returns* :
329
- # - Returns Array @definedTags
330
- #
331
- def createTagFilters(options = {})
332
- @defined_tags = nil
333
- if options.has_key?(:tags)
334
- return if options[:tags].nil?
335
- if options[:tags].is_a?(Fixnum)
336
- @defined_tags = @nsc.list_tags.select{ |nexposeTag| nexposeTag.id == options[:tags] }
337
- else
338
- ## Split the tags into an array
339
- tag_strings = options[:tags].strip.split(',')
340
- ## Grab the tag info for the ones we are looking for (if the exist in Nexpose).
341
- @defined_tags = @nsc.list_tags.select {|nexposeTag| tag_strings.include?(nexposeTag.name)}
342
- end
343
- end
344
- @defined_tags
345
- end
346
-
347
414
  end
348
415
  end