nexpose_ticketing 0.8.1 → 0.8.2

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