heimdall_tools 1.3.36 → 1.3.40

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.
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