Skip to content

Commit ee48083

Browse files
authored
Merge pull request #59 from mitre/synk_mapper
2 parents eefe425 + b3294fb commit ee48083

File tree

6 files changed

+202
-1
lines changed

6 files changed

+202
-1
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
matrix:
1313
platform: [ubuntu-16.04, ubuntu-latest, macos-latest, windows-latest]
14-
ruby: [ 2.4, 2.5, 2.6, 2.7 ]
14+
ruby: [ 2.5, 2.6, 2.7 ]
1515
# TODO: Remove this exclusion once Nokogiri is supported on ruby 2.7 for Windows
1616
exclude:
1717
- platform: windows-latest

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ HeimdallTools supplies several methods to convert output from various tools to "
1010
- **zap_mapper** - OWASP ZAP - open-source dynamic code analysis tool
1111
- **burpsuite_mapper** - commercial dynamic analysis tool
1212
- **nessus_mapper** - commercial vulnerability scanner
13+
- **snyk_mapper** - commercial package vulnerability scanner
1314

1415
Ruby 2.4 or higher (check using "ruby -v")
1516

@@ -145,6 +146,23 @@ FLAGS:
145146
example: heimdall_tools nessus_mapper -x nessus-results.xml -o test-env
146147
```
147148

149+
## snyk_mapper
150+
151+
snyk_mapper translates an Snyk results JSON file into HDF format json to be viewable in Heimdall
152+
153+
Note: A separate HDF JSON is generated for each project reported in the Snyk Report.
154+
155+
```
156+
USAGE: heimdall_tools snyk_mapper [OPTIONS] -x <snyk-results-json> -o <hdf-file-prefix>
157+
158+
FLAGS:
159+
-j <snyk_results_jsonl> : path to Snyk results JSON file.
160+
-o --output_prefix <prefix> : path to output scan-results json.
161+
-V --verbose : verbose run [optional].
162+
163+
example: heimdall_tools snyk_mapper -j snyk_results.json -o output-file-prefix
164+
```
165+
148166
## version
149167

150168
Prints out the gem version

lib/heimdall_tools.rb

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ module HeimdallTools
1010
autoload :SonarQubeMapper, 'heimdall_tools/sonarqube_mapper'
1111
autoload :BurpSuiteMapper, 'heimdall_tools/burpsuite_mapper'
1212
autoload :NessusMapper, 'heimdall_tools/nessus_mapper'
13+
autoload :SnykMapper, 'heimdall_tools/snyk_mapper'
1314
end

lib/heimdall_tools/cli.rb

+14
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ def nessus_mapper
6161

6262
end
6363

64+
desc 'snyk_mapper', 'snyk_mapper translates Synk results Json to HDF format Json be viewed on Heimdall'
65+
long_desc Help.text(:fortify_mapper)
66+
option :json, required: true, aliases: '-j'
67+
option :output_prefix, required: true, aliases: '-o'
68+
option :verbose, type: :boolean, aliases: '-V'
69+
def snyk_mapper
70+
hdfs = HeimdallTools::SnykMapper.new(File.read(options[:json]), options[:name]).to_hdf
71+
puts "\r\HDF Generated:\n"
72+
hdfs.keys.each do | host |
73+
File.write("#{options[:output_prefix]}-#{host}.json", hdfs[host])
74+
puts "#{options[:output_prefix]}-#{host}.json"
75+
end
76+
end
77+
6478
desc 'version', 'prints version'
6579
def version
6680
puts VERSION
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
snyk_mapper translates an Snyk results JSON file into HDF format json to be viewable in Heimdall
2+
3+
A separate HDF JSON is generated for each project reported in the Snyk Report.
4+
5+
Examples:
6+
7+
heimdall_tools snyk_mapper -j snyk_results.json -o output-file-prefix

lib/heimdall_tools/snyk_mapper.rb

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
10+
IMPACT_MAPPING = {
11+
high: 0.7,
12+
medium: 0.5,
13+
low: 0.3,
14+
}.freeze
15+
16+
SNYK_VERSION_REGEX = 'v(\d+.)(\d+.)(\d+)'.freeze
17+
18+
DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze
19+
20+
# Loading spinner sign
21+
$spinner = Enumerator.new do |e|
22+
loop do
23+
e.yield '|'
24+
e.yield '/'
25+
e.yield '-'
26+
e.yield '\\'
27+
end
28+
end
29+
30+
module HeimdallTools
31+
class SnykMapper
32+
def initialize(synk_json, name=nil, verbose = false)
33+
@synk_json = synk_json
34+
@verbose = verbose
35+
36+
begin
37+
@cwe_nist_mapping = parse_mapper
38+
@projects = JSON.parse(synk_json)
39+
40+
# Cover single and multi-project scan use cases.
41+
unless @projects.kind_of?(Array)
42+
@projects = [ @projects ]
43+
end
44+
45+
rescue StandardError => e
46+
raise "Invalid Snyk JSON file provided Exception: #{e}"
47+
end
48+
end
49+
50+
def extract_scaninfo(project)
51+
info = {}
52+
begin
53+
info['policy'] = project['policy']
54+
reg = Regexp.new(SNYK_VERSION_REGEX, Regexp::IGNORECASE)
55+
info['version'] = info['policy'].scan(reg).join
56+
info['projectName'] = project['projectName']
57+
info['summary'] = project['summary']
58+
59+
info
60+
rescue StandardError => e
61+
raise "Error extracting project info from Synk JSON file provided Exception: #{e}"
62+
end
63+
end
64+
65+
def finding(vulnerability)
66+
finding = {}
67+
finding['status'] = 'failed'
68+
finding['code_desc'] = "From : [ #{vulnerability['from'].join(" , ").to_s } ]"
69+
finding['run_time'] = NA_FLOAT
70+
71+
# Snyk results does not profile scan timestamp; using current time to satisfy HDF format
72+
finding['start_time'] = NA_STRING
73+
[finding]
74+
end
75+
76+
def nist_tag(cweid)
77+
entries = @cwe_nist_mapping.select { |x| cweid.include? x[:cweid].to_s }
78+
tags = entries.map { |x| x[:nistid] }
79+
tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
80+
end
81+
82+
def parse_identifiers(vulnerability, ref)
83+
# Extracting id number from reference style CWE-297
84+
vulnerability['identifiers'][ref].map { |e| e.split("#{ref}-")[1] }
85+
rescue
86+
return []
87+
end
88+
89+
def impact(severity)
90+
IMPACT_MAPPING[severity.to_sym]
91+
end
92+
93+
def parse_mapper
94+
csv_data = CSV.read(CWE_NIST_MAPPING_FILE, **{ encoding: 'UTF-8',
95+
headers: true,
96+
header_converters: :symbol,
97+
converters: :all })
98+
csv_data.map(&:to_hash)
99+
end
100+
101+
def desc_tags(data, label)
102+
{ "data": data || NA_STRING, "label": label || NA_STRING }
103+
end
104+
105+
# Snyk report could have multiple vulnerability entries for multiple findings of same issue type.
106+
# The meta data is identical across entries
107+
# method collapse_duplicates return unique controls with applicable findings collapsed into it.
108+
def collapse_duplicates(controls)
109+
unique_controls = []
110+
111+
controls.map { |x| x['id'] }.uniq.each do |id|
112+
collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
113+
unique_control = controls.find { |x| x['id'].eql?(id) }
114+
unique_control['results'] = collapsed_results.flatten
115+
unique_controls << unique_control
116+
end
117+
unique_controls
118+
end
119+
120+
121+
def to_hdf
122+
project_results = {}
123+
@projects.each do | project |
124+
controls = []
125+
project['vulnerabilities'].each do | vulnerability |
126+
printf("\rProcessing: %s", $spinner.next)
127+
128+
item = {}
129+
item['tags'] = {}
130+
item['descriptions'] = []
131+
item['refs'] = NA_ARRAY
132+
item['source_location'] = NA_HASH
133+
item['descriptions'] = NA_ARRAY
134+
135+
item['title'] = vulnerability['title'].to_s
136+
item['id'] = vulnerability['id'].to_s
137+
item['desc'] = vulnerability['description'].to_s
138+
item['impact'] = impact(vulnerability['severity'])
139+
item['code'] = ''
140+
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')
145+
146+
controls << item
147+
end
148+
controls = collapse_duplicates(controls)
149+
scaninfo = extract_scaninfo(project)
150+
results = HeimdallDataFormat.new(profile_name: scaninfo['policy'],
151+
version: scaninfo['version'],
152+
title: "Snyk Project: #{scaninfo['projectName']}",
153+
summary: "Snyk Summary: #{scaninfo['summary']}",
154+
controls: controls,
155+
target_id: scaninfo['projectName'])
156+
project_results[scaninfo['projectName']] = results.to_hdf
157+
end
158+
project_results
159+
end
160+
end
161+
end

0 commit comments

Comments
 (0)