heimdall_tools 1.3.37 → 1.3.41

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ netsparker_mapper translates an Netsparker XML results file into HDF format json to be viewable in Heimdall
2
+
3
+ The current iteration only works with Netsparker Enterprise Vulnerabilities Scan.
4
+
5
+ Examples:
6
+
7
+ heimdall_tools netsparker_mapper -x netsparker_results.xml -o netsparker_hdf.json
@@ -10,10 +10,10 @@ CWE_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'cwe-nist-mapping.csv')
10
10
  IMPACT_MAPPING = {
11
11
  high: 0.7,
12
12
  medium: 0.5,
13
- low: 0.3,
13
+ low: 0.3
14
14
  }.freeze
15
15
 
16
- DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze
16
+ DEFAULT_NIST_TAG = %w{SA-11 RA-5}.freeze
17
17
 
18
18
  # Loading spinner sign
19
19
  $spinner = Enumerator.new do |e|
@@ -27,14 +27,13 @@ end
27
27
 
28
28
  module HeimdallTools
29
29
  class JfrogXrayMapper
30
- def initialize(xray_json, name=nil, verbose = false)
30
+ def initialize(xray_json, _name = nil, verbose = false)
31
31
  @xray_json = xray_json
32
32
  @verbose = verbose
33
33
 
34
34
  begin
35
35
  @cwe_nist_mapping = parse_mapper
36
36
  @project = JSON.parse(xray_json)
37
-
38
37
  rescue StandardError => e
39
38
  raise "Invalid JFrog Xray JSON file provided Exception: #{e}"
40
39
  end
@@ -44,11 +43,11 @@ module HeimdallTools
44
43
  finding = {}
45
44
  finding['status'] = 'failed'
46
45
  finding['code_desc'] = []
47
- finding['code_desc'] << "source_comp_id : #{vulnerability['source_comp_id'].to_s }"
48
- finding['code_desc'] << "vulnerable_versions : #{vulnerability['component_versions']['vulnerable_versions'].to_s }"
49
- finding['code_desc'] << "fixed_versions : #{vulnerability['component_versions']['fixed_versions'].to_s }"
50
- finding['code_desc'] << "issue_type : #{vulnerability['issue_type'].to_s }"
51
- finding['code_desc'] << "provider : #{vulnerability['provider'].to_s }"
46
+ finding['code_desc'] << "source_comp_id : #{vulnerability['source_comp_id']}"
47
+ finding['code_desc'] << "vulnerable_versions : #{vulnerability['component_versions']['vulnerable_versions']}"
48
+ finding['code_desc'] << "fixed_versions : #{vulnerability['component_versions']['fixed_versions']}"
49
+ finding['code_desc'] << "issue_type : #{vulnerability['issue_type']}"
50
+ finding['code_desc'] << "provider : #{vulnerability['provider']}"
52
51
  finding['code_desc'] = finding['code_desc'].join("\n")
53
52
  finding['run_time'] = NA_FLOAT
54
53
 
@@ -57,17 +56,25 @@ module HeimdallTools
57
56
  [finding]
58
57
  end
59
58
 
59
+ def format_control_desc(vulnerability)
60
+ text = []
61
+ info = vulnerability['component_versions']['more_details']
62
+ text << info['description'].to_s
63
+ text << "cves: #{info['cves']}" unless info['cves'].nil?
64
+ text.join('<br>')
65
+ end
66
+
60
67
  def nist_tag(cweid)
61
- entries = @cwe_nist_mapping.select { |x| cweid.include? x[:cweid].to_s }
68
+ entries = @cwe_nist_mapping.select { |x| cweid.include?(x[:cweid].to_s) && !x[:nistid].nil? }
62
69
  tags = entries.map { |x| x[:nistid] }
63
70
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
64
71
  end
65
72
 
66
73
  def parse_identifiers(vulnerability, ref)
67
74
  # Extracting id number from reference style CWE-297
68
- vulnerability['component_versions']['more_details']['cves'][0][ref.downcase].map { |e| e.split("#{ref}-")[1] }
69
- rescue
70
- return []
75
+ vulnerability['component_versions']['more_details']['cves'][0][ref.downcase].map { |e| e.split("#{ref}-")[1] }
76
+ rescue StandardError
77
+ []
71
78
  end
72
79
 
73
80
  def impact(severity)
@@ -83,17 +90,17 @@ module HeimdallTools
83
90
  end
84
91
 
85
92
  def desc_tags(data, label)
86
- { "data": data || NA_STRING, "label": label || NA_STRING }
93
+ { data: data || NA_STRING, label: label || NA_STRING }
87
94
  end
88
95
 
89
96
  # Xray report could have multiple vulnerability entries for multiple findings of same issue type.
90
- # The meta data is identical across entries
97
+ # The meta data is identical across entries
91
98
  # method collapse_duplicates return unique controls with applicable findings collapsed into it.
92
99
  def collapse_duplicates(controls)
93
100
  unique_controls = []
94
101
 
95
102
  controls.map { |x| x['id'] }.uniq.each do |id|
96
- collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
103
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map { |x| x['results'] }
97
104
  unique_control = controls.find { |x| x['id'].eql?(id) }
98
105
  unique_control['results'] = collapsed_results.flatten
99
106
  unique_controls << unique_control
@@ -104,9 +111,9 @@ module HeimdallTools
104
111
  def to_hdf
105
112
  controls = []
106
113
  vulnerability_count = 0
107
- @project['data'].uniq.each do | vulnerability |
114
+ @project['data'].uniq.each do |vulnerability|
108
115
  printf("\rProcessing: %s", $spinner.next)
109
-
116
+
110
117
  vulnerability_count +=1
111
118
  item = {}
112
119
  item['tags'] = {}
@@ -115,26 +122,26 @@ module HeimdallTools
115
122
  item['source_location'] = NA_HASH
116
123
  item['descriptions'] = NA_ARRAY
117
124
 
118
- # Xray JSONs might note have `id` fields populated.
125
+ # Xray JSONs might note have `id` fields populated.
119
126
  # If thats a case MD5 hash is used to collapse vulnerability findings of the same type.
120
- item['id'] = vulnerability['id'].empty? ? OpenSSL::Digest::MD5.digest(vulnerability['summary'].to_s).unpack("H*")[0].to_s : vulnerability['id']
127
+ item['id'] = vulnerability['id'].empty? ? OpenSSL::Digest::MD5.digest(vulnerability['summary'].to_s).unpack1('H*').to_s : vulnerability['id']
121
128
  item['title'] = vulnerability['summary'].to_s
122
- item['desc'] = vulnerability['component_versions']['more_details']['description'].to_s
123
- item['impact'] = impact(vulnerability['severity'].to_s)
129
+ item['desc'] = format_control_desc(vulnerability)
130
+ item['impact'] = impact(vulnerability['severity'].to_s)
124
131
  item['code'] = NA_STRING
125
132
  item['results'] = finding(vulnerability)
126
133
 
127
- item['tags']['nist'] = nist_tag( parse_identifiers( vulnerability, 'CWE') )
128
- item['tags']['cweid'] = parse_identifiers( vulnerability, 'CWE')
134
+ item['tags']['nist'] = nist_tag(parse_identifiers(vulnerability, 'CWE'))
135
+ item['tags']['cweid'] = parse_identifiers(vulnerability, 'CWE')
129
136
 
130
137
  controls << item
131
138
  end
132
139
 
133
140
  controls = collapse_duplicates(controls)
134
- results = HeimdallDataFormat.new(profile_name: "JFrog Xray Scan",
141
+ results = HeimdallDataFormat.new(profile_name: 'JFrog Xray Scan',
135
142
  version: NA_STRING,
136
- title: "JFrog Xray Scan",
137
- summary: "Continuous Security and Universal Artifact Analysis",
143
+ title: 'JFrog Xray Scan',
144
+ summary: 'Continuous Security and Universal Artifact Analysis',
138
145
  controls: controls)
139
146
  results.to_hdf
140
147
  end
@@ -6,7 +6,7 @@ require 'nokogiri'
6
6
 
7
7
  RESOURCE_DIR = Pathname.new(__FILE__).join('../../data')
8
8
 
9
- NESSUS_PLUGINS_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'nessus-plugins-nist-mapping.csv')
9
+ NESSUS_PLUGINS_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'nessus-plugins-nist-mapping.csv')
10
10
  U_CCI_LIST = File.join(RESOURCE_DIR, 'U_CCI_List.xml')
11
11
 
12
12
  IMPACT_MAPPING = {
@@ -14,16 +14,16 @@ IMPACT_MAPPING = {
14
14
  Low: 0.3,
15
15
  Medium: 0.5,
16
16
  High: 0.7,
17
- Critical: 0.9,
17
+ Critical: 0.9
18
18
  }.freeze
19
19
 
20
- DEFAULT_NIST_TAG = ["unmapped"].freeze
20
+ DEFAULT_NIST_TAG = ['unmapped'].freeze
21
21
 
22
22
  # Nessus results file 800-53 refs does not contain Nist rev version. Using this default
23
23
  # version in that case
24
24
  DEFAULT_NIST_REV = 'Rev_4'.freeze
25
25
 
26
- NA_PLUGIN_OUTPUT = "This Nessus Plugin does not provide output message.".freeze
26
+ NA_PLUGIN_OUTPUT = 'This Nessus Plugin does not provide output message.'.freeze
27
27
 
28
28
  # rubocop:disable Metrics/AbcSize
29
29
 
@@ -51,19 +51,16 @@ module HeimdallTools
51
51
  rescue StandardError => e
52
52
  raise "Invalid Nessus XML file provided Exception: #{e}"
53
53
  end
54
-
55
54
  end
56
55
 
57
56
  def extract_report
58
- begin
59
- # When there are multiple hosts in the nessus report ReportHost field is an array
60
- # When there is only one host in the nessus report ReportHost field is a hash
61
- # Array() converts ReportHost to array in case there is only one host
62
- reports = @data['NessusClientData_v2']['Report']['ReportHost']
63
- reports.kind_of?(Array) ? reports : [reports]
64
- rescue StandardError => e
65
- raise "Invalid Nessus XML file provided Exception: #{e}"
66
- end
57
+ # When there are multiple hosts in the nessus report ReportHost field is an array
58
+ # When there is only one host in the nessus report ReportHost field is a hash
59
+ # Array() converts ReportHost to array in case there is only one host
60
+ reports = @data['NessusClientData_v2']['Report']['ReportHost']
61
+ reports.is_a?(Array) ? reports : [reports]
62
+ rescue StandardError => e
63
+ raise "Invalid Nessus XML file provided Exception: #{e}"
67
64
  end
68
65
 
69
66
  def parse_refs(refs, key)
@@ -71,24 +68,20 @@ module HeimdallTools
71
68
  end
72
69
 
73
70
  def extract_scaninfo
74
- begin
75
- policy = @data['NessusClientData_v2']['Policy']
76
- info = {}
71
+ policy = @data['NessusClientData_v2']['Policy']
72
+ info = {}
77
73
 
78
- info['policyName'] = policy['policyName']
79
- info['version'] = policy['Preferences']['ServerPreferences']['preference'].select {|x| x['name'].eql? 'sc_version'}.first['value']
80
- info
81
- rescue StandardError => e
82
- raise "Invalid Nessus XML file provided Exception: #{e}"
83
- end
74
+ info['policyName'] = policy['policyName']
75
+ info['version'] = policy['Preferences']['ServerPreferences']['preference'].select { |x| x['name'].eql? 'sc_version' }.first['value']
76
+ info
77
+ rescue StandardError => e
78
+ raise "Invalid Nessus XML file provided Exception: #{e}"
84
79
  end
85
80
 
86
81
  def extract_timestamp(report)
87
- begin
88
- timestamp = report['HostProperties']['tag'].select {|x| x['name'].eql? 'HOST_START'}.first['text']
89
- rescue StandardError => e
90
- raise "Invalid Nessus XML file provided Exception: #{e}"
91
- end
82
+ report['HostProperties']['tag'].select { |x| x['name'].eql? 'HOST_START' }.first['text']
83
+ rescue StandardError => e
84
+ raise "Invalid Nessus XML file provided Exception: #{e}"
92
85
  end
93
86
 
94
87
  def format_desc(issue)
@@ -129,7 +122,7 @@ module HeimdallTools
129
122
 
130
123
  def cci_nist_tag(cci_refs)
131
124
  nist_tags = []
132
- cci_refs.each do | cci_ref |
125
+ cci_refs.each do |cci_ref|
133
126
  item_node = @cci_xml.xpath("//cci_list/cci_items/cci_item[@id='#{cci_ref}']")[0] unless @cci_xml.nil?
134
127
  unless item_node.nil?
135
128
  nist_ref = item_node.xpath('./references/reference[not(@version <= preceding-sibling::reference/@version) and not(@version <=following-sibling::reference/@version)]/@index').text
@@ -140,7 +133,7 @@ module HeimdallTools
140
133
  end
141
134
 
142
135
  def plugin_nist_tag(pluginfamily, pluginid)
143
- entries = @cwe_nist_mapping.select { |x| (x[:pluginfamily].eql?(pluginfamily) && (x[:pluginid].eql?('*') || x[:pluginid].eql?(pluginid.to_i)) ) }
136
+ entries = @cwe_nist_mapping.select { |x| (x[:pluginfamily].eql?(pluginfamily) && (x[:pluginid].eql?('*') || x[:pluginid].eql?(pluginid.to_i))) && !x[:nistid].nil? }
144
137
  tags = entries.map { |x| [x[:nistid].split('|'), "Rev_#{x[:rev]}"] }
145
138
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
146
139
  end
@@ -148,15 +141,15 @@ module HeimdallTools
148
141
  def impact(severity)
149
142
  # Map CAT levels and Plugin severity to HDF impact levels
150
143
  case severity
151
- when "0"
144
+ when '0'
152
145
  IMPACT_MAPPING[:Info]
153
- when "1","III"
146
+ when '1', 'III'
154
147
  IMPACT_MAPPING[:Low]
155
- when "2","II"
148
+ when '2', 'II'
156
149
  IMPACT_MAPPING[:Medium]
157
- when "3","I"
150
+ when '3', 'I'
158
151
  IMPACT_MAPPING[:High]
159
- when "4"
152
+ when '4'
160
153
  IMPACT_MAPPING[:Critical]
161
154
  else
162
155
  -1
@@ -172,17 +165,17 @@ module HeimdallTools
172
165
  end
173
166
 
174
167
  def desc_tags(data, label)
175
- { "data": data || NA_STRING, "label": label || NA_STRING }
168
+ { data: data || NA_STRING, label: label || NA_STRING }
176
169
  end
177
170
 
178
171
  # Nessus report could have multiple issue entries for multiple findings of same issue type.
179
- # The meta data is identical across entries
172
+ # The meta data is identical across entries
180
173
  # method collapse_duplicates return unique controls with applicable findings collapsed into it.
181
174
  def collapse_duplicates(controls)
182
175
  unique_controls = []
183
176
 
184
177
  controls.map { |x| x['id'] }.uniq.each do |id|
185
- collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
178
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map { |x| x['results'] }
186
179
  unique_control = controls.find { |x| x['id'].eql?(id) }
187
180
  unique_control['results'] = collapsed_results.flatten
188
181
  unique_controls << unique_control
@@ -192,9 +185,9 @@ module HeimdallTools
192
185
 
193
186
  def to_hdf
194
187
  host_results = {}
195
- @reports.each do | report|
188
+ @reports.each do |report|
196
189
  controls = []
197
- report['ReportItem'].each do | item |
190
+ report['ReportItem'].each do |item|
198
191
  printf("\rProcessing: %s", $spinner.next)
199
192
  @item = {}
200
193
  @item['tags'] = {}
@@ -207,7 +200,7 @@ module HeimdallTools
207
200
  # Current version covers STIG based 'Policy Compliance' results
208
201
  # TODO Cover cases for 'Policy Compliance' results based on CIS
209
202
  if item['compliance-reference']
210
- @item['id'] = parse_refs(item['compliance-reference'],'Vuln-ID').join.to_s
203
+ @item['id'] = parse_refs(item['compliance-reference'], 'Vuln-ID').join.to_s
211
204
  else
212
205
  @item['id'] = item['pluginID'].to_s
213
206
  end
@@ -222,17 +215,17 @@ module HeimdallTools
222
215
  @item['desc'] = format_desc(item).to_s
223
216
  end
224
217
  if item['compliance-reference']
225
- @item['impact'] = impact(parse_refs(item['compliance-reference'],'CAT').join.to_s)
218
+ @item['impact'] = impact(parse_refs(item['compliance-reference'], 'CAT').join.to_s)
226
219
  else
227
- @item['impact'] = impact(item['severity'])
220
+ @item['impact'] = impact(item['severity'])
228
221
  end
229
222
  if item['compliance-reference']
230
- @item['tags']['nist'] = cci_nist_tag(parse_refs(item['compliance-reference'],'CCI'))
223
+ @item['tags']['nist'] = cci_nist_tag(parse_refs(item['compliance-reference'], 'CCI'))
231
224
  else
232
- @item['tags']['nist'] = plugin_nist_tag(item['pluginFamily'],item['pluginID'])
225
+ @item['tags']['nist'] = plugin_nist_tag(item['pluginFamily'], item['pluginID'])
233
226
  end
234
227
  if item['compliance-solution']
235
- @item['descriptions'] << desc_tags(item['compliance-solution'], 'check')
228
+ @item['descriptions'] << desc_tags(item['compliance-solution'], 'check')
236
229
  end
237
230
 
238
231
  @item['code'] = ''
@@ -0,0 +1,164 @@
1
+ require 'json'
2
+ require 'csv'
3
+ require 'heimdall_tools/hdf'
4
+ require 'utilities/xml_to_hash'
5
+
6
+ RESOURCE_DIR = Pathname.new(__FILE__).join('../../data')
7
+
8
+ CWE_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'cwe-nist-mapping.csv')
9
+ OWASP_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'owasp-nist-mapping.csv')
10
+
11
+ IMPACT_MAPPING = {
12
+ Critical: 1.0,
13
+ High: 0.7,
14
+ Medium: 0.5,
15
+ Low: 0.3,
16
+ Best_Practice: 0.0,
17
+ Information: 0.0
18
+ }.freeze
19
+
20
+ DEFAULT_NIST_TAG = %w{SA-11 RA-5}.freeze
21
+
22
+ module HeimdallTools
23
+ class NetsparkerMapper
24
+ def initialize(xml, _name = nil, verbose = false)
25
+ @verbose = verbose
26
+
27
+ begin
28
+ @cwe_nist_mapping = parse_mapper(CWE_NIST_MAPPING_FILE)
29
+ @owasp_nist_mapping = parse_mapper(OWASP_NIST_MAPPING_FILE)
30
+ data = xml_to_hash(xml)
31
+
32
+ @vulnerabilities = data['netsparker-enterprise']['vulnerabilities']['vulnerability']
33
+ @scan_info = data['netsparker-enterprise']['target']
34
+ rescue StandardError => e
35
+ raise "Invalid Netsparker XML file provided Exception: #{e}"
36
+ end
37
+ end
38
+
39
+ def to_hdf
40
+ controls = []
41
+ @vulnerabilities.each do |vulnerability|
42
+ @item = {}
43
+ @item['id'] = vulnerability['LookupId'].to_s
44
+ @item['title'] = vulnerability['name'].to_s
45
+ @item['desc'] = format_control_desc(vulnerability)
46
+ @item['impact'] = impact(vulnerability['severity'])
47
+ @item['tags'] = {}
48
+ @item['descriptions'] = []
49
+
50
+ @item['descriptions'] << desc_tags(format_check_text(vulnerability), 'check')
51
+ @item['descriptions'] << desc_tags(format_fix_text(vulnerability), 'fix')
52
+ @item['refs'] = NA_ARRAY
53
+ @item['source_location'] = NA_HASH
54
+ @item['tags']['nist'] = nist_tag(vulnerability['classification'])
55
+ @item['code'] = ''
56
+ @item['results'] = finding(vulnerability)
57
+
58
+ controls << @item
59
+ end
60
+ controls = collapse_duplicates(controls)
61
+ results = HeimdallDataFormat.new(profile_name: 'Netsparker Enterprise Scan',
62
+ title: "Netsparker Enterprise Scan ID: #{@scan_info['scan-id']} URL: #{@scan_info['url']}",
63
+ summary: 'Netsparker Enterprise Scan',
64
+ target_id: @scan_info['url'],
65
+ controls: controls)
66
+ results.to_hdf
67
+ end
68
+
69
+ private
70
+
71
+ def parse_html(block)
72
+ block['#cdata-section'].to_s.strip unless block.nil?
73
+ end
74
+
75
+ def finding(vulnerability)
76
+ finding = {}
77
+ finding['status'] = 'failed'
78
+ finding['code_desc'] = []
79
+ finding['code_desc'] << "http-request : #{parse_html(vulnerability['http-request']['content'])}"
80
+ finding['code_desc'] << "method : #{vulnerability['http-request']['method']}"
81
+ finding['code_desc'] = finding['code_desc'].join("\n")
82
+
83
+ finding['message'] = []
84
+ finding['message'] << "http-response : #{parse_html(vulnerability['http-response']['content'])}"
85
+ finding['message'] << "duration : #{vulnerability['http-response']['duration']}"
86
+ finding['message'] << "status-code : #{vulnerability['http-response']['status-code']}"
87
+ finding['message'] = finding['message'].join("\n")
88
+ finding['run_time'] = NA_FLOAT
89
+
90
+ finding['start_time'] = @scan_info['initiated']
91
+ [finding]
92
+ end
93
+
94
+ def format_control_desc(vulnerability)
95
+ text = []
96
+ text << parse_html(vulnerability['description']).to_s unless vulnerability['description'].nil?
97
+ text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
98
+ text << "Extra-information: #{vulnerability['extra-information']}" unless vulnerability['extra-information'].nil?
99
+ text << "Classification: #{vulnerability['classification']}" unless vulnerability['classification'].nil?
100
+ text << "Impact: #{parse_html(vulnerability['impact'])}" unless vulnerability['impact'].nil?
101
+ text << "FirstSeenDate: #{vulnerability['FirstSeenDate']}" unless vulnerability['FirstSeenDate'].nil?
102
+ text << "LastSeenDate: #{vulnerability['LastSeenDate']}" unless vulnerability['LastSeenDate'].nil?
103
+ text << "Certainty: #{vulnerability['certainty']}" unless vulnerability['certainty'].nil?
104
+ text << "Type: #{vulnerability['type']}" unless vulnerability['type'].nil?
105
+ text << "Confirmed: #{vulnerability['confirmed']}" unless vulnerability['confirmed'].nil?
106
+ text.join('<br>')
107
+ end
108
+
109
+ def format_check_text(vulnerability)
110
+ text = []
111
+ text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
112
+ text << "Proof-of-concept: #{parse_html(vulnerability['proof-of-concept'])}" unless vulnerability['proof-of-concept'].nil?
113
+ text.join('<br>')
114
+ end
115
+
116
+ def format_fix_text(vulnerability)
117
+ text = []
118
+ text << "Remedial-actions: #{parse_html(vulnerability['remedial-actions'])}" unless vulnerability['remedial-actions'].nil?
119
+ text << "Remedial-procedure: #{parse_html(vulnerability['remedial-procedure'])}" unless vulnerability['remedial-procedure'].nil?
120
+ text << "Remedy-references: #{parse_html(vulnerability['remedy-references'])}" unless vulnerability['remedy-references'].nil?
121
+ text.join('<br>')
122
+ end
123
+
124
+ def nist_tag(classification)
125
+ tags = []
126
+ entries = @cwe_nist_mapping.select { |x| classification['cwe'].include?(x[:cweid].to_s) && !x[:nistid].nil? }
127
+ tags << entries.map { |x| x[:nistid] }
128
+ entries = @owasp_nist_mapping.select { |x| classification['owasp'].include?(x[:owaspid].to_s) && !x[:nistid].nil? }
129
+ tags << entries.map { |x| x[:nistid] }
130
+ tags.flatten.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
131
+ end
132
+
133
+ def impact(severity)
134
+ IMPACT_MAPPING[severity.to_sym]
135
+ end
136
+
137
+ def parse_mapper(mapping_file)
138
+ csv_data = CSV.read(mapping_file, { encoding: 'UTF-8',
139
+ headers: true,
140
+ header_converters: :symbol,
141
+ converters: :all })
142
+ csv_data.map(&:to_hash)
143
+ end
144
+
145
+ def desc_tags(data, label)
146
+ { data: data || NA_STRING, label: label || NA_STRING }
147
+ end
148
+
149
+ # Netsparker report could have multiple issue entries for multiple findings of same issue type.
150
+ # The meta data is identical across entries
151
+ # method collapse_duplicates return unique controls with applicable findings collapsed into it.
152
+ def collapse_duplicates(controls)
153
+ unique_controls = []
154
+
155
+ controls.map { |x| x['id'] }.uniq.each do |id|
156
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map { |x| x['results'] }
157
+ unique_control = controls.find { |x| x['id'].eql?(id) }
158
+ unique_control['results'] = collapsed_results.flatten
159
+ unique_controls << unique_control
160
+ end
161
+ unique_controls
162
+ end
163
+ end
164
+ end