heimdall_tools 1.3.36 → 1.3.40

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34a80978c354919fb48f33582e9a6b4e2676eb884c1868e8d710daeb6c8bfb9f
4
- data.tar.gz: 58a1f7cde61bf0ad07454fd3cc90448ad40385a9f9d8dd951f24901ccce7a79d
3
+ metadata.gz: 1a306a3ebf9a2755760b9cc825f1123a62291723d92d215c03d3e9d958d22497
4
+ data.tar.gz: 5e551876a20872c32126a6c96d08e3c6cd6acdf73bc31abf1ba918693764d4e9
5
5
  SHA512:
6
- metadata.gz: 49faba0d70053387a28208efc837a3978bb100e44d65436e0c8f9f6d335d1d8639987b77b44607a2d195cbafaf4646e2e4b80894a8ebb06a7206a929e2a115d3
7
- data.tar.gz: 42c71d905a92366eb864113d86de09997cf1043133049d6e16efbc34967a5af2d648d4cb31b6a92fb17874941be8c92f367abcad3d37c26bb8f6b4d46d600f56
6
+ metadata.gz: 4ed8a026a5fbbd63d3da4ebb4211d8ee8cc371ae29e57f4deb10f9e188491af29b2988e17524aa5ab4c064f62a44349edfa9b626641c89d6a958ff040df39e40
7
+ data.tar.gz: 2874bbd8f062e601ed1fb65b53ec53ee5267d7c17457cef91f936dd28936bac6f35b4387c3d452c301c593cdb13e01ea6d587c1885a88b1a6f025ac3ee38bdaa
data/README.md CHANGED
@@ -14,6 +14,8 @@ HeimdallTools supplies several methods to convert output from various tools to "
14
14
  - **nikto_mapper** - open-source web server scanner
15
15
  - **jfrog_xray_mapper** - package vulnerability scanner
16
16
  - **dbprotect_mapper** - database vulnerability scanner
17
+ - **aws_config_mapper** - assess, audit, and evaluate AWS resources
18
+ - **netsparker_mapper** - web application security scanner
17
19
 
18
20
  Ruby 2.4 or higher (check using "ruby -v")
19
21
 
@@ -213,6 +215,43 @@ FLAGS:
213
215
  example: heimdall_tools dbprotect_mapper -x check_results_details_report.xml -o db_protect_hdf.json
214
216
  ```
215
217
 
218
+ ## aws_config_mapper
219
+
220
+ aws_config_mapper pulls Ruby AWS SDK data to translate AWS Config Rule results into HDF format json to be viewable in Heimdall
221
+
222
+ ### AWS Config Rule Mapping:
223
+ The mapping of AWS Config Rules to 800-53 Controls was sourced from [this link](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-nist-800-53_rev_4.html).
224
+
225
+ ### Authentication with AWS:
226
+ [Developer Guide for configuring Ruby AWS SDK for authentication](https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html)
227
+
228
+ ```
229
+ USAGE: heimdall_tools aws_config_mapper [OPTIONS] -o <hdf-scan-results.json>
230
+
231
+ FLAGS:
232
+ -o --output <scan-results> : path to output scan-results json.
233
+ -V --verbose : verbose run [optional].
234
+
235
+ example: heimdall_tools aws_config_mapper -o aws_config_results_hdf.json
236
+ ```
237
+
238
+ ## netsparker_mapper
239
+
240
+ netsparker_mapper translates an Netsparker XML results file into HDF format JSON to be viewable in Heimdall.
241
+
242
+ The current iteration only works with Netsparker Enterprise Vulnerabilities Scan.
243
+
244
+ ```
245
+ USAGE: heimdall_tools netsparker_mapper [OPTIONS] -x <netsparker_results_xml> -o <hdf-scan-results.json>
246
+
247
+ FLAGS:
248
+ -x <netsparker_results_xml> : path to netsparker results XML file.
249
+ -o --output <scan-results> : path to output scan-results json.
250
+ -V --verbose : verbose run [optional].
251
+
252
+ example: heimdall_tools netsparker_mapper -x netsparker_results.xml -o netsparker_hdf.json
253
+ ```
254
+
216
255
  ## version
217
256
 
218
257
  Prints out the gem version
@@ -0,0 +1,107 @@
1
+ AwsConfigRuleName,NIST-ID,Rev
2
+ secretsmanager-scheduled-rotation-success-check,AC-2(1)|AC-2(j),4
3
+ iam-user-group-membership-check,AC-2(1)|AC-2(j)|AC-3|AC-6,4
4
+ iam-password-policy,AC-2(1)|AC-2(f)|AC-2(j)|IA-2|IA-5(1)(a)(d)(e)|IA-5(4),4
5
+ access-keys-rotated,AC-2(1)|AC-2(j),4
6
+ iam-user-unused-credentials-check,AC-2(1)|AC-2(3)|AC-2(f)|AC-3|AC-6,4
7
+ securityhub-enabled,AC-2(1)|AC-2(4)|AC-2(12)(a)|AC-2(g)|AC-17(1)|AU-6(1)(3)|CA-7(a)(b)|SA-10|SI-4(2)|SI-4(4)|SI-4(5)|SI-4(16)|SI-4(a)(b)(c),4
8
+ guardduty-enabled-centralized,AC-2(1)|AC-2(4)|AC-2(12)(a)|AC-2(g)|AC-17(1)|AU-6(1)(3)|CA-7(a)(b)|RA-5|SA-10|SI-4(1)|SI-4(2)|SI-4(4)|SI-4(5)|SI-4(16)|SI-4(a)(b)(c),4
9
+ cloud-trail-cloud-watch-logs-enabled,AC-2(4)|AC-2(g)|AU-2(a)(d)|AU-3|AU-6(1)(3)|AU-7(1)|AU-12(a)(c)|CA-7(a)(b)|SI-4(2)|SI-4(4)|SI-4(5)|SI-4(a)(b)(c),4
10
+ cloudtrail-enabled,AC-2(4)|AC-2(g)|AU-2(a)(d)|AU-3|AU-12(a)(c),4
11
+ multi-region-cloudtrail-enabled,AC-2(4)|AU-2(a)(d)|AU-3|AU-12(a)(c),4
12
+ rds-logging-enabled,AC-2(4)|AC-2(g)|AU-2(a)(d)|AU-3|AU-12(a)(c),4
13
+ cloudwatch-alarm-action-check,AC-2(4)|AU-6(1)(3)|AU-7(1)|CA-7(a)(b)|IR-4(1)|SI-4(2)|SI-4(4)|SI-4(5)|SI-4(a)(b)(c),4
14
+ redshift-cluster-configuration-check,AC-2(4)|AC-2(g)|AU-2(a)(d)|AU-3|AU-12(a)(c)|SC-13|SC-28,4
15
+ iam-root-access-key-check,AC-2(f)|AC-2(j)|AC-3|AC-6|AC-6(10),4
16
+ s3-bucket-logging-enabled,AC-2(g)|AU-2(a)(d)|AU-3|AU-12(a)(c),4
17
+ cloudtrail-s3-dataevents-enabled,AC-2(g)|AU-2(a)(d)|AU-3|AU-12(a)(c),4
18
+ root-account-mfa-enabled,AC-2(j)|IA-2(1)(11),4
19
+ emr-kerberos-enabled,AC-2(j)|AC-3|AC-5(c)|AC-6,4
20
+ iam-group-has-users-check,AC-2(j)|AC-3|AC-5(c)|AC-6|SC-2,4
21
+ iam-policy-no-statements-with-admin-access,AC-2(j)|AC-3|AC-5(c)|AC-6|SC-2,4
22
+ iam-user-no-policies-check,AC-2(j)|AC-3|AC-5(c)|AC-6,4
23
+ s3-bucket-public-write-prohibited,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
24
+ lambda-function-public-access-prohibited,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
25
+ rds-snapshots-public-prohibited,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
26
+ redshift-cluster-public-access-check,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
27
+ s3-bucket-policy-grantee-check,AC-3|AC-6|SC-7|SC-7(3),4
28
+ s3-bucket-public-read-prohibited,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
29
+ s3-account-level-public-access-blocks,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
30
+ dms-replication-not-public,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
31
+ ebs-snapshot-public-restorable-check,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
32
+ sagemaker-notebook-no-direct-internet-access,AC-3|AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
33
+ rds-instance-public-access-check,AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
34
+ lambda-inside-vpc,AC-4|SC-7|SC-7(3),4
35
+ ec2-instances-in-vpc,AC-4|SC-7|SC-7(3),4
36
+ restricted-common-ports,AC-4|CM-2|SC-7|SC-7(3),4
37
+ restricted-ssh,AC-4|SC-7|SC-7(3),4
38
+ vpc-default-security-group-closed,AC-4|SC-7|SC-7(3),4
39
+ vpc-sg-open-only-to-authorized-ports,AC-4|SC-7|SC-7(3),4
40
+ acm-certificate-expiration-check,AC-4|AC-17(2)|SC-12,4
41
+ ec2-instance-no-public-ip,AC-4|AC-6|AC-21(b)|SC-7|SC-7(3),4
42
+ elasticsearch-in-vpc-only,AC-4|SC-7|SC-7(3),4
43
+ emr-master-no-public-ip,AC-4|AC-21(b)|SC-7|SC-7(3),4
44
+ internet-gateway-authorized-vpc-only,AC-4|AC-17(3)|SC-7|SC-7(3),4
45
+ codebuild-project-envvar-awscred-check,AC-6|IA-5(7)|SA-3(a),4
46
+ ec2-imdsv2-check,AC-6,4
47
+ iam-no-inline-policy-check,AC-6,4
48
+ alb-http-to-https-redirection-check,AC-17(2)|SC-7|SC-8|SC-8(1)|SC-13|SC-23,4
49
+ redshift-require-tls-ssl,AC-17(2)|SC-7|SC-8|SC-8(1)|SC-13,4
50
+ s3-bucket-ssl-requests-only,AC-17(2)|SC-7|SC-8|SC-8(1)|SC-13,4
51
+ elb-acm-certificate-required,AC-17(2)|SC-7|SC-8|SC-8(1)|SC-13,4
52
+ alb-http-drop-invalid-header-enabled,AC-17(2)|SC-7|SC-8|SC-8(1)|SC-23,4
53
+ elb-tls-https-listeners-only,AC-17(2)|SC-7|SC-8|SC-8(1)|SC-23,4
54
+ api-gw-execution-logging-enabled,AU-2(a)(d)|AU-3|AU-12(a)(c),4
55
+ elb-logging-enabled,AU-2(a)(d)|AU-3|AU-12(a)(c),4
56
+ vpc-flow-logs-enabled,AU-2(a)(d)|AU-3|AU-12(a)(c),4
57
+ wafv2-logging-enabled,AU-2(a)(d)|AU-3|AU-12(a)(c)|SC-7|SI-4(a)(b)(c),4
58
+ cloud-trail-encryption-enabled,AU-9|SC-13|SC-28,4
59
+ cloudwatch-log-group-encrypted,AU-9|SC-13|SC-28,4
60
+ s3-bucket-replication-enabled,AU-9(2)|CP-9(b)|CP-10|SC-5|SC-36,4
61
+ cw-loggroup-retention-period-check,AU-11|SI-12,4
62
+ ec2-instance-detailed-monitoring-enabled,CA-7(a)(b)|SI-4(2)|SI-4(a)(b)(c),4
63
+ rds-enhanced-monitoring-enabled,CA-7(a)(b),4
64
+ ec2-instance-managed-by-systems-manager,CM-2|CM-7(a)|CM-8(1)|CM-8(3)(a)|SA-3(a)|SA-10|SI-2(2)|SI-7(1),4
65
+ ec2-managedinstance-association-compliance-status-check,CM-2|CM-7(a)|CM-8(3)(a)|SI-2(2),4
66
+ ec2-stopped-instance,CM-2,4
67
+ ec2-volume-inuse-check,CM-2|SC-4,4
68
+ elb-deletion-protection-enabled,CM-2|CP-10,4
69
+ cloudtrail-security-trail-enabled,CM-2,4
70
+ ec2-managedinstance-patch-compliance-status-check,CM-8(3)(a)|SI-2(2)|SI-7(1),4
71
+ db-instance-backup-enabled,CP-9(b)|CP-10|SI-12,4
72
+ dynamodb-pitr-enabled,CP-9(b)|CP-10|SI-12,4
73
+ elasticache-redis-cluster-automatic-backup-check,CP-9(b)|CP-10|SI-12,4
74
+ dynamodb-in-backup-plan,CP-9(b)|CP-10|SI-12,4
75
+ ebs-in-backup-plan,CP-9(b)|CP-10|SI-12,4
76
+ efs-in-backup-plan,CP-9(b)|CP-10|SI-12,4
77
+ rds-in-backup-plan,CP-9(b)|CP-10|SI-12,4
78
+ dynamodb-autoscaling-enabled,CP-10|SC-5,4
79
+ rds-multi-az-support,CP-10|SC-5|SC-36,4
80
+ s3-bucket-versioning-enabled,CP-10|SI-12,4
81
+ vpc-vpn-2-tunnels-up,CP-10,4
82
+ elb-cross-zone-load-balancing-enabled,CP-10|SC-5,4
83
+ root-account-hardware-mfa-enabled,IA-2(1)(11),4
84
+ mfa-enabled-for-iam-console-access,IA-2(1)(2)(11),4
85
+ iam-user-mfa-enabled,IA-2(1)(2)(11),4
86
+ guardduty-non-archived-findings,IR-4(1)|IR-6(1)|IR-7(1)|RA-5|SA-10|SI-4(a)(b)(c),4
87
+ codebuild-project-source-repo-url-check,SA-3(a),4
88
+ autoscaling-group-elb-healthcheck-required,SC-5,4
89
+ rds-instance-deletion-protection-enabled,SC-5,4
90
+ alb-waf-enabled,SC-7|SI-4(a)(b)(c),4
91
+ elasticsearch-node-to-node-encryption-check,SC-7|SC-8|SC-8(1),4
92
+ cmk-backing-key-rotation-enabled,SC-12,4
93
+ kms-cmk-not-scheduled-for-deletion,SC-12|SC-28,4
94
+ api-gw-cache-enabled-and-encrypted,SC-13|SC-28,4
95
+ efs-encrypted-check,SC-13|SC-28,4
96
+ elasticsearch-encrypted-at-rest,SC-13|SC-28,4
97
+ encrypted-volumes,SC-13|SC-28,4
98
+ rds-storage-encrypted,SC-13|SC-28,4
99
+ s3-bucket-server-side-encryption-enabled,SC-13|SC-28,4
100
+ sagemaker-endpoint-configuration-kms-key-configured,SC-13|SC-28,4
101
+ sagemaker-notebook-instance-kms-key-configured,SC-13|SC-28,4
102
+ sns-encrypted-kms,SC-13|SC-28,4
103
+ dynamodb-table-encrypted-kms,SC-13,4
104
+ s3-bucket-default-lock-enabled,SC-28,4
105
+ ec2-ebs-encryption-by-default,SC-28,4
106
+ rds-snapshot-encrypted,SC-28,4
107
+ cloud-trail-log-file-validation-enabled,SI-7|SI-7(1),4
@@ -14,4 +14,6 @@ module HeimdallTools
14
14
  autoload :NiktoMapper, 'heimdall_tools/nikto_mapper'
15
15
  autoload :JfrogXrayMapper, 'heimdall_tools/jfrog_xray_mapper'
16
16
  autoload :DBProtectMapper, 'heimdall_tools/dbprotect_mapper'
17
+ autoload :AwsConfigMapper, 'heimdall_tools/aws_config_mapper'
18
+ autoload :NetsparkerMapper, 'heimdall_tools/netsparker_mapper'
17
19
  end
@@ -0,0 +1,284 @@
1
+ require 'aws-sdk-configservice'
2
+ require 'heimdall_tools/hdf'
3
+ require 'csv'
4
+ require 'json'
5
+
6
+ RESOURCE_DIR = Pathname.new(__FILE__).join('../../data')
7
+
8
+ AWS_CONFIG_MAPPING_FILE = File.join(RESOURCE_DIR, 'aws-config-mapping.csv')
9
+
10
+ NOT_APPLICABLE_MSG = 'No AWS resources found to evaluate complaince for this rule'.freeze
11
+ INSUFFICIENT_DATA_MSG = 'Not enough data has been collectd to determine compliance yet.'.freeze
12
+
13
+ ##
14
+ # HDF mapper for use with AWS Config rules.
15
+ #
16
+ # Ruby AWS Ruby SDK for ConfigService:
17
+ # - https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ConfigService/Client.html
18
+ #
19
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength
20
+ module HeimdallTools
21
+ class AwsConfigMapper
22
+ def initialize(custom_mapping, verbose = false)
23
+ @verbose = verbose
24
+ @default_mapping = get_rule_mapping(AWS_CONFIG_MAPPING_FILE)
25
+ @custom_mapping = custom_mapping.nil? ? {} : get_rule_mapping(custom_mapping)
26
+ @client = Aws::ConfigService::Client.new
27
+ @issues = get_all_config_rules
28
+ end
29
+
30
+ ##
31
+ # Convert to HDF
32
+ #
33
+ # If there is overlap in rule names from @default_mapping and @custom_mapping,
34
+ # then the tags from both will be added to the rule.
35
+ def to_hdf
36
+ controls = @issues.map do |issue|
37
+ @item = {}
38
+ @item['id'] = issue[:config_rule_name]
39
+ @item['title'] = issue[:config_rule_name]
40
+ @item['desc'] = issue[:description]
41
+ @item['impact'] = 0.5
42
+ @item['tags'] = hdf_tags(issue)
43
+ @item['descriptions'] = hdf_descriptions(issue)
44
+ @item['refs'] = NA_ARRAY
45
+ @item['source_location'] = { ref: issue[:config_rule_arn], line: 1 }
46
+ @item['code'] = ''
47
+ @item['results'] = issue[:results]
48
+ # Avoid duplicating rules that exist in the custom mapping as 'unmapped' in this loop
49
+ if @custom_mapping.include?(issue[:config_rule_name]) && !@default_mapping.include?(issue[:config_rule_name])
50
+ nil
51
+ else
52
+ @item
53
+ end
54
+ end
55
+ results = HeimdallDataFormat.new(
56
+ profile_name: 'AWS Config',
57
+ title: 'AWS Config',
58
+ summary: 'AWS Config',
59
+ controls: controls,
60
+ statistics: { aws_config_sdk_version: Aws::ConfigService::GEM_VERSION }
61
+ )
62
+ results.to_hdf
63
+ end
64
+
65
+ private
66
+
67
+ ##
68
+ # Read in a config rule -> 800-53 control mapping CSV.
69
+ #
70
+ # Params:
71
+ # - path: The file path to the CSV file
72
+ #
73
+ # Returns: A mapped version of the csv in the format { rule_name: row, ... }
74
+ def get_rule_mapping(path)
75
+ Hash[CSV.read(path, headers: true).map { |row| [row[0], row] }]
76
+ end
77
+
78
+ ##
79
+ # Fetches information on all of the config rules available to the
80
+ # AWS account.
81
+ #
82
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ConfigService/Client.html#describe_config_rules-instance_method
83
+ #
84
+ # Returns: list of hash for all config rules available
85
+ def get_all_config_rules
86
+ config_rules = []
87
+
88
+ # Fetch all rules with pagination
89
+ response = @client.describe_config_rules
90
+ config_rules += response.config_rules
91
+ while response.next_token
92
+ response = @client.describe_config_rules(next_token: response.next_token)
93
+ config_rules += response.config_rules
94
+ end
95
+ config_rules = config_rules.map(&:to_h)
96
+
97
+ # Add necessary data to rules using helpers
98
+ add_compliance_to_config_rules(config_rules)
99
+ add_results_to_config_rules(config_rules)
100
+ end
101
+
102
+ ##
103
+ # Adds compliance information for config rules to the config rule hash
104
+ # from AwsConfigMapper::get_all_config_rules.
105
+ #
106
+ # `complaince_type` may be any of the following:
107
+ # ["COMPLIANT", "NON_COMPLIANT", "NOT_APPLICABLE", "INSUFFICIENT_DATA"]
108
+ #
109
+ # Params:
110
+ # - config_rules: The list of hash from AwsConfigMapper::get_all_config_rules
111
+ #
112
+ # Returns: The same config_rules array with `compliance` key added to each rule
113
+ def add_compliance_to_config_rules(config_rules)
114
+ mapped_compliance_results = fetch_all_compliance_info(config_rules)
115
+
116
+ # Add compliance to config_rules
117
+ config_rules.each do |rule|
118
+ rule[:compliance] = mapped_compliance_results[rule[:config_rule_name]]&.dig(:compliance, :compliance_type)
119
+ end
120
+
121
+ config_rules
122
+ end
123
+
124
+ ##
125
+ # Fetch and combine all compliance information for the config rules.
126
+ #
127
+ # AWS allows passing up to 25 rules at a time to this endpoint.
128
+ #
129
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ConfigService/Client.html#describe_compliance_by_config_rule-instance_method
130
+ #
131
+ # Params:
132
+ # - config_rules: The list of hash from AwsConfigMapper::get_all_config_rules
133
+ #
134
+ # Returns: Results mapped by config rule in the format { name: {<response>}, ... }
135
+ def fetch_all_compliance_info(config_rules)
136
+ compliance_results = []
137
+
138
+ config_rules.each_slice(25).each do |slice|
139
+ config_rule_names = slice.map { |r| r[:config_rule_name] }
140
+ response = @client.describe_compliance_by_config_rule(config_rule_names: config_rule_names)
141
+ compliance_results += response.compliance_by_config_rules
142
+ end
143
+
144
+ # Map based on name for easy lookup
145
+ Hash[compliance_results.collect { |r| [r.config_rule_name, r.to_h] }]
146
+ end
147
+
148
+ ##
149
+ # Takes in config rules and formats the results for hdf format.
150
+ #
151
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ConfigService/Client.html#get_compliance_details_by_config_rule-instance_method
152
+ #
153
+ # Example hdf results:
154
+ # [
155
+ # {
156
+ # "code_desc": "This rule...",
157
+ # "run_time": 0.314016,
158
+ # "start_time": "2018-11-18T20:21:40-05:00",
159
+ # "status": "passed"
160
+ # },
161
+ # ...
162
+ # ]
163
+ #
164
+ # Status may be any of the following: ['passed', 'failed', 'skipped', 'loaded']
165
+ #
166
+ # Params:
167
+ # - rule: Rules from AwsConfigMapper::get_all_config_rules
168
+ #
169
+ # Returns: The same config_rules array with `results` key added to each rule.
170
+ def add_results_to_config_rules(config_rules)
171
+ config_rules.each do |rule|
172
+ response = @client.get_compliance_details_by_config_rule(config_rule_name: rule[:config_rule_name], limit: 100)
173
+ rule_results = response.to_h[:evaluation_results]
174
+ while response.next_token
175
+ response = @client.get_compliance_details_by_config_rule(next_token: response.next_token, limit: 100)
176
+ rule_results += response.to_h[:evaluation_results]
177
+ end
178
+
179
+ rule[:results] = []
180
+ rule_results.each do |result|
181
+ hdf_result = {}
182
+ # code_desc
183
+ hdf_result['code_desc'] = result.dig(:evaluation_result_identifier, :evaluation_result_qualifier)&.map do |k, v|
184
+ "#{k}: #{v}"
185
+ end&.join(', ')
186
+ # start_time
187
+ hdf_result['start_time'] = if result.key?(:config_rule_invoked_time)
188
+ DateTime.parse(result[:config_rule_invoked_time].to_s).strftime('%Y-%m-%dT%H:%M:%S%:z')
189
+ end
190
+ # run_time
191
+ hdf_result['run_time'] = if result.key?(:result_recorded_time) && result.key?(:config_rule_invoked_time)
192
+ (result[:result_recorded_time] - result[:config_rule_invoked_time]).round(6)
193
+ end
194
+ # status
195
+ hdf_result['status'] = case result.dig(:compliance_type)
196
+ when 'COMPLIANT'
197
+ 'passed'
198
+ when 'NON_COMPLIANT'
199
+ 'failed'
200
+ else
201
+ 'skipped'
202
+ end
203
+ hdf_result['message'] = "(#{hdf_result['code_desc']}): #{result[:annotation] || 'Rule does not pass rule compliance'}" if hdf_result['status'] == 'failed'
204
+ rule[:results] << hdf_result
205
+ end
206
+ next unless rule[:results].empty?
207
+
208
+ case rule[:compliance]
209
+ when 'NOT_APPLICABLE'
210
+ rule[:impact] = 0
211
+ rule[:results] << {
212
+ 'run_time': 0,
213
+ 'code_desc': NOT_APPLICABLE_MSG,
214
+ 'skip_message': NOT_APPLICABLE_MSG,
215
+ 'start_time': DateTime.now.strftime('%Y-%m-%dT%H:%M:%S%:z'),
216
+ 'status': 'skipped'
217
+ }
218
+ when 'INSUFFICIENT_DATA'
219
+ rule[:results] << {
220
+ 'run_time': 0,
221
+ 'code_desc': INSUFFICIENT_DATA_MSG,
222
+ 'skip_message': INSUFFICIENT_DATA_MSG,
223
+ 'start_time': DateTime.now.strftime('%Y-%m-%dT%H:%M:%S%:z'),
224
+ 'status': 'skipped'
225
+ }
226
+ end
227
+ end
228
+
229
+ config_rules
230
+ end
231
+
232
+ ##
233
+ # Takes in a config rule and pulls out tags that are useful for HDF.
234
+ #
235
+ # Params:
236
+ # - config_rule: A single config rule from AwsConfigMapper::get_all_config_rules
237
+ #
238
+ # Returns: Hash containing all relevant HDF tags
239
+ def hdf_tags(config_rule)
240
+ result = {}
241
+
242
+ @default_mapping
243
+ @custom_mapping
244
+
245
+ # NIST tag
246
+ result['nist'] = []
247
+ default_mapping_match = @default_mapping[config_rule[:config_rule_name]]
248
+
249
+ result['nist'] += default_mapping_match[1].split('|') unless default_mapping_match.nil?
250
+
251
+ custom_mapping_match = @custom_mapping[config_rule[:config_rule_name]]
252
+
253
+ result['nist'] += custom_mapping_match[1].split('|').map { |name| "#{name} (user provided)" } unless custom_mapping_match.nil?
254
+
255
+ result['nist'] = ['unmapped'] if result['nist'].empty?
256
+
257
+ result
258
+ end
259
+
260
+ def check_text(config_rule)
261
+ params = (JSON.parse(config_rule[:input_parameters]).map { |key, value| "#{key}: #{value}" }).join('<br/>')
262
+ check_text = config_rule[:config_rule_arn]
263
+ check_text += "<br/>#{params}" unless params.empty?
264
+ check_text
265
+ end
266
+
267
+ ##
268
+ # Takes in a config rule and pulls out information for the descriptions array
269
+ #
270
+ # Params:
271
+ # - config_rule: A single config rule from AwsConfigMapper::get_all_config_rules
272
+ #
273
+ # Returns: Array containing all relevant descriptions information
274
+ def hdf_descriptions(config_rule)
275
+ [
276
+ {
277
+ 'label': 'check',
278
+ 'data': check_text(config_rule)
279
+ }
280
+ ]
281
+ end
282
+ end
283
+ end
284
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength
@@ -63,7 +63,7 @@ module HeimdallTools
63
63
  end
64
64
 
65
65
  def nist_tag(cweid)
66
- entries = @cwe_nist_mapping.select { |x| cweid.include? x[:cweid].to_s }
66
+ entries = @cwe_nist_mapping.select { |x| cweid.include?(x[:cweid].to_s) && !x[:nistid].nil? }
67
67
  tags = entries.map { |x| [x[:nistid], "Rev_#{x[:rev]}"] }
68
68
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
69
69
  end
@@ -98,7 +98,7 @@ module HeimdallTools
98
98
  puts "\r\HDF Generated:\n"
99
99
  puts "#{options[:output]}"
100
100
  end
101
-
101
+
102
102
  desc 'dbprotect_mapper', 'dbprotect_mapper translates dbprotect results xml to HDF format Json be viewed on Heimdall'
103
103
  long_desc Help.text(:dbprotect_mapper)
104
104
  option :xml, required: true, aliases: '-x'
@@ -111,6 +111,30 @@ module HeimdallTools
111
111
  puts "#{options[:output]}"
112
112
  end
113
113
 
114
+ desc 'aws_config_mapper', 'aws_config_mapper pulls Ruby AWS SDK data to translate AWS Config Rule results into HDF format Json to be viewable in Heimdall'
115
+ long_desc Help.text(:aws_config_mapper)
116
+ # option :custom_mapping, required: false, aliases: '-m'
117
+ option :output, required: true, aliases: '-o'
118
+ option :verbose, type: :boolean, aliases: '-V'
119
+ def aws_config_mapper
120
+ hdf = HeimdallTools::AwsConfigMapper.new(options[:custom_mapping]).to_hdf
121
+ File.write(options[:output], hdf)
122
+ puts "\r\HDF Generated:\n"
123
+ puts "#{options[:output]}"
124
+ end
125
+
126
+ desc 'netsparker_mapper', 'netsparker_mapper translates netsparker enterprise results xml to HDF format Json be viewed on Heimdall'
127
+ long_desc Help.text(:netsparker_mapper)
128
+ option :xml, required: true, aliases: '-x'
129
+ option :output, required: true, aliases: '-o'
130
+ option :verbose, type: :boolean, aliases: '-V'
131
+ def netsparker_mapper
132
+ hdf = HeimdallTools::NetsparkerMapper.new(File.read(options[:xml])).to_hdf
133
+ File.write(options[:output], hdf)
134
+ puts "\r\HDF Generated:\n"
135
+ puts "#{options[:output]}"
136
+ end
137
+
114
138
  desc 'version', 'prints version'
115
139
  def version
116
140
  puts VERSION
@@ -29,7 +29,8 @@ module HeimdallTools
29
29
  groups: NA_ARRAY,
30
30
  status: 'loaded',
31
31
  controls: NA_TAG,
32
- target_id: NA_TAG)
32
+ target_id: NA_TAG,
33
+ statistics: NA_HASH)
33
34
 
34
35
  @results_json = {}
35
36
  @results_json['platform'] = {}
@@ -40,6 +41,7 @@ module HeimdallTools
40
41
 
41
42
  @results_json['statistics'] = {}
42
43
  @results_json['statistics']['duration'] = duration || NA_TAG
44
+ @results_json['statistics'].merge! statistics
43
45
 
44
46
  @results_json['profiles'] = []
45
47
 
@@ -0,0 +1,30 @@
1
+ aws_config_mapper pulls Ruby AWS SDK data to translate AWS Config Rule results into HDF format json to be viewable in Heimdall
2
+
3
+ AWS Config Rule Mapping:
4
+ The mapping of AWS Config Rules to 800-53 Controls was sourced from [this link](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-nist-800-53_rev_4.html).
5
+
6
+ Authentication with AWS:
7
+ [Developer Guide for configuring Ruby AWS SDK for authentication](https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html)
8
+
9
+ Authentication Example:
10
+
11
+ - Create `~/.aws/credentials`
12
+ - Add contents to file, replacing with your access ID and key
13
+
14
+ ```
15
+ [default]
16
+ aws_access_key_id = your_access_key_id
17
+ aws_secret_access_key = your_secret_access_key
18
+ ```
19
+
20
+ - (optional) set AWS region through `~/.aws/config` file with contents
21
+
22
+ ```
23
+ [default]
24
+ output = json
25
+ region = us-gov-west-1
26
+ ```
27
+
28
+ Examples:
29
+
30
+ heimdall_tools aws_config_mapper -o aws_config_results.json
@@ -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
@@ -57,8 +57,16 @@ module HeimdallTools
57
57
  [finding]
58
58
  end
59
59
 
60
+ def format_control_desc(vulnerability)
61
+ text = []
62
+ info = vulnerability['component_versions']['more_details']
63
+ text << info['description'].to_s
64
+ text << "cves: #{info['cves'].to_s }" unless info['cves'].nil?
65
+ text.join("<br>")
66
+ end
67
+
60
68
  def nist_tag(cweid)
61
- entries = @cwe_nist_mapping.select { |x| cweid.include? x[:cweid].to_s }
69
+ entries = @cwe_nist_mapping.select { |x| cweid.include?(x[:cweid].to_s) && !x[:nistid].nil? }
62
70
  tags = entries.map { |x| x[:nistid] }
63
71
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
64
72
  end
@@ -119,7 +127,7 @@ module HeimdallTools
119
127
  # If thats a case MD5 hash is used to collapse vulnerability findings of the same type.
120
128
  item['id'] = vulnerability['id'].empty? ? OpenSSL::Digest::MD5.digest(vulnerability['summary'].to_s).unpack("H*")[0].to_s : vulnerability['id']
121
129
  item['title'] = vulnerability['summary'].to_s
122
- item['desc'] = vulnerability['component_versions']['more_details']['description'].to_s
130
+ item['desc'] = format_control_desc(vulnerability)
123
131
  item['impact'] = impact(vulnerability['severity'].to_s)
124
132
  item['code'] = NA_STRING
125
133
  item['results'] = finding(vulnerability)
@@ -140,7 +140,7 @@ module HeimdallTools
140
140
  end
141
141
 
142
142
  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)) ) }
143
+ entries = @cwe_nist_mapping.select { |x| (x[:pluginfamily].eql?(pluginfamily) && (x[:pluginid].eql?('*') || x[:pluginid].eql?(pluginid.to_i)) ) && !x[:nistid].nil? }
144
144
  tags = entries.map { |x| [x[:nistid].split('|'), "Rev_#{x[:rev]}"] }
145
145
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
146
146
  end
@@ -0,0 +1,167 @@
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 = ["SA-11", "RA-5"].freeze
21
+
22
+ # rubocop:disable Metrics/AbcSize
23
+
24
+ module HeimdallTools
25
+ 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
40
+ end
41
+
42
+ def to_hdf
43
+ controls = []
44
+ @vulnerabilities.each do |vulnerability|
45
+ @item = {}
46
+ @item['id'] = vulnerability['LookupId'].to_s
47
+ @item['title'] = vulnerability['name'].to_s
48
+ @item['desc'] = format_control_desc(vulnerability)
49
+ @item['impact'] = impact(vulnerability['severity'])
50
+ @item['tags'] = {}
51
+ @item['descriptions'] = []
52
+
53
+ @item['descriptions'] << desc_tags(format_check_text(vulnerability), 'check')
54
+ @item['descriptions'] << desc_tags(format_fix_text(vulnerability), 'fix')
55
+ @item['refs'] = NA_ARRAY
56
+ @item['source_location'] = NA_HASH
57
+ @item['tags']['nist'] = nist_tag(vulnerability['classification'])
58
+ @item['code'] = ''
59
+ @item['results'] = finding(vulnerability)
60
+
61
+ controls << @item
62
+ end
63
+ controls = collapse_duplicates(controls)
64
+ results = HeimdallDataFormat.new(profile_name: 'Netsparker Enterprise Scan',
65
+ title: "Netsparker Enterprise Scan ID: #{@scan_info['scan-id']} URL: #{@scan_info['url']}",
66
+ summary: "Netsparker Enterprise Scan",
67
+ target_id: @scan_info['url'],
68
+ controls: controls)
69
+ results.to_hdf
70
+ end
71
+
72
+ private
73
+
74
+ def parse_html(block)
75
+ block['#cdata-section'].to_s.strip unless block.nil?
76
+ end
77
+
78
+ def finding(vulnerability)
79
+ finding = {}
80
+ finding['status'] = 'failed'
81
+ finding['code_desc'] = []
82
+ finding['code_desc'] << "http-request : #{parse_html(vulnerability['http-request']['content']) }"
83
+ finding['code_desc'] << "method : #{vulnerability['http-request']['method']}"
84
+ finding['code_desc'] = finding['code_desc'].join("\n")
85
+
86
+ finding['message'] = []
87
+ finding['message'] << "http-response : #{parse_html(vulnerability['http-response']['content']) }"
88
+ finding['message'] << "duration : #{vulnerability['http-response']['duration']}"
89
+ finding['message'] << "status-code : #{vulnerability['http-response']['status-code']}"
90
+ finding['message'] = finding['message'].join("\n")
91
+ finding['run_time'] = NA_FLOAT
92
+
93
+ finding['start_time'] = @scan_info['initiated']
94
+ [finding]
95
+ end
96
+
97
+ def format_control_desc(vulnerability)
98
+ 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?
101
+ text << "Extra-information: #{vulnerability['extra-information']}" unless vulnerability['extra-information'].nil?
102
+ text << "Classification: #{vulnerability['classification']}" unless vulnerability['classification'].nil?
103
+ text << "Impact: #{parse_html(vulnerability['impact'])}" unless vulnerability['impact'].nil?
104
+ text << "FirstSeenDate: #{vulnerability['FirstSeenDate']}" unless vulnerability['FirstSeenDate'].nil?
105
+ text << "LastSeenDate: #{vulnerability['LastSeenDate']}" unless vulnerability['LastSeenDate'].nil?
106
+ text << "Certainty: #{vulnerability['certainty']}" unless vulnerability['certainty'].nil?
107
+ text << "Type: #{vulnerability['type']}" unless vulnerability['type'].nil?
108
+ text << "Confirmed: #{vulnerability['confirmed']}" unless vulnerability['confirmed'].nil?
109
+ text.join("<br>")
110
+ end
111
+
112
+ def format_check_text(vulnerability)
113
+ text = []
114
+ text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
115
+ text << "Proof-of-concept: #{parse_html(vulnerability['proof-of-concept'])}" unless vulnerability['proof-of-concept'].nil?
116
+ text.join("<br>")
117
+ end
118
+
119
+ def format_fix_text(vulnerability)
120
+ text = []
121
+ text << "Remedial-actions: #{parse_html(vulnerability['remedial-actions'])}" unless vulnerability['remedial-actions'].nil?
122
+ text << "Remedial-procedure: #{parse_html(vulnerability['remedial-procedure'])}" unless vulnerability['remedial-procedure'].nil?
123
+ text << "Remedy-references: #{parse_html(vulnerability['remedy-references'])}" unless vulnerability['remedy-references'].nil?
124
+ text.join("<br>")
125
+ end
126
+
127
+ def nist_tag(classification)
128
+ tags = []
129
+ entries = @cwe_nist_mapping.select { |x| classification['cwe'].include?(x[:cweid].to_s) && !x[:nistid].nil? }
130
+ tags << entries.map { |x| x[:nistid] }
131
+ entries = @owasp_nist_mapping.select { |x| classification['owasp'].include?(x[:owaspid].to_s) && !x[:nistid].nil? }
132
+ tags << entries.map { |x| x[:nistid] }
133
+ tags.flatten.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
134
+ end
135
+
136
+ def impact(severity)
137
+ IMPACT_MAPPING[severity.to_sym]
138
+ end
139
+
140
+ def parse_mapper(mapping_file)
141
+ csv_data = CSV.read(mapping_file, { encoding: 'UTF-8',
142
+ headers: true,
143
+ header_converters: :symbol,
144
+ converters: :all })
145
+ csv_data.map(&:to_hash)
146
+ end
147
+
148
+ def desc_tags(data, label)
149
+ { "data": data || NA_STRING, "label": label || NA_STRING }
150
+ end
151
+
152
+ # Netsparker report could have multiple issue entries for multiple findings of same issue type.
153
+ # The meta data is identical across entries
154
+ # method collapse_duplicates return unique controls with applicable findings collapsed into it.
155
+ def collapse_duplicates(controls)
156
+ unique_controls = []
157
+
158
+ controls.map { |x| x['id'] }.uniq.each do |id|
159
+ collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
160
+ unique_control = controls.find { |x| x['id'].eql?(id) }
161
+ unique_control['results'] = collapsed_results.flatten
162
+ unique_controls << unique_control
163
+ end
164
+ unique_controls
165
+ end
166
+ end
167
+ end
@@ -71,7 +71,7 @@ module HeimdallTools
71
71
  end
72
72
 
73
73
  def nist_tag(niktoid)
74
- entries = @nikto_nist_mapping.select { |x| niktoid.eql?(x[:niktoid].to_s) }
74
+ entries = @nikto_nist_mapping.select { |x| niktoid.eql?(x[:niktoid].to_s) && !x[:nistid].nil? }
75
75
  tags = entries.map { |x| x[:nistid] }
76
76
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
77
77
  end
@@ -74,7 +74,7 @@ module HeimdallTools
74
74
  end
75
75
 
76
76
  def nist_tag(cweid)
77
- entries = @cwe_nist_mapping.select { |x| cweid.include? x[:cweid].to_s }
77
+ entries = @cwe_nist_mapping.select { |x| cweid.include?(x[:cweid].to_s) && !x[:nistid].nil? }
78
78
  tags = entries.map { |x| x[:nistid] }
79
79
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
80
80
  end
@@ -65,7 +65,7 @@ module HeimdallTools
65
65
  end
66
66
 
67
67
  def nist_tag(cweid)
68
- entries = @cwe_nist_mapping.select { |x| x[:cweid].to_s.eql?(cweid.to_s) }
68
+ entries = @cwe_nist_mapping.select { |x| x[:cweid].to_s.eql?(cweid.to_s) && !x[:nistid].nil? }
69
69
  tags = entries.map { |x| [x[:nistid], "Rev_#{x[:rev]}"] }
70
70
  tags.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
71
71
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heimdall_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.36
4
+ version: 1.3.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Thew
@@ -10,8 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-03-01 00:00:00.000000000 Z
13
+ date: 2021-03-16 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws-sdk-configservice
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1'
15
29
  - !ruby/object:Gem::Dependency
16
30
  name: nokogiri
17
31
  requirement: !ruby/object:Gem::Requirement
@@ -181,11 +195,13 @@ files:
181
195
  - Rakefile
182
196
  - exe/heimdall_tools
183
197
  - lib/data/U_CCI_List.xml
198
+ - lib/data/aws-config-mapping.csv
184
199
  - lib/data/cwe-nist-mapping.csv
185
200
  - lib/data/nessus-plugins-nist-mapping.csv
186
201
  - lib/data/nikto-nist-mapping.csv
187
202
  - lib/data/owasp-nist-mapping.csv
188
203
  - lib/heimdall_tools.rb
204
+ - lib/heimdall_tools/aws_config_mapper.rb
189
205
  - lib/heimdall_tools/burpsuite_mapper.rb
190
206
  - lib/heimdall_tools/cli.rb
191
207
  - lib/heimdall_tools/command.rb
@@ -193,17 +209,20 @@ files:
193
209
  - lib/heimdall_tools/fortify_mapper.rb
194
210
  - lib/heimdall_tools/hdf.rb
195
211
  - lib/heimdall_tools/help.rb
212
+ - lib/heimdall_tools/help/aws_config_mapper.md
196
213
  - lib/heimdall_tools/help/burpsuite_mapper.md
197
214
  - lib/heimdall_tools/help/dbprotect_mapper.md
198
215
  - lib/heimdall_tools/help/fortify_mapper.md
199
216
  - lib/heimdall_tools/help/jfrog_xray_mapper.md
200
217
  - lib/heimdall_tools/help/nessus_mapper.md
218
+ - lib/heimdall_tools/help/netsparker_mapper.md
201
219
  - lib/heimdall_tools/help/nikto_mapper.md
202
220
  - lib/heimdall_tools/help/snyk_mapper.md
203
221
  - lib/heimdall_tools/help/sonarqube_mapper.md
204
222
  - lib/heimdall_tools/help/zap_mapper.md
205
223
  - lib/heimdall_tools/jfrog_xray_mapper.rb
206
224
  - lib/heimdall_tools/nessus_mapper.rb
225
+ - lib/heimdall_tools/netsparker_mapper.rb
207
226
  - lib/heimdall_tools/nikto_mapper.rb
208
227
  - lib/heimdall_tools/snyk_mapper.rb
209
228
  - lib/heimdall_tools/sonarqube_mapper.rb