File Coverage

blib/lib/CVSS/v4.pm
Criterion Covered Total %
statement 272 301 90.3
branch 61 80 76.2
condition 162 215 75.3
subroutine 19 29 65.5
pod 18 20 90.0
total 532 645 82.4


line stmt bran cond sub pod time code
1             package CVSS::v4;
2              
3 3     3   95116 use feature ':5.10';
  3         5  
  3         382  
4 3     3   15 use strict;
  3         4  
  3         89  
5 3     3   440 use utf8;
  3         248  
  3         15  
6 3     3   117 use warnings;
  3         7  
  3         161  
7              
8 3     3   18 use Carp ();
  3         18  
  3         72  
9 3     3   13 use List::Util qw(max min);
  3         3  
  3         212  
10              
11 3     3   13 use base 'CVSS::Base';
  3         4  
  3         2086  
12 3     3   487 use CVSS::Constants ();
  3         6  
  3         215  
13              
14             our $VERSION = '1.15';
15             $VERSION =~ tr/_//d; ## no critic
16              
17 3     3   17 use constant DEBUG => $ENV{CVSS_DEBUG};
  3         5  
  3         15497  
18              
19 4     4 1 28 sub ATTRIBUTES { CVSS::Constants->CVSS4_ATTRIBUTES }
20 0     0 1 0 sub SCORE_SEVERITY { CVSS::Constants->CVSS4_SCORE_SEVERITY }
21 43134     43134 1 114119 sub NOT_DEFINED_VALUE { CVSS::Constants->CVSS4_NOT_DEFINED_VALUE }
22 2054     2054 1 23035 sub VECTOR_STRING_REGEX { CVSS::Constants->CVSS4_VECTOR_STRING_REGEX }
23 12324     12324 1 46015 sub METRIC_GROUPS { CVSS::Constants->CVSS4_METRIC_GROUPS }
24 0     0 1 0 sub METRIC_NAMES { CVSS::Constants->CVSS4_METRIC_NAMES }
25 0     0 1 0 sub METRIC_VALUES { CVSS::Constants->CVSS4_METRIC_VALUES }
26              
27             my $MAX_COMPOSED = CVSS::Constants->CVSS4_MAX_COMPOSED;
28             my $CVSS_LOOKUP_GLOBAL = CVSS::Constants->CVSS4_LOOKUP_GLOBAL;
29             my $MAX_SEVERITY = CVSS::Constants->CVSS4_MAX_SEVERITY;
30              
31 6162     6162 1 30815 sub version {'4.0'}
32              
33             sub macro_vector {
34              
35 2040     2040 1 2365 my ($self) = @_;
36              
37 2040         2551 my $eq1 = undef;
38 2040         1835 my $eq2 = undef;
39 2040         1933 my $eq3 = undef;
40 2040         2053 my $eq4 = undef;
41 2040         1681 my $eq5 = undef;
42 2040         1958 my $eq6 = undef;
43              
44              
45             # Specification https://www.first.org/cvss/v4.0/specification-document
46              
47              
48             # EQ1 (Table 24)
49              
50             # Levels Constraints
51             # 0 AV:N and PR:N and UI:N
52             # 1 (AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P
53             # 2 AV:P or not(AV:N or PR:N or UI:N)
54              
55 2040 100 100     2510 $eq1 = 0 if ($self->M('AV') eq 'N' && $self->M('PR') eq 'N' && $self->M('UI') eq 'N');
      100        
56              
57 2040 100 100     3132 $eq1 = 1
      100        
      100        
      100        
58             if (($self->M('AV') eq 'N' || $self->M('PR') eq 'N' || $self->M('UI') eq 'N')
59             && !($self->M('AV') eq 'N' && $self->M('PR') eq 'N' && $self->M('UI') eq 'N')
60             && !($self->M('AV') eq 'P'));
61              
62 2040 100 100     3034 $eq1 = 2 if ($self->M('AV') eq 'P' || !($self->M('AV') eq 'N' || $self->M('PR') eq 'N' || $self->M('UI') eq 'N'));
      100        
63              
64 2040         2065 DEBUG and say STDERR "-- MacroVector - EQ1 : $eq1";
65              
66              
67             # EQ2 (Table 25)
68              
69             # Levels Constraints
70             # 0 AC:L and AT:N
71             # 1 not (AC:L and AT:N)
72              
73 2040 100 100     3024 $eq2 = 0 if ($self->M('AC') eq 'L' && $self->M('AT') eq 'N');
74 2040 100 100     2506 $eq2 = 1 if (!($self->M('AC') eq 'L' && $self->M('AT') eq 'N'));
75              
76 2040         2162 DEBUG and say STDERR "-- MacroVector - EQ2 : $eq2";
77              
78             # EQ3 (Table 26)
79             # Levels Constraints
80             # 0 VC:H and VI:H
81             # 1 not (VC:H and VI:H) and (VC:H or VI:H or VA:H)
82             # 2 not (VC:H or VI:H or VA:H)
83              
84 2040 100 100     2252 $eq3 = 0 if ($self->M('VC') eq 'H' && $self->M('VI') eq 'H');
85              
86 2040 100 100     2708 $eq3 = 1
      100        
      100        
87             if (!($self->M('VC') eq 'H' && $self->M('VI') eq 'H')
88             && ($self->M('VC') eq 'H' || $self->M('VI') eq 'H' || $self->M('VA') eq 'H'));
89              
90 2040 100 100     2740 $eq3 = 2 if (!($self->M('VC') eq 'H' || $self->M('VI') eq 'H' || $self->M('VA') eq 'H'));
      100        
91              
92 2040         2198 DEBUG and say STDERR "-- MacroVector - EQ3 : $eq3";
93              
94              
95             # EQ4 (Table 27)
96             # Levels Constraints
97             # 0 MSI:S or MSA:S
98             # 1 not (MSI:S or MSA:S) and (SC:H or SI:H or SA:H)
99             # 2 not (MSI:S or MSA:S) and not (SC:H or SI:H or SA:H)
100              
101 2040 50 33     2194 $eq4 = 0 if ($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S');
102              
103 2040 100 33     2671 $eq4 = 1
      100        
      66        
104             if (!($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S')
105             && ($self->M('SC') eq 'H' || $self->M('SI') eq 'H' || $self->M('SA') eq 'H'));
106              
107 2040 100 33     3041 $eq4 = 2
      100        
      100        
      66        
108             if (!($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S')
109             && !(($self->M('SC') eq 'H' || $self->M('SI') eq 'H' || $self->M('SA') eq 'H')));
110              
111 2040         2170 DEBUG and say STDERR "-- MacroVector - EQ4 : $eq4";
112              
113             # EQ5 (Table 28)
114              
115             # Levels Constraints
116             # 0 E:A
117             # 1 E:P
118             # 2 E:U
119              
120 2040 50       2134 $eq5 = 0 if ($self->M('E') eq 'A');
121 2040 50       2459 $eq5 = 1 if ($self->M('E') eq 'P');
122 2040 50       2708 $eq5 = 2 if ($self->M('E') eq 'U');
123              
124 2040         1868 DEBUG and say STDERR "-- MacroVector - EQ5 : $eq5";
125              
126             # EQ6 (Table 29)
127              
128             # Levels Constraints
129             # 0 (CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)
130             # 1 not (CR:H and VC:H) and not (IR:H and VI:H) and not (AR:H and VA:H)
131              
132 2040 100 66     2225 $eq6 = 0
      66        
      100        
      66        
      100        
133             if (($self->M('CR') eq 'H' && $self->M('VC') eq 'H')
134             || ($self->M('IR') eq 'H' && $self->M('VI') eq 'H')
135             || ($self->M('AR') eq 'H' && $self->M('VA') eq 'H'));
136              
137 2040 100 66     3131 $eq6 = 1
      66        
      100        
      66        
      100        
138             if (!($self->M('CR') eq 'H' && $self->M('VC') eq 'H')
139             && !($self->M('IR') eq 'H' && $self->M('VI') eq 'H')
140             && !($self->M('AR') eq 'H' && $self->M('VA') eq 'H'));
141              
142 2040         2186 DEBUG and say STDERR "-- MacroVector - EQ6 : $eq6";
143              
144 2040         4382 my @macro_vector = ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6);
145 2040         6492 my $macro_vector = join '', @macro_vector;
146              
147 2040         2006 DEBUG and say STDERR "-- MacroVector : $macro_vector";
148              
149 2040         5798 my $SEVERITY = {0 => 'HIGH', 1 => 'MEDIUM', 2 => 'LOW'};
150              
151 2040         4227 $self->{exploitability} = $SEVERITY->{$eq1};
152 2040         1940 DEBUG and say STDERR "-- MacroVector EQ1 - Exploitability : $self->{exploitability}";
153              
154 2040         3101 $self->{complexity} = $SEVERITY->{$eq2};
155 2040         1673 DEBUG and say STDERR "-- MacroVector EQ2 - Complexity : $self->{complexity}";
156              
157 2040         5096 $self->{vulnerable_system} = $SEVERITY->{$eq3};
158 2040         1715 DEBUG and say STDERR "-- MacroVector EQ3 - Vulnerable System : $self->{vulnerable_system}";
159              
160 2040         2707 $self->{subsequent_system} = $SEVERITY->{$eq4};
161 2040         1939 DEBUG and say STDERR "-- MacroVector EQ4 - Subsequent System : $self->{subsequent_system}";
162              
163 2040         2553 $self->{exploitation} = $SEVERITY->{$eq5};
164 2040         1670 DEBUG and say STDERR "-- MacroVector EQ5 - Exploitation : $self->{exploitation}";
165              
166 2040         3000 $self->{security_requirements} = $SEVERITY->{$eq6};
167 2040         1675 DEBUG and say STDERR "-- MacroVector EQ6 - Security Requirements : $self->{security_requirements}";
168              
169 2040 50       9998 return wantarray ? @macro_vector : "$macro_vector";
170              
171             }
172              
173 0     0 1 0 sub exploitability { shift->{exploitability} }
174 0     0 1 0 sub complexity { shift->{complexity} }
175 0     0 1 0 sub vulnerable_system { shift->{vulnerable_system} }
176 0     0 1 0 sub subsequent_system { shift->{subsequent_system} }
177 0     0 1 0 sub exploitation { shift->{exploitation} }
178 0     0 1 0 sub security_requirements { shift->{security_requirements} }
179              
180             sub M {
181              
182 167662     167662 1 168748 my ($self, $metric) = @_;
183              
184 167662         202007 my $value = $self->SUPER::M($metric);
185              
186             # (From table 12)
187             # This is the default value and is equivalent to Attacked (A) for the
188             # purposes of the calculation of the score by assuming the worst case.
189 167662 100 66     218630 return 'A' if ($metric eq 'E' && $value eq 'X');
190              
191             # (From table 13)
192             # [...] This is the default value. Assigning this value indicates there is
193             # insufficient information to choose one of the other values. This has the
194             # same effect as assigning High as the worst case.
195 161542 100 66     196395 return 'H' if ($metric eq 'CR' && $value eq 'X');
196 151693 100 66     178189 return 'H' if ($metric eq 'IR' && $value eq 'X');
197 143446 100 66     168244 return 'H' if ($metric eq 'AR' && $value eq 'X');
198              
199 135811         250533 return $value;
200              
201             }
202              
203             sub calculate_score {
204              
205 2054     2054 1 3418 my ($self) = @_;
206              
207 2054 50       2679 if (%{$self->metrics}) {
  2054         4369  
208 2054         2614 for (@{$self->METRIC_GROUPS->{base}}) {
  2054         3928  
209 22594 50       32793 Carp::croak sprintf('Missing base metric (%s)', $_) unless ($self->metrics->{$_});
210             }
211             }
212              
213             # Set NOT_DEFINED
214 2054   50     3490 $self->metrics->{E} //= 'X';
215              
216 2054   50     3929 $self->metrics->{CR} //= 'X';
217 2054   50     3385 $self->metrics->{IR} //= 'X';
218 2054   50     3177 $self->metrics->{AR} //= 'X';
219 2054   50     3330 $self->metrics->{MAV} //= 'X';
220 2054   50     3310 $self->metrics->{MAC} //= 'X';
221 2054   50     3166 $self->metrics->{MAT} //= 'X';
222 2054   50     3233 $self->metrics->{MPR} //= 'X';
223 2054   50     3014 $self->metrics->{MUI} //= 'X';
224 2054   50     3064 $self->metrics->{MVC} //= 'X';
225 2054   50     3837 $self->metrics->{MVI} //= 'X';
226 2054   50     3401 $self->metrics->{MVA} //= 'X';
227 2054   50     3027 $self->metrics->{MSC} //= 'X';
228 2054   50     3085 $self->metrics->{MSI} //= 'X';
229 2054   50     3199 $self->metrics->{MSA} //= 'X';
230              
231 2054   50     3037 $self->metrics->{S} //= 'X';
232 2054   50     3083 $self->metrics->{AU} //= 'X';
233 2054   50     3304 $self->metrics->{R} //= 'X';
234 2054   50     3451 $self->metrics->{V} //= 'X';
235 2054   50     3496 $self->metrics->{RE} //= 'X';
236 2054   50     3144 $self->metrics->{U} //= 'X';
237              
238              
239             # The following defines the index of each metric's values.
240             # It is used when looking for the highest vector part of the
241             # combinations produced by the MacroVector respective highest vectors.
242 2054         5809 my $AV_levels = {N => 0.0, A => 0.1, L => 0.2, P => 0.3};
243 2054         3717 my $PR_levels = {N => 0.0, L => 0.1, H => 0.2};
244 2054         3060 my $UI_levels = {N => 0.0, P => 0.1, A => 0.2};
245              
246 2054         2970 my $AC_levels = {L => 0.0, H => 0.1};
247 2054         2821 my $AT_levels = {N => 0.0, P => 0.1};
248              
249 2054         3268 my $VC_levels = {H => 0.0, L => 0.1, N => 0.2};
250 2054         2919 my $VI_levels = {H => 0.0, L => 0.1, N => 0.2};
251 2054         3219 my $VA_levels = {H => 0.0, L => 0.1, N => 0.2};
252              
253 2054         2863 my $SC_levels = {H => 0.1, L => 0.2, N => 0.3};
254 2054         3573 my $SI_levels = {S => 0.0, H => 0.1, L => 0.2, N => 0.3};
255 2054         2897 my $SA_levels = {S => 0.0, H => 0.1, L => 0.2, N => 0.3};
256              
257 2054         3124 my $CR_levels = {H => 0.0, M => 0.1, L => 0.2};
258 2054         2919 my $IR_levels = {H => 0.0, M => 0.1, L => 0.2};
259 2054         3138 my $AR_levels = {H => 0.0, M => 0.1, L => 0.2};
260              
261 2054         3422 my $E_levels = {U => 0.2, P => 0.1, A => 0.0};
262              
263 2054 100 100     3180 if ( $self->M('VC') eq 'N'
      100        
      100        
      100        
      100        
264             && $self->M('VI') eq 'N'
265             && $self->M('VA') eq 'N'
266             && $self->M('SC') eq 'N'
267             && $self->M('SI') eq 'N'
268             && $self->M('SA') eq 'N')
269             {
270 14         21 $self->{scores}->{base} = '0.0';
271 14         74 return 1;
272             }
273              
274 2040         3812 my @macro_vector = $self->macro_vector;
275 2040         3232 my $macro_vector = join '', @macro_vector;
276              
277 2040         4542 $self->{macro_vector} = $macro_vector;
278              
279 2040         3497 my ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6) = @macro_vector;
280              
281 2040         3466 my $value = $CVSS_LOOKUP_GLOBAL->{$macro_vector};
282              
283 2040         3441 my $eq1_next_lower_macro = join '', ($eq1 + 1, $eq2, $eq3, $eq4, $eq5, $eq6);
284 2040         2962 my $eq2_next_lower_macro = join '', ($eq1, $eq2 + 1, $eq3, $eq4, $eq5, $eq6);
285 2040         2045 my $eq3eq6_next_lower_macro = undef;
286 2040         1881 my $eq3eq6_next_lower_macro_left = undef;
287 2040         1880 my $eq3eq6_next_lower_macro_right = undef;
288              
289 2040 50 66     8449 if ($eq3 == 1 && $eq6 == 1) {
    50 66        
    100 66        
    100 66        
290 0         0 $eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
291             }
292             elsif ($eq3 == 0 && $eq6 == 1) {
293 0         0 $eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
294             }
295             elsif ($eq3 == 1 && $eq6 == 0) {
296 830         1610 $eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6 + 1);
297             }
298             elsif ($eq3 == 0 && $eq6 == 0) {
299 471         830 $eq3eq6_next_lower_macro_left = join '', ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6 + 1);
300 471         801 $eq3eq6_next_lower_macro_right = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
301             }
302             else {
303 739         1266 $eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6 + 1);
304             }
305              
306 2040         3079 my $eq4_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4 + 1, $eq5, $eq6);
307 2040         2715 my $eq5_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4, $eq5 + 1, $eq6);
308              
309 2040   100     3898 my $score_eq1_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq1_next_lower_macro} || 'NaN';
310 2040   100     3650 my $score_eq2_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq2_next_lower_macro} || 'NaN';
311 2040         1753 my $score_eq3eq6_next_lower_macro_left = undef;
312 2040         1586 my $score_eq3eq6_next_lower_macro_right = undef;
313 2040         1581 my $score_eq3eq6_next_lower_macro = undef;
314              
315 2040 100 66     3522 if ($eq3 == 0 && $eq6 == 0) {
316              
317             # multiple path take the one with higher score
318 471   50     794 $score_eq3eq6_next_lower_macro_left = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro_left} || 'NaN';
319 471   50     694 $score_eq3eq6_next_lower_macro_right = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro_right} || 'NaN';
320              
321 471         1252 $score_eq3eq6_next_lower_macro = max($score_eq3eq6_next_lower_macro_left, $score_eq3eq6_next_lower_macro_right);
322              
323             }
324             else {
325 1569   100     2632 $score_eq3eq6_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro} || 'NaN';
326             }
327              
328              
329 2040   100     3349 my $score_eq4_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq4_next_lower_macro} || 'NaN';
330 2040   50     2950 my $score_eq5_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq5_next_lower_macro} || 'NaN';
331              
332             # b. The severity distance of the to-be scored vector from a
333             # highest severity vector in the same MacroVector is determined.
334 2040         2748 my $eq1_maxes = $MAX_COMPOSED->{eq1}->{$eq1};
335 2040         2422 my $eq2_maxes = $MAX_COMPOSED->{eq2}->{$eq2};
336 2040         2418 my $eq3_eq6_maxes = $MAX_COMPOSED->{eq3}->{$eq3}->{$eq6};
337 2040         2055 my $eq4_maxes = $MAX_COMPOSED->{eq4}->{$eq4};
338 2040         2454 my $eq5_maxes = $MAX_COMPOSED->{eq5}->{$eq5};
339              
340             # compose them
341 2040         2044 my @max_vectors = ();
342 2040         1689 for my $eq1_max (@{$eq1_maxes}) {
  2040         3136  
343 5295         4269 for my $eq2_max (@{$eq2_maxes}) {
  5295         4853  
344 8141         6207 for my $eq3_eq6_max (@{$eq3_eq6_maxes}) {
  8141         7614  
345 11473         8658 for my $eq4_max (@{$eq4_maxes}) {
  11473         10954  
346 11473         8614 for my $eq5_max (@{$eq5_maxes}) {
  11473         10106  
347 11473         25854 push @max_vectors, join '', ($eq1_max, $eq2_max, $eq3_eq6_max, $eq4_max, $eq5_max);
348             }
349             }
350             }
351             }
352             }
353              
354 2040         1970 DEBUG and say STDERR "-- MaxVectors: @max_vectors";
355              
356 2040         1811 my $severity_distance_AV = undef;
357 2040         1765 my $severity_distance_PR = undef;
358 2040         1575 my $severity_distance_UI = undef;
359              
360 2040         1858 my $severity_distance_AC = undef;
361 2040         1576 my $severity_distance_AT = undef;
362              
363 2040         1564 my $severity_distance_VC = undef;
364 2040         1554 my $severity_distance_VI = undef;
365 2040         1529 my $severity_distance_VA = undef;
366              
367 2040         1552 my $severity_distance_SC = undef;
368 2040         1702 my $severity_distance_SI = undef;
369 2040         1528 my $severity_distance_SA = undef;
370              
371 2040         1531 my $severity_distance_CR = undef;
372 2040         1568 my $severity_distance_IR = undef;
373 2040         1655 my $severity_distance_AR = undef;
374              
375              
376             # Find the max vector to use i.e. one in the combination of all the highests
377             # that is greater or equal (severity distance) than the to-be scored vector.
378 2040         2139 DISTANCE: foreach my $max_vector (@max_vectors) {
379              
380             $severity_distance_AV
381 5769         7192 = $AV_levels->{$self->M("AV")} - $AV_levels->{$self->extract_value_metric("AV", $max_vector)};
382             $severity_distance_PR
383 5769         8448 = $PR_levels->{$self->M("PR")} - $PR_levels->{$self->extract_value_metric("PR", $max_vector)};
384             $severity_distance_UI
385 5769         8203 = $UI_levels->{$self->M("UI")} - $UI_levels->{$self->extract_value_metric("UI", $max_vector)};
386              
387             $severity_distance_AC
388 5769         7506 = $AC_levels->{$self->M("AC")} - $AC_levels->{$self->extract_value_metric("AC", $max_vector)};
389             $severity_distance_AT
390 5769         7377 = $AT_levels->{$self->M("AT")} - $AT_levels->{$self->extract_value_metric("AT", $max_vector)};
391              
392             $severity_distance_VC
393 5769         7219 = $VC_levels->{$self->M("VC")} - $VC_levels->{$self->extract_value_metric("VC", $max_vector)};
394             $severity_distance_VI
395 5769         7229 = $VI_levels->{$self->M("VI")} - $VI_levels->{$self->extract_value_metric("VI", $max_vector)};
396             $severity_distance_VA
397 5769         7274 = $VA_levels->{$self->M("VA")} - $VA_levels->{$self->extract_value_metric("VA", $max_vector)};
398              
399             $severity_distance_SC
400 5769         7008 = $SC_levels->{$self->M("SC")} - $SC_levels->{$self->extract_value_metric("SC", $max_vector)};
401             $severity_distance_SI
402 5769         8424 = $SI_levels->{$self->M("SI")} - $SI_levels->{$self->extract_value_metric("SI", $max_vector)};
403             $severity_distance_SA
404 5769         7362 = $SA_levels->{$self->M("SA")} - $SA_levels->{$self->extract_value_metric("SA", $max_vector)};
405              
406             $severity_distance_CR
407 5769         8271 = $CR_levels->{$self->M("CR")} - $CR_levels->{$self->extract_value_metric("CR", $max_vector)};
408             $severity_distance_IR
409 5769         7886 = $IR_levels->{$self->M("IR")} - $IR_levels->{$self->extract_value_metric("IR", $max_vector)};
410             $severity_distance_AR
411 5769         7905 = $AR_levels->{$self->M("AR")} - $AR_levels->{$self->extract_value_metric("AR", $max_vector)};
412              
413              
414 5769         15224 my @check = (
415             $severity_distance_AV, $severity_distance_PR, $severity_distance_UI, $severity_distance_AC,
416             $severity_distance_AT, $severity_distance_VC, $severity_distance_VI, $severity_distance_VA,
417             $severity_distance_SC, $severity_distance_SI, $severity_distance_SA, $severity_distance_CR,
418             $severity_distance_IR, $severity_distance_AR
419             );
420              
421             # if any is less than zero this is not the right max
422 5769         6997 foreach (@check) {
423 36646 100       45109 next DISTANCE if ($_ < 0);
424             }
425              
426             # if multiple maxes exist to reach it it is enough the first one
427 2040         3269 last;
428             }
429              
430 2040         2074 my $step = 0.1;
431              
432 2040         3902 my $current_severity_distance_eq1 = ($severity_distance_AV + $severity_distance_PR + $severity_distance_UI);
433 2040         2307 my $current_severity_distance_eq2 = ($severity_distance_AC + $severity_distance_AT);
434 2040         3186 my $current_severity_distance_eq3eq6
435             = ( $severity_distance_VC
436             + $severity_distance_VI
437             + $severity_distance_VA
438             + $severity_distance_CR
439             + $severity_distance_IR
440             + $severity_distance_AR);
441 2040         2313 my $current_severity_distance_eq4 = ($severity_distance_SC + $severity_distance_SI + $severity_distance_SA);
442 2040         1801 my $current_severity_distance_eq5 = 0;
443              
444             # if the next lower macro score do not exist the result is Nan
445             # Rename to maximal scoring difference (aka MSD)
446 2040         2135 my $available_distance_eq1 = $value - $score_eq1_next_lower_macro;
447 2040         2935 my $available_distance_eq2 = $value - $score_eq2_next_lower_macro;
448 2040         2150 my $available_distance_eq3eq6 = $value - $score_eq3eq6_next_lower_macro;
449 2040         2065 my $available_distance_eq4 = $value - $score_eq4_next_lower_macro;
450 2040         1787 my $available_distance_eq5 = $value - $score_eq5_next_lower_macro;
451              
452 2040         1710 my $percent_to_next_eq1_severity = 0;
453 2040         1721 my $percent_to_next_eq2_severity = 0;
454 2040         1596 my $percent_to_next_eq3eq6_severity = 0;
455 2040         1562 my $percent_to_next_eq4_severity = 0;
456 2040         1739 my $percent_to_next_eq5_severity = 0;
457              
458 2040         1732 my $normalized_severity_eq1 = 0;
459 2040         1615 my $normalized_severity_eq2 = 0;
460 2040         2022 my $normalized_severity_eq3eq6 = 0;
461 2040         1824 my $normalized_severity_eq4 = 0;
462 2040         1667 my $normalized_severity_eq5 = 0;
463              
464             # multiply by step because distance is pure
465 2040         3488 my $max_severity_eq1 = $MAX_SEVERITY->{eq1}->{$eq1} * $step;
466 2040         2774 my $max_severity_eq2 = $MAX_SEVERITY->{eq2}->{$eq2} * $step;
467 2040         2843 my $max_severity_eq3eq6 = $MAX_SEVERITY->{eq3eq6}->{$eq3}->{$eq6} * $step;
468 2040         2347 my $max_severity_eq4 = $MAX_SEVERITY->{eq4}->{$eq4} * $step;
469              
470              
471             # c. The proportion of the distance is determined by dividing
472             # the severity distance of the to-be-scored vector by the depth
473             # of the MacroVector.
474             # d. The maximal scoring difference is multiplied by the proportion of
475             # distance.
476              
477 2040         1903 my $n_existing_lower = 0;
478              
479 2040 100 66     2918 if (!isNaN($available_distance_eq1) && $available_distance_eq1 >= 0) {
480 1797         1973 $n_existing_lower += 1;
481 1797         2144 $percent_to_next_eq1_severity = ($current_severity_distance_eq1) / $max_severity_eq1;
482 1797         2135 $normalized_severity_eq1 = $available_distance_eq1 * $percent_to_next_eq1_severity;
483             }
484              
485 2040 100 66     2871 if (!isNaN($available_distance_eq2) && $available_distance_eq2 >= 0) {
486 947         792 $n_existing_lower += 1;
487 947         917 $percent_to_next_eq2_severity = ($current_severity_distance_eq2) / $max_severity_eq2;
488 947         863 $normalized_severity_eq2 = $available_distance_eq2 * $percent_to_next_eq2_severity;
489             }
490              
491 2040 100 66     2180 if (!isNaN($available_distance_eq3eq6) && $available_distance_eq3eq6 >= 0) {
492 1301         1278 $n_existing_lower += 1;
493 1301         1346 $percent_to_next_eq3eq6_severity = ($current_severity_distance_eq3eq6) / $max_severity_eq3eq6;
494 1301         1215 $normalized_severity_eq3eq6 = $available_distance_eq3eq6 * $percent_to_next_eq3eq6_severity;
495             }
496              
497 2040 100 66     2110 if (!isNaN($available_distance_eq4) && $available_distance_eq4 >= 0) {
498 674         707 $n_existing_lower += 1;
499 674         835 $percent_to_next_eq4_severity = ($current_severity_distance_eq4) / $max_severity_eq4;
500 674         611 $normalized_severity_eq4 = $available_distance_eq4 * $percent_to_next_eq4_severity;
501             }
502              
503 2040 50 33     2176 if (!isNaN($available_distance_eq5) && $available_distance_eq5 >= 0) {
504 2040         1763 $n_existing_lower += 1;
505 2040         1828 $percent_to_next_eq5_severity = 0;
506 2040         2009 $normalized_severity_eq5 = $available_distance_eq5 * $percent_to_next_eq5_severity;
507             }
508              
509 2040         2196 my $mean_distance = undef;
510              
511             # 2. The mean of the above computed proportional distances is computed.
512 2040 50       2635 if ($n_existing_lower == 0) {
513 0         0 $mean_distance = 0;
514             }
515             else {
516             # sometimes we need to go up but there is nothing there, or down but there is nothing there so it's a change of 0.
517 2040         2999 $mean_distance
518             = ( $normalized_severity_eq1
519             + $normalized_severity_eq2
520             + $normalized_severity_eq3eq6
521             + $normalized_severity_eq4
522             + $normalized_severity_eq5)
523             / $n_existing_lower;
524             }
525              
526             # /
527              
528 2040         1641 DEBUG and say STDERR "-- Value: $value - MeanDistance: $mean_distance";
529              
530             # 3. The score of the vector is the score of the MacroVector
531             # (i.e. the score of the highest severity vector) minus the mean
532             # distance so computed. This score is rounded to one decimal place.
533 2040         1925 $value -= $mean_distance;
534              
535 2040         1480 DEBUG and say STDERR "-- Value $value";
536              
537 2040         4405 $value = max(0.0, $value);
538 2040         2443 $value = min(10.0, $value);
539              
540 2040         3345 my $base_score = int($value * 10 + 0.5) / 10;
541              
542 2040         1641 DEBUG and say STDERR "-- BaseScore: $base_score ($value)";
543              
544 2040         3760 $self->{scores}->{base} = $base_score;
545              
546 2040         15772 return 1;
547              
548             }
549              
550             sub extract_value_metric {
551 80766     80766 0 87136 my ($self, $metric, $vector_string) = @_;
552 80766         693179 my %metrics = split /[\/:]/, $vector_string;
553 80766         227243 return $metrics{$metric};
554             }
555              
556 10200     10200 0 22475 sub isNaN { !defined($_[0] <=> 9**9**9) }
557              
558             sub to_xml {
559              
560 0     0 1   my ($self) = @_;
561              
562 0           my $metric_value_names = $self->METRIC_NAMES;
563              
564 0 0         $self->calculate_score unless ($self->base_score);
565              
566 0           my $version = $self->version;
567 0           my $metrics = $self->metrics;
568 0           my $base_score = $self->base_score;
569 0           my $base_severity = $self->base_severity;
570 0           my $environmental_score = '';
571 0           my $environmental_severity = '';
572              
573 0           my $xml_metrics = <<"XML";
574            
575             $metric_value_names->{AV}->{values}->{$metrics->{AV}}
576             $metric_value_names->{AC}->{values}->{$metrics->{AC}}
577             $metric_value_names->{AT}->{values}->{$metrics->{AT}}
578             $metric_value_names->{PR}->{values}->{$metrics->{PR}}
579             $metric_value_names->{UI}->{values}->{$metrics->{UI}}
580             $metric_value_names->{VC}->{values}->{$metrics->{VC}}
581             $metric_value_names->{VI}->{values}->{$metrics->{VI}}
582             $metric_value_names->{VA}->{values}->{$metrics->{VA}}
583             $metric_value_names->{SC}->{values}->{$metrics->{SC}}
584             $metric_value_names->{SI}->{values}->{$metrics->{SI}}
585             $metric_value_names->{SA}->{values}->{$metrics->{SA}}
586             $base_score
587             $base_severity
588            
589             XML
590              
591 0 0         if ($self->metric_group_is_set('threat')) {
592 0           $xml_metrics .= <<"XML";
593            
594             $metric_value_names->{E}->{values}->{$metrics->{E}}
595            
596             XML
597             }
598              
599 0 0         if ($self->metric_group_is_set('environmental')) {
600 0           $xml_metrics .= <<"XML";
601            
602             $metric_value_names->{CR}->{values}->{$metrics->{CR}}
603             $metric_value_names->{IR}->{values}->{$metrics->{IR}}
604             $metric_value_names->{AR}->{values}->{$metrics->{AR}}
605             $metric_value_names->{MAV}->{values}->{$metrics->{MAV}}
606             $metric_value_names->{MAC}->{values}->{$metrics->{MAC}}
607             $metric_value_names->{MAT}->{values}->{$metrics->{MAT}}
608             $metric_value_names->{MPR}->{values}->{$metrics->{MPR}}
609             $metric_value_names->{MUI}->{values}->{$metrics->{MUI}}
610             $metric_value_names->{MVC}->{values}->{$metrics->{MVC}}
611             $metric_value_names->{MVI}->{values}->{$metrics->{MVI}}
612             $metric_value_names->{MVA}->{values}->{$metrics->{MVA}}
613             $metric_value_names->{MSC}->{values}->{$metrics->{MSC}}
614             $metric_value_names->{MSI}->{values}->{$metrics->{MSI}}
615             $metric_value_names->{MSA}->{values}->{$metrics->{MSA}}
616             $environmental_score
617             $environmental_severity
618            
619             XML
620             }
621              
622 0 0         if ($self->metric_group_is_set('supplemental')) {
623 0           $xml_metrics .= <<"XML";
624            
625             $metric_value_names->{S}->{values}->{$metrics->{S}}
626             $metric_value_names->{AU}->{values}->{$metrics->{AU}}
627             $metric_value_names->{R}->{values}->{$metrics->{R}}
628             $metric_value_names->{V}->{values}->{$metrics->{V}}
629             $metric_value_names->{RE}->{values}->{$metrics->{RE}}
630             $metric_value_names->{U}->{values}->{$metrics->{U}}
631            
632             XML
633             }
634              
635 0           my $xml = <<"XML";
636            
637            
638             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
639             xsi:schemaLocation="https://www.first.org/cvss/cvss-v$version.xsd https://www.first.org/cvss/cvss-v$version.xsd"
640             >
641              
642             $xml_metrics
643            
644             XML
645              
646             }
647              
648             1;
649              
650             1;
651             __END__