heimdall_tools 1.3.40 → 1.3.45

Sign up to get free protection for your applications and to get access to all the features.
@@ -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