heimdall_tools 1.3.40 → 1.3.45

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.
@@ -17,26 +17,19 @@ IMPACT_MAPPING = {
17
17
  Information: 0.0
18
18
  }.freeze
19
19
 
20
- DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze
21
-
22
- # rubocop:disable Metrics/AbcSize
20
+ DEFAULT_NIST_TAG = %w{SA-11 RA-5}.freeze
23
21
 
24
22
  module HeimdallTools
25
23
  class NetsparkerMapper
26
- def initialize(xml, name=nil, verbose = false)
27
- @verbose = verbose
28
-
29
- begin
30
- @cwe_nist_mapping = parse_mapper(CWE_NIST_MAPPING_FILE)
31
- @owasp_nist_mapping = parse_mapper(OWASP_NIST_MAPPING_FILE)
32
- data = xml_to_hash(xml)
33
-
34
- @vulnerabilities = data['netsparker-enterprise']['vulnerabilities']['vulnerability']
35
- @scan_info = data['netsparker-enterprise']['target']
36
-
37
- rescue StandardError => e
38
- raise "Invalid Netsparker XML file provided Exception: #{e}"
39
- end
24
+ def initialize(xml, _name = nil)
25
+ @cwe_nist_mapping = parse_mapper(CWE_NIST_MAPPING_FILE)
26
+ @owasp_nist_mapping = parse_mapper(OWASP_NIST_MAPPING_FILE)
27
+ data = xml_to_hash(xml)
28
+
29
+ @vulnerabilities = data['netsparker-enterprise']['vulnerabilities']['vulnerability']
30
+ @scan_info = data['netsparker-enterprise']['target']
31
+ rescue StandardError => e
32
+ raise "Invalid Netsparker XML file provided Exception: #{e}"
40
33
  end
41
34
 
42
35
  def to_hdf
@@ -63,7 +56,7 @@ module HeimdallTools
63
56
  controls = collapse_duplicates(controls)
64
57
  results = HeimdallDataFormat.new(profile_name: 'Netsparker Enterprise Scan',
65
58
  title: "Netsparker Enterprise Scan ID: #{@scan_info['scan-id']} URL: #{@scan_info['url']}",
66
- summary: "Netsparker Enterprise Scan",
59
+ summary: 'Netsparker Enterprise Scan',
67
60
  target_id: @scan_info['url'],
68
61
  controls: controls)
69
62
  results.to_hdf
@@ -79,25 +72,25 @@ module HeimdallTools
79
72
  finding = {}
80
73
  finding['status'] = 'failed'
81
74
  finding['code_desc'] = []
82
- finding['code_desc'] << "http-request : #{parse_html(vulnerability['http-request']['content']) }"
75
+ finding['code_desc'] << "http-request : #{parse_html(vulnerability['http-request']['content'])}"
83
76
  finding['code_desc'] << "method : #{vulnerability['http-request']['method']}"
84
77
  finding['code_desc'] = finding['code_desc'].join("\n")
85
78
 
86
79
  finding['message'] = []
87
- finding['message'] << "http-response : #{parse_html(vulnerability['http-response']['content']) }"
80
+ finding['message'] << "http-response : #{parse_html(vulnerability['http-response']['content'])}"
88
81
  finding['message'] << "duration : #{vulnerability['http-response']['duration']}"
89
82
  finding['message'] << "status-code : #{vulnerability['http-response']['status-code']}"
90
83
  finding['message'] = finding['message'].join("\n")
91
84
  finding['run_time'] = NA_FLOAT
92
85
 
93
- finding['start_time'] = @scan_info['initiated']
86
+ finding['start_time'] = @scan_info['initiated']
94
87
  [finding]
95
88
  end
96
89
 
97
90
  def format_control_desc(vulnerability)
98
91
  text = []
99
- text << "#{parse_html(vulnerability['description'])}" unless vulnerability['description'].nil?
100
- text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
92
+ text << parse_html(vulnerability['description']).to_s unless vulnerability['description'].nil?
93
+ text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
101
94
  text << "Extra-information: #{vulnerability['extra-information']}" unless vulnerability['extra-information'].nil?
102
95
  text << "Classification: #{vulnerability['classification']}" unless vulnerability['classification'].nil?
103
96
  text << "Impact: #{parse_html(vulnerability['impact'])}" unless vulnerability['impact'].nil?
@@ -106,14 +99,14 @@ module HeimdallTools
106
99
  text << "Certainty: #{vulnerability['certainty']}" unless vulnerability['certainty'].nil?
107
100
  text << "Type: #{vulnerability['type']}" unless vulnerability['type'].nil?
108
101
  text << "Confirmed: #{vulnerability['confirmed']}" unless vulnerability['confirmed'].nil?
109
- text.join("<br>")
102
+ text.join('<br>')
110
103
  end
111
104
 
112
105
  def format_check_text(vulnerability)
113
106
  text = []
114
107
  text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
115
108
  text << "Proof-of-concept: #{parse_html(vulnerability['proof-of-concept'])}" unless vulnerability['proof-of-concept'].nil?
116
- text.join("<br>")
109
+ text.join('<br>')
117
110
  end
118
111
 
119
112
  def format_fix_text(vulnerability)
@@ -121,7 +114,7 @@ module HeimdallTools
121
114
  text << "Remedial-actions: #{parse_html(vulnerability['remedial-actions'])}" unless vulnerability['remedial-actions'].nil?
122
115
  text << "Remedial-procedure: #{parse_html(vulnerability['remedial-procedure'])}" unless vulnerability['remedial-procedure'].nil?
123
116
  text << "Remedy-references: #{parse_html(vulnerability['remedy-references'])}" unless vulnerability['remedy-references'].nil?
124
- text.join("<br>")
117
+ text.join('<br>')
125
118
  end
126
119
 
127
120
  def nist_tag(classification)
@@ -146,7 +139,7 @@ module HeimdallTools
146
139
  end
147
140
 
148
141
  def desc_tags(data, label)
149
- { "data": data || NA_STRING, "label": label || NA_STRING }
142
+ { data: data || NA_STRING, label: label || NA_STRING }
150
143
  end
151
144
 
152
145
  # Netsparker report could have multiple issue entries for multiple findings of same issue type.
@@ -156,7 +149,7 @@ module HeimdallTools
156
149
  unique_controls = []
157
150
 
158
151
  controls.map { |x| x['id'] }.uniq.each do |id|
159
- collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
152
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map { |x| x['results'] }
160
153
  unique_control = controls.find { |x| x['id'].eql?(id) }
161
154
  unique_control['results'] = collapsed_results.flatten
162
155
  unique_controls << unique_control
@@ -9,10 +9,10 @@ NIKTO_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'nikto-nist-mapping.csv')
9
9
  IMPACT_MAPPING = {
10
10
  high: 0.7,
11
11
  medium: 0.5,
12
- low: 0.3,
12
+ low: 0.3
13
13
  }.freeze
14
14
 
15
- DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze
15
+ DEFAULT_NIST_TAG = %w{SA-11 RA-5}.freeze
16
16
 
17
17
  # Loading spinner sign
18
18
  $spinner = Enumerator.new do |e|
@@ -26,9 +26,8 @@ end
26
26
 
27
27
  module HeimdallTools
28
28
  class NiktoMapper
29
- def initialize(nikto_json, name=nil, verbose = false)
29
+ def initialize(nikto_json, _name = nil)
30
30
  @nikto_json = nikto_json
31
- @verbose = verbose
32
31
 
33
32
  begin
34
33
  @nikto_nist_mapping = parse_mapper
@@ -36,9 +35,9 @@ module HeimdallTools
36
35
  raise "Invalid Nikto to NIST mapping file: Exception: #{e}"
37
36
  end
38
37
 
39
- # TODO: Support Multi-target scan results
40
- # Nikto multi-target scans generate invalid format JSONs
41
- # Possible workaround to use https://stackoverflow.com/a/58209963/1670307
38
+ # TODO: Support Multi-target scan results
39
+ # Nikto multi-target scans generate invalid format JSONs
40
+ # Possible workaround to use https://stackoverflow.com/a/58209963/1670307
42
41
 
43
42
  begin
44
43
  @project = JSON.parse(nikto_json)
@@ -64,7 +63,7 @@ module HeimdallTools
64
63
  def finding(vulnerability)
65
64
  finding = {}
66
65
  finding['status'] = 'failed'
67
- finding['code_desc'] = "URL : #{vulnerability['url'].to_s } Method: #{vulnerability['method'].to_s}"
66
+ finding['code_desc'] = "URL : #{vulnerability['url']} Method: #{vulnerability['method']}"
68
67
  finding['run_time'] = NA_FLOAT
69
68
  finding['start_time'] = NA_STRING
70
69
  [finding]
@@ -83,32 +82,32 @@ module HeimdallTools
83
82
  def parse_mapper
84
83
  csv_data = CSV.read(NIKTO_NIST_MAPPING_FILE, **{ encoding: 'UTF-8',
85
84
  headers: true,
86
- header_converters: :symbol})
85
+ header_converters: :symbol })
87
86
  csv_data.map(&:to_hash)
88
87
  end
89
88
 
90
89
  def desc_tags(data, label)
91
- { "data": data || NA_STRING, "label": label || NA_STRING }
90
+ { data: data || NA_STRING, label: label || NA_STRING }
92
91
  end
93
92
 
94
- # Nikto report could have multiple vulnerability entries for multiple findings of same issue type.
95
- # The meta data is identical across entries
96
- # method collapse_duplicates return unique controls with applicable findings collapsed into it.
97
- def collapse_duplicates(controls)
98
- unique_controls = []
99
-
100
- controls.map { |x| x['id'] }.uniq.each do |id|
101
- collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
102
- unique_control = controls.find { |x| x['id'].eql?(id) }
103
- unique_control['results'] = collapsed_results.flatten
104
- unique_controls << unique_control
105
- end
106
- unique_controls
107
- end
93
+ # Nikto report could have multiple vulnerability entries for multiple findings of same issue type.
94
+ # The meta data is identical across entries
95
+ # method collapse_duplicates return unique controls with applicable findings collapsed into it.
96
+ def collapse_duplicates(controls)
97
+ unique_controls = []
98
+
99
+ controls.map { |x| x['id'] }.uniq.each do |id|
100
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map { |x| x['results'] }
101
+ unique_control = controls.find { |x| x['id'].eql?(id) }
102
+ unique_control['results'] = collapsed_results.flatten
103
+ unique_controls << unique_control
104
+ end
105
+ unique_controls
106
+ end
108
107
 
109
108
  def to_hdf
110
109
  controls = []
111
- @project['vulnerabilities'].each do | vulnerability |
110
+ @project['vulnerabilities'].each do |vulnerability|
112
111
  printf("\rProcessing: %s", $spinner.next)
113
112
 
114
113
  item = {}
@@ -125,11 +124,11 @@ module HeimdallTools
125
124
  # Duplicating vulnerability msg field
126
125
  item['desc'] = vulnerability['msg'].to_s
127
126
 
128
- # Nitko does not provide finding severity; hard-coding severity to medium
129
- item['impact'] = impact('medium')
127
+ # Nitko does not provide finding severity; hard-coding severity to medium
128
+ item['impact'] = impact('medium')
130
129
  item['code'] = NA_STRING
131
130
  item['results'] = finding(vulnerability)
132
- item['tags']['nist'] = nist_tag( vulnerability['id'].to_s )
131
+ item['tags']['nist'] = nist_tag(vulnerability['id'].to_s)
133
132
  item['tags']['ösvdb'] = vulnerability['OSVDB']
134
133
 
135
134
  controls << item
@@ -10,12 +10,12 @@ 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
16
  SNYK_VERSION_REGEX = 'v(\d+.)(\d+.)(\d+)'.freeze
17
17
 
18
- DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze
18
+ DEFAULT_NIST_TAG = %w{SA-11 RA-5}.freeze
19
19
 
20
20
  # Loading spinner sign
21
21
  $spinner = Enumerator.new do |e|
@@ -29,19 +29,17 @@ end
29
29
 
30
30
  module HeimdallTools
31
31
  class SnykMapper
32
- def initialize(synk_json, name=nil, verbose = false)
32
+ def initialize(synk_json, _name = nil)
33
33
  @synk_json = synk_json
34
- @verbose = verbose
35
34
 
36
35
  begin
37
36
  @cwe_nist_mapping = parse_mapper
38
37
  @projects = JSON.parse(synk_json)
39
38
 
40
39
  # Cover single and multi-project scan use cases.
41
- unless @projects.kind_of?(Array)
42
- @projects = [ @projects ]
40
+ unless @projects.is_a?(Array)
41
+ @projects = [@projects]
43
42
  end
44
-
45
43
  rescue StandardError => e
46
44
  raise "Invalid Snyk JSON file provided Exception: #{e}"
47
45
  end
@@ -52,7 +50,7 @@ module HeimdallTools
52
50
  begin
53
51
  info['policy'] = project['policy']
54
52
  reg = Regexp.new(SNYK_VERSION_REGEX, Regexp::IGNORECASE)
55
- info['version'] = info['policy'].scan(reg).join
53
+ info['version'] = info['policy'].scan(reg).join
56
54
  info['projectName'] = project['projectName']
57
55
  info['summary'] = project['summary']
58
56
 
@@ -65,7 +63,7 @@ module HeimdallTools
65
63
  def finding(vulnerability)
66
64
  finding = {}
67
65
  finding['status'] = 'failed'
68
- finding['code_desc'] = "From : [ #{vulnerability['from'].join(" , ").to_s } ]"
66
+ finding['code_desc'] = "From : [ #{vulnerability['from'].join(' , ')} ]"
69
67
  finding['run_time'] = NA_FLOAT
70
68
 
71
69
  # Snyk results does not profile scan timestamp; using current time to satisfy HDF format
@@ -81,9 +79,9 @@ module HeimdallTools
81
79
 
82
80
  def parse_identifiers(vulnerability, ref)
83
81
  # Extracting id number from reference style CWE-297
84
- vulnerability['identifiers'][ref].map { |e| e.split("#{ref}-")[1] }
85
- rescue
86
- return []
82
+ vulnerability['identifiers'][ref].map { |e| e.split("#{ref}-")[1] }
83
+ rescue StandardError
84
+ []
87
85
  end
88
86
 
89
87
  def impact(severity)
@@ -99,17 +97,17 @@ module HeimdallTools
99
97
  end
100
98
 
101
99
  def desc_tags(data, label)
102
- { "data": data || NA_STRING, "label": label || NA_STRING }
100
+ { data: data || NA_STRING, label: label || NA_STRING }
103
101
  end
104
102
 
105
103
  # Snyk report could have multiple vulnerability entries for multiple findings of same issue type.
106
- # The meta data is identical across entries
104
+ # The meta data is identical across entries
107
105
  # method collapse_duplicates return unique controls with applicable findings collapsed into it.
108
106
  def collapse_duplicates(controls)
109
107
  unique_controls = []
110
108
 
111
109
  controls.map { |x| x['id'] }.uniq.each do |id|
112
- collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
110
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map { |x| x['results'] }
113
111
  unique_control = controls.find { |x| x['id'].eql?(id) }
114
112
  unique_control['results'] = collapsed_results.flatten
115
113
  unique_controls << unique_control
@@ -117,12 +115,11 @@ module HeimdallTools
117
115
  unique_controls
118
116
  end
119
117
 
120
-
121
118
  def to_hdf
122
119
  project_results = {}
123
- @projects.each do | project |
120
+ @projects.each do |project|
124
121
  controls = []
125
- project['vulnerabilities'].each do | vulnerability |
122
+ project['vulnerabilities'].each do |vulnerability|
126
123
  printf("\rProcessing: %s", $spinner.next)
127
124
 
128
125
  item = {}
@@ -135,13 +132,13 @@ module HeimdallTools
135
132
  item['title'] = vulnerability['title'].to_s
136
133
  item['id'] = vulnerability['id'].to_s
137
134
  item['desc'] = vulnerability['description'].to_s
138
- item['impact'] = impact(vulnerability['severity'])
135
+ item['impact'] = impact(vulnerability['severity'])
139
136
  item['code'] = ''
140
137
  item['results'] = finding(vulnerability)
141
- item['tags']['nist'] = nist_tag( parse_identifiers( vulnerability, 'CWE') )
142
- item['tags']['cweid'] = parse_identifiers( vulnerability, 'CWE')
143
- item['tags']['cveid'] = parse_identifiers( vulnerability, 'CVE')
144
- item['tags']['ghsaid'] = parse_identifiers( vulnerability, 'GHSA')
138
+ item['tags']['nist'] = nist_tag(parse_identifiers(vulnerability, 'CWE'))
139
+ item['tags']['cweid'] = parse_identifiers(vulnerability, 'CWE')
140
+ item['tags']['cveid'] = parse_identifiers(vulnerability, 'CVE')
141
+ item['tags']['ghsaid'] = parse_identifiers(vulnerability, 'GHSA')
145
142
 
146
143
  controls << item
147
144
  end
@@ -5,7 +5,7 @@ require 'heimdall_tools/hdf'
5
5
 
6
6
  RESOURCE_DIR = Pathname.new(__FILE__).join('../../data')
7
7
 
8
- DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze
8
+ DEFAULT_NIST_TAG = %w{SA-11 RA-5}.freeze
9
9
 
10
10
  MAPPING_FILES = {
11
11
  cwe: File.join(RESOURCE_DIR, 'cwe-nist-mapping.csv'),
@@ -33,16 +33,18 @@ class SonarQubeApi
33
33
 
34
34
  PAGE_SIZE = 100
35
35
 
36
- def initialize(api_url, auth=nil)
36
+ def initialize(api_url, auth = nil)
37
37
  @api_url = api_url
38
38
  @auth = auth
39
39
  end
40
40
 
41
- def query_api(endpoint, params={})
42
- creds = {
43
- username: @auth.split(':')[0],
44
- password: @auth.split(':')[1]
45
- } unless @auth.nil?
41
+ def query_api(endpoint, params = {})
42
+ unless @auth.nil?
43
+ creds = {
44
+ username: @auth.split(':')[0],
45
+ password: @auth.split(':')[1]
46
+ }
47
+ end
46
48
 
47
49
  response = HTTParty.get(@api_url + endpoint, { query: params, basic_auth: creds })
48
50
  check_response response
@@ -109,9 +111,9 @@ end
109
111
  module HeimdallTools
110
112
  class SonarQubeMapper
111
113
  # Fetches the necessary data from the API and builds report
112
- def initialize(project_name, sonarqube_url, auth=nil)
114
+ def initialize(project_name, sonarqube_url, auth = nil)
113
115
  @project_name = project_name
114
- @api = SonarQubeApi.new(sonarqube_url,auth)
116
+ @api = SonarQubeApi.new(sonarqube_url, auth)
115
117
 
116
118
  @mappings = load_nist_mappings
117
119
  @findings = @api.query_issues(@project_name).map { |x| Finding.new(x, @api) }
@@ -132,16 +134,16 @@ module HeimdallTools
132
134
  headers: true,
133
135
  header_converters: :symbol,
134
136
  converters: :all })
135
- mappings[mapping_type] = Hash[csv_data.reject{ |row| row[:nistid].nil? }.map { |row|
136
- [row[(mapping_type.to_s.downcase + 'id').to_sym].to_s, [row[:nistid], "Rev_#{row[:rev]}"]]
137
- }]
137
+ mappings[mapping_type] = csv_data.reject { |row| row[:nistid].nil? }.map { |row|
138
+ [row["#{mapping_type.to_s.downcase}id".to_sym].to_s, [row[:nistid], "Rev_#{row[:rev]}"]]
139
+ }.to_h
138
140
  end
139
141
  mappings
140
142
  end
141
143
 
142
144
  # Returns a report in HDF format
143
145
  def to_hdf
144
- results = HeimdallDataFormat.new(profile_name: "SonarQube Scan",
146
+ results = HeimdallDataFormat.new(profile_name: 'SonarQube Scan',
145
147
  version: @api.query_version,
146
148
  title: "SonarQube Scan of Project: #{@project_name}",
147
149
  summary: "SonarQube Scan of Project: #{@project_name}",
@@ -156,7 +158,7 @@ class Control
156
158
  # OWASP is stated specifically, ex owasp-a1
157
159
  #
158
160
  # SonarQube is inconsistent with tags (ex some cwe rules don't have cwe number in desc,) as noted below
159
- TAG_DATA = {} # NOTE: We count on Ruby to preserve order for TAG_DATA
161
+ TAG_DATA = {}.freeze # NOTE: We count on Ruby to preserve order for TAG_DATA
160
162
  TAG_DATA[:cwe] = {
161
163
  # Some rules with cwe tag don't have cwe number in description!
162
164
  # Currently only squid:S2658, but it has OWASP tag so we can use that.
@@ -206,8 +208,8 @@ class Control
206
208
  reg = Regexp.new(tag_data[:regex], Regexp::IGNORECASE)
207
209
  parsed_tags += @data['htmlDesc'].scan(reg).map(&:first)
208
210
 
209
- if parsed_tags.empty? and not KNOWN_BAD_RULES.include? @key
210
- puts "Error: Rule #{@key}: No regex matches for #{tag_type} tag." if parsed_tags.empty?
211
+ if parsed_tags.empty? and not KNOWN_BAD_RULES.include? @key && parsed_tags.empty?
212
+ puts "Error: Rule #{@key}: No regex matches for #{tag_type} tag."
211
213
  end
212
214
  else
213
215
  # If the tag type doesn't have a regex, it is specific enough to be mapped directly
@@ -239,11 +241,11 @@ class Control
239
241
  return [@mappings[tag_type][parsed_tag]].flatten.uniq
240
242
  end
241
243
 
242
- DEFAULT_NIST_TAG # Entries with unmapped NIST tags are defaulted to NIST tags ‘SA-11, RA-5 Rev_4’
244
+ DEFAULT_NIST_TAG # Entries with unmapped NIST tags fall back to defaults
243
245
  end
244
246
 
245
247
  def hdf
246
- # Note: Structure is based on fortify -> HDF converter output
248
+ # NOTE: Structure is based on fortify -> HDF converter output
247
249
  {
248
250
  title: @data['name'],
249
251
  desc: @data['htmlDesc'],
@@ -256,7 +258,7 @@ class Control
256
258
  id: @key,
257
259
  descriptions: NA_ARRAY,
258
260
  refs: NA_ARRAY,
259
- source_location: NA_HASH,
261
+ source_location: NA_HASH
260
262
  }
261
263
  end
262
264
  end
@@ -284,10 +286,10 @@ class Finding
284
286
 
285
287
  snip_html = "StartLine: #{snip_start}, EndLine: #{snip_end}<br>Code:<pre>#{snip}</pre>"
286
288
  {
287
- status: 'failed',
289
+ status: 'failed',
288
290
  code_desc: "Path:#{component}:#{vuln_start}:#{vuln_end} #{snip_html}",
289
291
  run_time: NA_FLOAT,
290
- start_time: Time.now.strftime("%a,%d %b %Y %X")
292
+ start_time: Time.now.strftime('%a,%d %b %Y %X')
291
293
  }
292
294
  end
293
295
  end