cvss-suite 3.0.1 → 3.3.0
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 +4 -4
- data/.github/workflows/rspec.yml +4 -4
- data/.github/workflows/rubocop.yml +3 -4
- data/.rspec +1 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +2 -2
- data/CHANGES.md +34 -0
- data/CODE_OF_CONDUCT.md +0 -5
- data/Gemfile +0 -6
- data/LICENSE.md +12 -2
- data/README.md +20 -11
- data/cvss_suite.gemspec +12 -14
- data/lib/cvss_suite/cvss.rb +7 -55
- data/lib/cvss_suite/cvss2/cvss2.rb +2 -8
- data/lib/cvss_suite/cvss2/cvss2_base.rb +0 -6
- data/lib/cvss_suite/cvss2/cvss2_environmental.rb +0 -6
- data/lib/cvss_suite/cvss2/cvss2_temporal.rb +0 -6
- data/lib/cvss_suite/cvss3/cvss3.rb +8 -8
- data/lib/cvss_suite/cvss3/cvss3_base.rb +8 -14
- data/lib/cvss_suite/cvss3/cvss3_environmental.rb +11 -17
- data/lib/cvss_suite/cvss3/cvss3_temporal.rb +3 -9
- data/lib/cvss_suite/cvss31/cvss31.rb +8 -8
- data/lib/cvss_suite/cvss31/cvss31_base.rb +8 -14
- data/lib/cvss_suite/cvss31/cvss31_environmental.rb +11 -17
- data/lib/cvss_suite/cvss31/cvss31_temporal.rb +3 -9
- data/lib/cvss_suite/cvss40/cvss40.rb +43 -0
- data/lib/cvss_suite/cvss40/cvss40_all_up.rb +40 -0
- data/lib/cvss_suite/cvss40/cvss40_base.rb +86 -0
- data/lib/cvss_suite/cvss40/cvss40_calc_helper.rb +397 -0
- data/lib/cvss_suite/cvss40/cvss40_constants_levels.rb +26 -0
- data/lib/cvss_suite/cvss40/cvss40_constants_macro_vector_lookup.rb +278 -0
- data/lib/cvss_suite/cvss40/cvss40_constants_max_composed.rb +41 -0
- data/lib/cvss_suite/cvss40/cvss40_constants_max_severity.rb +31 -0
- data/lib/cvss_suite/cvss40/cvss40_environmental.rb +105 -0
- data/lib/cvss_suite/cvss40/cvss40_environmental_security.rb +47 -0
- data/lib/cvss_suite/cvss40/cvss40_supplemental.rb +66 -0
- data/lib/cvss_suite/cvss40/cvss40_threat.rb +34 -0
- data/lib/cvss_suite/cvss_31_and_before.rb +61 -0
- data/lib/cvss_suite/cvss_40_and_later.rb +51 -0
- data/lib/cvss_suite/cvss_metric.rb +7 -7
- data/lib/cvss_suite/cvss_property.rb +23 -8
- data/lib/cvss_suite/errors.rb +0 -6
- data/lib/cvss_suite/helpers/cvss31_helper.rb +0 -6
- data/lib/cvss_suite/helpers/cvss3_helper.rb +0 -6
- data/lib/cvss_suite/invalid_cvss.rb +0 -6
- data/lib/cvss_suite/version.rb +1 -7
- data/lib/cvss_suite.rb +48 -11
- metadata +40 -11
@@ -0,0 +1,397 @@
|
|
1
|
+
require_relative 'cvss40_constants_macro_vector_lookup'
|
2
|
+
require_relative 'cvss40_constants_max_composed'
|
3
|
+
require_relative 'cvss40_constants_max_severity'
|
4
|
+
require_relative 'cvss40_constants_levels'
|
5
|
+
|
6
|
+
module CvssSuite
|
7
|
+
# This class performs much of the score calculation logic for CVSS 4.0.
|
8
|
+
# It is heavily ported from the m and scoring methods in https://github.com/FIRSTdotorg/cvss-v4-calculator/blob/ac71416d935ad2ac87cd107ff87024561ea954a7/app.js#L121
|
9
|
+
# This class has a few rubocop exclusions but maintaining parity with the ported
|
10
|
+
# code seems more valuable than trying to follow the cops in this case.
|
11
|
+
class Cvss40CalcHelper
|
12
|
+
include Cvss40Constants
|
13
|
+
|
14
|
+
def initialize(cvss_property_bag)
|
15
|
+
@cvss_property_bag = cvss_property_bag
|
16
|
+
end
|
17
|
+
|
18
|
+
def m(metric)
|
19
|
+
selected = @cvss_property_bag[metric]
|
20
|
+
|
21
|
+
# If E=X it will default to the worst case i.e. E=A
|
22
|
+
return 'A' if metric == 'E' && (selected == 'X' || selected.nil?)
|
23
|
+
# If CR=X, IR=X or AR=X they will default to the worst case i.e. CR=H, IR=H and AR=H
|
24
|
+
return 'H' if metric == 'CR' && (selected == 'X' || selected.nil?)
|
25
|
+
# IR:X is the same as IR:H
|
26
|
+
return 'H' if metric == 'IR' && (selected == 'X' || selected.nil?)
|
27
|
+
# AR:X is the same as AR:H
|
28
|
+
return 'H' if metric == 'AR' && (selected == 'X' || selected.nil?)
|
29
|
+
|
30
|
+
# All other environmental metrics just overwrite base score values,
|
31
|
+
# so if they’re not defined just use the base score value.
|
32
|
+
if @cvss_property_bag.include?("M#{metric}")
|
33
|
+
modified_selected = @cvss_property_bag["M#{metric}"]
|
34
|
+
return modified_selected if modified_selected != 'X'
|
35
|
+
end
|
36
|
+
|
37
|
+
selected
|
38
|
+
end
|
39
|
+
|
40
|
+
def retrieve_macro_vector
|
41
|
+
# EQ1: 0-AV:N and PR:N and UI:N
|
42
|
+
# 1-(AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P
|
43
|
+
# 2-AV:P or not(AV:N or PR:N or UI:N)
|
44
|
+
|
45
|
+
if m('AV') == 'N' && m('PR') == 'N' && m('UI') == 'N'
|
46
|
+
eq1 = '0'
|
47
|
+
elsif (m('AV') == 'N' || m('PR') == 'N' || m('UI') == 'N') &&
|
48
|
+
!(m('AV') == 'N' && m('PR') == 'N' && m('UI') == 'N') &&
|
49
|
+
(m('AV') != 'P')
|
50
|
+
eq1 = '1'
|
51
|
+
elsif m('AV') == 'P' ||
|
52
|
+
!(m('AV') == 'N' ||
|
53
|
+
m('PR') == 'N' ||
|
54
|
+
m('UI') == 'N')
|
55
|
+
eq1 = '2'
|
56
|
+
end
|
57
|
+
|
58
|
+
# EQ2: 0-(AC:L and AT:N)
|
59
|
+
# 1-(not(AC:L and AT:N))
|
60
|
+
|
61
|
+
if m('AC') == 'L' && m('AT') == 'N'
|
62
|
+
eq2 = '0'
|
63
|
+
elsif !(m('AC') == 'L' && m('AT') == 'N')
|
64
|
+
eq2 = '1'
|
65
|
+
end
|
66
|
+
|
67
|
+
# EQ3: 0-(VC:H and VI:H)
|
68
|
+
# 1-(not(VC:H and VI:H) and (VC:H or VI:H or VA:H))
|
69
|
+
# 2-not (VC:H or VI:H or VA:H)
|
70
|
+
if m('VC') == 'H' && m('VI') == 'H'
|
71
|
+
eq3 = '0'
|
72
|
+
elsif !(m('VC') == 'H' && m('VI') == 'H') &&
|
73
|
+
(m('VC') == 'H' || m('VI') == 'H' || m('VA') == 'H')
|
74
|
+
eq3 = '1'
|
75
|
+
elsif !(m('VC') == 'H' || m('VI') == 'H' || m('VA') == 'H')
|
76
|
+
eq3 = '2'
|
77
|
+
end
|
78
|
+
|
79
|
+
# EQ4: 0-(MSI:S or MSA:S)
|
80
|
+
# 1-not (MSI:S or MSA:S) and (SC:H or SI:H or SA:H)
|
81
|
+
# 2-not (MSI:S or MSA:S) and not (SC:H or SI:H or SA:H)
|
82
|
+
|
83
|
+
if m('MSI') == 'S' || m('MSA') == 'S'
|
84
|
+
eq4 = '0'
|
85
|
+
elsif !(m('MSI') == 'S' || m('MSA') == 'S') &&
|
86
|
+
(m('SC') == 'H' || m('SI') == 'H' || m('SA') == 'H')
|
87
|
+
eq4 = '1'
|
88
|
+
elsif !(m('MSI') == 'S' || m('MSA') == 'S') &&
|
89
|
+
!(m('SC') == 'H' || m('SI') == 'H' || m('SA') == 'H')
|
90
|
+
eq4 = '2'
|
91
|
+
end
|
92
|
+
|
93
|
+
# EQ5: 0-E:A
|
94
|
+
# 1-E:P
|
95
|
+
# 2-E:U
|
96
|
+
|
97
|
+
eq5 = case m('E')
|
98
|
+
when 'A'
|
99
|
+
'0'
|
100
|
+
when 'P'
|
101
|
+
'1'
|
102
|
+
when 'U'
|
103
|
+
'2'
|
104
|
+
else
|
105
|
+
# brphelps TODO added figure it out
|
106
|
+
'0'
|
107
|
+
end
|
108
|
+
|
109
|
+
# EQ6: 0-(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)
|
110
|
+
# 1-not[(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)]
|
111
|
+
|
112
|
+
if (m('CR') == 'H' && m('VC') == 'H') ||
|
113
|
+
(m('IR') == 'H' && m('VI') == 'H') ||
|
114
|
+
(m('AR') == 'H' && m('VA') == 'H')
|
115
|
+
eq6 = '0'
|
116
|
+
elsif !((m('CR') == 'H' && m('VC') == 'H') ||
|
117
|
+
(m('IR') == 'H' && m('VI') == 'H') ||
|
118
|
+
(m('AR') == 'H' && m('VA') == 'H'))
|
119
|
+
eq6 = '1'
|
120
|
+
end
|
121
|
+
|
122
|
+
eq1 + eq2 + eq3 + eq4 + eq5 + eq6
|
123
|
+
end
|
124
|
+
|
125
|
+
def score
|
126
|
+
# The following defines the index of each metric's values.
|
127
|
+
# It is used when looking for the highest vector part of the
|
128
|
+
# combinations produced by the MacroVector respective highest vectors.
|
129
|
+
|
130
|
+
macro_vector = retrieve_macro_vector
|
131
|
+
|
132
|
+
# Exception for no impact on system (shortcut)
|
133
|
+
return 0.0 if %w[VC VI VA SC SI SA].all? { |metric| m(metric) == 'N' }
|
134
|
+
|
135
|
+
value = LOOKUP[macro_vector]
|
136
|
+
|
137
|
+
# 1. For each of the EQs:
|
138
|
+
# a. The maximal scoring difference is determined as the difference
|
139
|
+
# between the current MacroVector and the lower MacroVector.
|
140
|
+
# i. If there is no lower MacroVector the available distance is
|
141
|
+
# set to nil and then ignored in the further calculations.
|
142
|
+
eq1_val = parse_int(macro_vector[0])
|
143
|
+
eq2_val = parse_int(macro_vector[1])
|
144
|
+
eq3_val = parse_int(macro_vector[2])
|
145
|
+
eq4_val = parse_int(macro_vector[3])
|
146
|
+
eq5_val = parse_int(macro_vector[4])
|
147
|
+
eq6_val = parse_int(macro_vector[5])
|
148
|
+
|
149
|
+
# compute next lower macro, it can also not exist
|
150
|
+
eq1_next_lower_macro = concat_and_stringify(eq1_val + 1, eq2_val, eq3_val, eq4_val, eq5_val, eq6_val)
|
151
|
+
eq2_next_lower_macro = concat_and_stringify(eq1_val, eq2_val + 1, eq3_val, eq4_val, eq5_val, eq6_val)
|
152
|
+
|
153
|
+
# eq3 and eq6 are related
|
154
|
+
if eq3_val == 1 && eq6_val == 1
|
155
|
+
# 11 --> 21
|
156
|
+
eq3eq6_next_lower_macro = concat_and_stringify(eq1_val, eq2_val, eq3_val + 1, eq4_val, eq5_val, eq6_val)
|
157
|
+
elsif eq3_val.zero? && eq6_val == 1
|
158
|
+
# 01 --> 11
|
159
|
+
eq3eq6_next_lower_macro = concat_and_stringify(eq1_val, eq2_val, eq3_val + 1, eq4_val, eq5_val, eq6_val)
|
160
|
+
elsif eq3_val == 1 && eq6_val.zero?
|
161
|
+
# 10 --> 11
|
162
|
+
eq3eq6_next_lower_macro = concat_and_stringify(eq1_val, eq2_val, eq3_val, eq4_val, eq5_val, eq6_val + 1)
|
163
|
+
elsif eq3_val.zero? && eq6_val.zero?
|
164
|
+
# 00 --> 01
|
165
|
+
# 00 --> 10
|
166
|
+
eq3eq6_next_lower_macro_left = concat_and_stringify(eq1_val, eq2_val, eq3_val, eq4_val, eq5_val, eq6_val + 1)
|
167
|
+
eq3eq6_next_lower_macro_right = concat_and_stringify(eq1_val, eq2_val, eq3_val + 1, eq4_val, eq5_val, eq6_val)
|
168
|
+
else
|
169
|
+
# 21 --> 32 (do not exist)
|
170
|
+
eq3eq6_next_lower_macro = concat_and_stringify(eq1_val, eq2_val, eq3_val + 1, eq4_val, eq5_val, eq6_val + 1)
|
171
|
+
end
|
172
|
+
|
173
|
+
eq4_next_lower_macro = concat_and_stringify(eq1_val, eq2_val, eq3_val, eq4_val + 1, eq5_val, eq6_val)
|
174
|
+
eq5_next_lower_macro = concat_and_stringify(eq1_val, eq2_val, eq3_val, eq4_val, eq5_val + 1, eq6_val)
|
175
|
+
|
176
|
+
# get their score, if the next lower macro score do not exist the result is NaN
|
177
|
+
score_eq1_next_lower_macro = LOOKUP[eq1_next_lower_macro]
|
178
|
+
score_eq2_next_lower_macro = LOOKUP[eq2_next_lower_macro]
|
179
|
+
|
180
|
+
if eq3_val.zero? && eq6_val.zero?
|
181
|
+
# multiple path take the one with higher score
|
182
|
+
score_eq3eq6_next_lower_macro_left = LOOKUP[eq3eq6_next_lower_macro_left]
|
183
|
+
score_eq3eq6_next_lower_macro_right = LOOKUP[eq3eq6_next_lower_macro_right]
|
184
|
+
|
185
|
+
score_eq3eq6_next_lower_macro = if score_eq3eq6_next_lower_macro_left > score_eq3eq6_next_lower_macro_right
|
186
|
+
score_eq3eq6_next_lower_macro_left
|
187
|
+
else
|
188
|
+
score_eq3eq6_next_lower_macro_right
|
189
|
+
end
|
190
|
+
else
|
191
|
+
score_eq3eq6_next_lower_macro = LOOKUP[eq3eq6_next_lower_macro]
|
192
|
+
end
|
193
|
+
|
194
|
+
score_eq4_next_lower_macro = LOOKUP[eq4_next_lower_macro]
|
195
|
+
score_eq5_next_lower_macro = LOOKUP[eq5_next_lower_macro]
|
196
|
+
|
197
|
+
# b. The severity distance of the to-be scored vector from a
|
198
|
+
# highest severity vector in the same MacroVector is determined.
|
199
|
+
eq1_maxes = get_eq_maxes(macro_vector, 1)
|
200
|
+
eq2_maxes = get_eq_maxes(macro_vector, 2)
|
201
|
+
eq3_eq6_maxes = get_eq_maxes(macro_vector, 3)[macro_vector[5]]
|
202
|
+
eq4_maxes = get_eq_maxes(macro_vector, 4)
|
203
|
+
eq5_maxes = get_eq_maxes(macro_vector, 5)
|
204
|
+
|
205
|
+
# compose them
|
206
|
+
max_vectors = []
|
207
|
+
eq1_maxes.each do |eq1_max|
|
208
|
+
eq2_maxes.each do |eq2_max|
|
209
|
+
eq3_eq6_maxes.each do |eq3_eq6_max|
|
210
|
+
eq4_maxes.each do |eq4_max|
|
211
|
+
eq5_maxes.each do |eq5max|
|
212
|
+
max_vectors.push(eq1_max + eq2_max + eq3_eq6_max + eq4_max + eq5max)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
severity_distance_av = severity_distance_pr = severity_distance_ui = 0
|
220
|
+
severity_distance_ac = severity_distance_at = severity_distance_vc = 0
|
221
|
+
severity_distance_vi = severity_distance_va = severity_distance_sc = 0
|
222
|
+
severity_distance_si = severity_distance_sa = severity_distance_cr = 0
|
223
|
+
severity_distance_ir = severity_distance_ar = 0
|
224
|
+
|
225
|
+
# Find the max vector to use i.e. one in the combination of all the highests
|
226
|
+
# that is greater or equal (severity distance) than the to-be scored vector.
|
227
|
+
max_vectors.each do |max_vector|
|
228
|
+
severity_distance_av = AV_LEVELS[m('AV')] - AV_LEVELS[extract_value_metric('AV', max_vector)]
|
229
|
+
severity_distance_pr = PR_LEVELS[m('PR')] - PR_LEVELS[extract_value_metric('PR', max_vector)]
|
230
|
+
severity_distance_ui = UI_LEVELS[m('UI')] - UI_LEVELS[extract_value_metric('UI', max_vector)]
|
231
|
+
|
232
|
+
severity_distance_ac = AC_LEVELS[m('AC')] - AC_LEVELS[extract_value_metric('AC', max_vector)]
|
233
|
+
severity_distance_at = AT_LEVELS[m('AT')] - AT_LEVELS[extract_value_metric('AT', max_vector)]
|
234
|
+
|
235
|
+
severity_distance_vc = VC_LEVELS[m('VC')] - VC_LEVELS[extract_value_metric('VC', max_vector)]
|
236
|
+
severity_distance_vi = VI_LEVELS[m('VI')] - VI_LEVELS[extract_value_metric('VI', max_vector)]
|
237
|
+
severity_distance_va = VA_LEVELS[m('VA')] - VA_LEVELS[extract_value_metric('VA', max_vector)]
|
238
|
+
|
239
|
+
severity_distance_sc = SC_LEVELS[m('SC')] - SC_LEVELS[extract_value_metric('SC', max_vector)]
|
240
|
+
severity_distance_si = SI_LEVELS[m('SI')] - SI_LEVELS[extract_value_metric('SI', max_vector)]
|
241
|
+
severity_distance_sa = SA_LEVELS[m('SA')] - SA_LEVELS[extract_value_metric('SA', max_vector)]
|
242
|
+
|
243
|
+
severity_distance_cr = subtract_or_nil(CR_LEVELS[m('CR')], CR_LEVELS[extract_value_metric('CR', max_vector)])
|
244
|
+
severity_distance_ir = subtract_or_nil(IR_LEVELS[m('IR')], IR_LEVELS[extract_value_metric('IR', max_vector)])
|
245
|
+
severity_distance_ar = subtract_or_nil(AR_LEVELS[m('AR')], AR_LEVELS[extract_value_metric('AR', max_vector)])
|
246
|
+
|
247
|
+
# if any is less than zero this is not the right max
|
248
|
+
if [severity_distance_av, severity_distance_pr, severity_distance_ui, severity_distance_ac,
|
249
|
+
severity_distance_at, severity_distance_vc, severity_distance_vi, severity_distance_va,
|
250
|
+
severity_distance_sc, severity_distance_si, severity_distance_sa,
|
251
|
+
severity_distance_cr,
|
252
|
+
severity_distance_ir, severity_distance_ar].compact.any?(&:negative?)
|
253
|
+
next
|
254
|
+
end
|
255
|
+
|
256
|
+
# if multiple maxes exist to reach it it is enough the first one
|
257
|
+
break
|
258
|
+
end
|
259
|
+
|
260
|
+
current_severity_distance_eq1 = severity_distance_av + severity_distance_pr + severity_distance_ui
|
261
|
+
current_severity_distance_eq2 = severity_distance_ac + severity_distance_at
|
262
|
+
current_severity_distance_eq3eq6 = sum_or_nil([severity_distance_vc, severity_distance_vi, severity_distance_va,
|
263
|
+
severity_distance_cr, severity_distance_ir, severity_distance_ar])
|
264
|
+
current_severity_distance_eq4 = severity_distance_sc + severity_distance_si + severity_distance_sa
|
265
|
+
|
266
|
+
step = 0.1
|
267
|
+
|
268
|
+
# if the next lower macro score do not exist the result is Nan
|
269
|
+
# Rename to maximal scoring difference (aka MSD)
|
270
|
+
available_distance_eq1 = score_eq1_next_lower_macro ? value - score_eq1_next_lower_macro : nil
|
271
|
+
available_distance_eq2 = score_eq2_next_lower_macro ? value - score_eq2_next_lower_macro : nil
|
272
|
+
available_distance_eq3eq6 = score_eq3eq6_next_lower_macro ? value - score_eq3eq6_next_lower_macro : nil
|
273
|
+
available_distance_eq4 = score_eq4_next_lower_macro ? value - score_eq4_next_lower_macro : nil
|
274
|
+
available_distance_eq5 = score_eq5_next_lower_macro ? value - score_eq5_next_lower_macro : nil
|
275
|
+
|
276
|
+
# some of them do not exist, we will find them by retrieving the score. If score null then do not exist
|
277
|
+
n_existing_lower = 0
|
278
|
+
|
279
|
+
normalized_severity_eq1 = 0
|
280
|
+
normalized_severity_eq2 = 0
|
281
|
+
normalized_severity_eq3eq6 = 0
|
282
|
+
normalized_severity_eq4 = 0
|
283
|
+
normalized_severity_eq5 = 0
|
284
|
+
|
285
|
+
# multiply by step because distance is pure
|
286
|
+
max_severity_eq1 = MAX_SEVERITY['eq1'][eq1_val] * step
|
287
|
+
max_severity_eq2 = MAX_SEVERITY['eq2'][eq2_val] * step
|
288
|
+
max_severity_eq3eq6 = MAX_SEVERITY['eq3eq6'][eq3_val][eq6_val] * step
|
289
|
+
max_severity_eq4 = MAX_SEVERITY['eq4'][eq4_val] * step
|
290
|
+
|
291
|
+
# c. The proportion of the distance is determined by dividing
|
292
|
+
# the severity distance of the to-be-scored vector by the depth
|
293
|
+
# of the MacroVector.
|
294
|
+
# d. The maximal scoring difference is multiplied by the proportion of
|
295
|
+
# distance.
|
296
|
+
unless nil?(available_distance_eq1)
|
297
|
+
n_existing_lower += 1
|
298
|
+
percent_to_next_eq1_severity = current_severity_distance_eq1 / max_severity_eq1
|
299
|
+
normalized_severity_eq1 = available_distance_eq1 * percent_to_next_eq1_severity
|
300
|
+
end
|
301
|
+
|
302
|
+
unless nil?(available_distance_eq2)
|
303
|
+
n_existing_lower += 1
|
304
|
+
percent_to_next_eq2_severity = current_severity_distance_eq2 / max_severity_eq2
|
305
|
+
normalized_severity_eq2 = available_distance_eq2 * percent_to_next_eq2_severity
|
306
|
+
end
|
307
|
+
|
308
|
+
unless nil?(available_distance_eq3eq6)
|
309
|
+
n_existing_lower += 1
|
310
|
+
percent_to_next_eq3eq6_severity = current_severity_distance_eq3eq6 / max_severity_eq3eq6
|
311
|
+
normalized_severity_eq3eq6 = available_distance_eq3eq6 * percent_to_next_eq3eq6_severity
|
312
|
+
end
|
313
|
+
|
314
|
+
unless nil?(available_distance_eq4)
|
315
|
+
n_existing_lower += 1
|
316
|
+
percent_to_next_eq4_severity = current_severity_distance_eq4 / max_severity_eq4
|
317
|
+
normalized_severity_eq4 = available_distance_eq4 * percent_to_next_eq4_severity
|
318
|
+
end
|
319
|
+
|
320
|
+
unless nil?(available_distance_eq5)
|
321
|
+
# for eq5 is always 0 the percentage
|
322
|
+
n_existing_lower += 1
|
323
|
+
percent_to_next_eq5_severity = 0
|
324
|
+
normalized_severity_eq5 = available_distance_eq5 * percent_to_next_eq5_severity
|
325
|
+
end
|
326
|
+
|
327
|
+
# 2. The mean of the above computed proportional distances is computed.
|
328
|
+
mean_distance = if n_existing_lower.zero?
|
329
|
+
0
|
330
|
+
else # sometimes we need to go up but there is nothing there, or down
|
331
|
+
# but there is nothing there so it's a change of 0.
|
332
|
+
(normalized_severity_eq1 + normalized_severity_eq2 + normalized_severity_eq3eq6 +
|
333
|
+
normalized_severity_eq4 + normalized_severity_eq5) / n_existing_lower
|
334
|
+
end
|
335
|
+
|
336
|
+
# 3. The score of the vector is the score of the MacroVector
|
337
|
+
# (i.e. the score of the highest severity vector) minus the mean
|
338
|
+
# distance so computed. This score is rounded to one decimal place.
|
339
|
+
value -= mean_distance
|
340
|
+
value = 0.0 if value.negative?
|
341
|
+
value = 10.0 if value > 10
|
342
|
+
value.round(1)
|
343
|
+
end
|
344
|
+
|
345
|
+
def get_eq_maxes(lookup, eq_value)
|
346
|
+
MAX_COMPOSED["eq#{eq_value}"][lookup[eq_value - 1]]
|
347
|
+
end
|
348
|
+
|
349
|
+
def nil?(value)
|
350
|
+
value.nil?
|
351
|
+
end
|
352
|
+
|
353
|
+
def concat_and_stringify(first, second, third, fourth, fifth, sixth)
|
354
|
+
String.new.concat(first.to_s, second.to_s, third.to_s, fourth.to_s, fifth.to_s, sixth.to_s)
|
355
|
+
end
|
356
|
+
|
357
|
+
def sum_or_nil(values)
|
358
|
+
return nil if values.any?(&:nil?)
|
359
|
+
|
360
|
+
values.sum
|
361
|
+
end
|
362
|
+
|
363
|
+
def subtract_or_nil(left, right)
|
364
|
+
return nil if left.nil? || right.nil?
|
365
|
+
|
366
|
+
left - right
|
367
|
+
end
|
368
|
+
|
369
|
+
def parse_int(string_to_parse)
|
370
|
+
Integer(string_to_parse)
|
371
|
+
end
|
372
|
+
|
373
|
+
def extract_value_metric(metric, str)
|
374
|
+
# indexOf gives first index of the metric, we then need to go over its size
|
375
|
+
index = str.index(metric) + metric.length + 1
|
376
|
+
extracted = str.slice(index..)
|
377
|
+
# remove what follow
|
378
|
+
if extracted.index('/').positive?
|
379
|
+
index_to_drop_after = extracted.index('/') - 1
|
380
|
+
metric_val = truncate(extracted, index_to_drop_after)
|
381
|
+
elsif extracted
|
382
|
+
metric_val = extracted
|
383
|
+
# case where it is the last metric so no ending /
|
384
|
+
end
|
385
|
+
|
386
|
+
metric_val
|
387
|
+
end
|
388
|
+
|
389
|
+
# rails defines this method on String, so we need to avoid polluting the
|
390
|
+
# String class to preserve Rails behavior.
|
391
|
+
def truncate(string_to_truncate, truncate_to)
|
392
|
+
return string_to_truncate.dup unless string_to_truncate.length > truncate_to
|
393
|
+
|
394
|
+
(string_to_truncate[0, truncate_to + 1]).to_s
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CvssSuite
|
2
|
+
module Cvss40Constants
|
3
|
+
# These constants were almost directly ported from the CVSS 4.0 calculator code found at https://github.com/FIRSTdotorg/cvss-v4-calculator/blob/ac71416d935ad2ac87cd107ff87024561ea954a7/app.js#L278C17-L278C18
|
4
|
+
|
5
|
+
AV_LEVELS = { 'N' => 0.0, 'A' => 0.1, 'L' => 0.2, 'P' => 0.3 }.freeze
|
6
|
+
PR_LEVELS = { 'N' => 0.0, 'L' => 0.1, 'H' => 0.2 }.freeze
|
7
|
+
UI_LEVELS = { 'N' => 0.0, 'P' => 0.1, 'A' => 0.2 }.freeze
|
8
|
+
|
9
|
+
AC_LEVELS = { 'L' => 0.0, 'H' => 0.1 }.freeze
|
10
|
+
AT_LEVELS = { 'N' => 0.0, 'P' => 0.1 }.freeze
|
11
|
+
|
12
|
+
VC_LEVELS = { 'H' => 0.0, 'L' => 0.1, 'N' => 0.2 }.freeze
|
13
|
+
VI_LEVELS = { 'H' => 0.0, 'L' => 0.1, 'N' => 0.2 }.freeze
|
14
|
+
VA_LEVELS = { 'H' => 0.0, 'L' => 0.1, 'N' => 0.2 }.freeze
|
15
|
+
|
16
|
+
SC_LEVELS = { 'H' => 0.1, 'L' => 0.2, 'N' => 0.3 }.freeze
|
17
|
+
SI_LEVELS = { 'S' => 0.0, 'H' => 0.1, 'L' => 0.2, 'N' => 0.3 }.freeze
|
18
|
+
SA_LEVELS = { 'S' => 0.0, 'H' => 0.1, 'L' => 0.2, 'N' => 0.3 }.freeze
|
19
|
+
|
20
|
+
CR_LEVELS = { 'H' => 0.0, 'M' => 0.1, 'L' => 0.2 }.freeze
|
21
|
+
IR_LEVELS = { 'H' => 0.0, 'M' => 0.1, 'L' => 0.2 }.freeze
|
22
|
+
AR_LEVELS = { 'H' => 0.0, 'M' => 0.1, 'L' => 0.2 }.freeze
|
23
|
+
|
24
|
+
E_LEVELS = { 'U' => 0.2, 'P' => 0.1, 'A' => 0 }.freeze
|
25
|
+
end
|
26
|
+
end
|