File Coverage

blib/lib/CVSS/v4.pm
Criterion Covered Total %
statement 272 301 90.3
branch 61 80 76.2
condition 161 215 74.8
subroutine 19 29 65.5
pod 18 20 90.0
total 531 645 82.3


line stmt bran cond sub pod time code
1             package CVSS::v4;
2              
3 3     3   109278 use feature ':5.10';
  3         6  
  3         494  
4 3     3   18 use strict;
  3         5  
  3         98  
5 3     3   452 use utf8;
  3         218  
  3         16  
6 3     3   71 use warnings;
  3         6  
  3         141  
7              
8 3     3   12 use Carp ();
  3         5  
  3         90  
9 3     3   14 use List::Util qw(max min);
  3         5  
  3         200  
10              
11 3     3   14 use base 'CVSS::Base';
  3         17  
  3         724  
12 3     3   479 use CVSS::Constants ();
  3         6  
  3         231  
13              
14             our $VERSION = '1.14';
15             $VERSION =~ tr/_//d; ## no critic
16              
17 3     3   29 use constant DEBUG => $ENV{CVSS_DEBUG};
  3         6  
  3         16183  
18              
19 2     2 1 26 sub ATTRIBUTES { CVSS::Constants->CVSS4_ATTRIBUTES }
20 0     0 1 0 sub SCORE_SEVERITY { CVSS::Constants->CVSS4_SCORE_SEVERITY }
21 5145     5145 1 16512 sub NOT_DEFINED_VALUE { CVSS::Constants->CVSS4_NOT_DEFINED_VALUE }
22 245     245 1 3309 sub VECTOR_STRING_REGEX { CVSS::Constants->CVSS4_VECTOR_STRING_REGEX }
23 1470     1470 1 6832 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 735     735 1 5136 sub version {'4.0'}
32              
33             sub macro_vector {
34              
35 244     244 1 438 my ($self) = @_;
36              
37 244         451 my $eq1 = undef;
38 244         333 my $eq2 = undef;
39 244         329 my $eq3 = undef;
40 244         288 my $eq4 = undef;
41 244         277 my $eq5 = undef;
42 244         294 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 244 100 100     425 $eq1 = 0 if ($self->M('AV') eq 'N' && $self->M('PR') eq 'N' && $self->M('UI') eq 'N');
      100        
56              
57 244 100 100     550 $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 244 100 100     509 $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 244         339 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 244 100 100     468 $eq2 = 0 if ($self->M('AC') eq 'L' && $self->M('AT') eq 'N');
74 244 100 100     544 $eq2 = 1 if (!($self->M('AC') eq 'L' && $self->M('AT') eq 'N'));
75              
76 244         391 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 244 100 100     494 $eq3 = 0 if ($self->M('VC') eq 'H' && $self->M('VI') eq 'H');
85              
86 244 100 100     489 $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 244 100 100     509 $eq3 = 2 if (!($self->M('VC') eq 'H' || $self->M('VI') eq 'H' || $self->M('VA') eq 'H'));
      100        
91              
92 244         348 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 244 50 33     418 $eq4 = 0 if ($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S');
102              
103 244 100 33     439 $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 244 100 33     499 $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 244         399 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 244 50       395 $eq5 = 0 if ($self->M('E') eq 'A');
121 244 50       420 $eq5 = 1 if ($self->M('E') eq 'P');
122 244 50       414 $eq5 = 2 if ($self->M('E') eq 'U');
123              
124 244         362 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 244 100 66     444 $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 244 100 66     537 $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 244         432 DEBUG and say STDERR "-- MacroVector - EQ6 : $eq6";
143              
144 244         808 my @macro_vector = ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6);
145 244         1016 my $macro_vector = join '', @macro_vector;
146              
147 244         269 DEBUG and say STDERR "-- MacroVector : $macro_vector";
148              
149 244         1131 my $SEVERITY = {0 => 'HIGH', 1 => 'MEDIUM', 2 => 'LOW'};
150              
151 244         779 $self->{exploitability} = $SEVERITY->{$eq1};
152 244         367 DEBUG and say STDERR "-- MacroVector EQ1 - Exploitability : $self->{exploitability}";
153              
154 244         553 $self->{complexity} = $SEVERITY->{$eq2};
155 244         295 DEBUG and say STDERR "-- MacroVector EQ2 - Complexity : $self->{complexity}";
156              
157 244         585 $self->{vulnerable_system} = $SEVERITY->{$eq3};
158 244         361 DEBUG and say STDERR "-- MacroVector EQ3 - Vulnerable System : $self->{vulnerable_system}";
159              
160 244         787 $self->{subsequent_system} = $SEVERITY->{$eq4};
161 244         278 DEBUG and say STDERR "-- MacroVector EQ4 - Subsequent System : $self->{subsequent_system}";
162              
163 244         482 $self->{exploitation} = $SEVERITY->{$eq5};
164 244         386 DEBUG and say STDERR "-- MacroVector EQ5 - Exploitation : $self->{exploitation}";
165              
166 244         459 $self->{security_requirements} = $SEVERITY->{$eq6};
167 244         326 DEBUG and say STDERR "-- MacroVector EQ6 - Security Requirements : $self->{security_requirements}";
168              
169 244 50       1505 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 18896     18896 1 29215 my ($self, $metric) = @_;
183              
184 18896         35403 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 18896 100 66     39497 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 18164 100 66     35216 return 'H' if ($metric eq 'CR' && $value eq 'X');
196 17056 100 66     31444 return 'H' if ($metric eq 'IR' && $value eq 'X');
197 16186 100 66     29121 return 'H' if ($metric eq 'AR' && $value eq 'X');
198              
199 15372         43057 return $value;
200              
201             }
202              
203             sub calculate_score {
204              
205 245     245 1 477 my ($self) = @_;
206              
207 245 50       2154 if (%{$self->metrics}) {
  245         631  
208 245         399 for (@{$self->METRIC_GROUPS->{base}}) {
  245         760  
209 2695 50       4731 Carp::croak sprintf('Missing base metric (%s)', $_) unless ($self->metrics->{$_});
210             }
211             }
212              
213             # Set NOT_DEFINED
214 245   50     697 $self->metrics->{E} //= 'X';
215              
216 245   50     544 $self->metrics->{CR} //= 'X';
217 245   50     454 $self->metrics->{IR} //= 'X';
218 245   50     551 $self->metrics->{AR} //= 'X';
219 245   50     484 $self->metrics->{MAV} //= 'X';
220 245   50     560 $self->metrics->{MAC} //= 'X';
221 245   50     535 $self->metrics->{MAT} //= 'X';
222 245   50     520 $self->metrics->{MPR} //= 'X';
223 245   50     464 $self->metrics->{MUI} //= 'X';
224 245   50     533 $self->metrics->{MVC} //= 'X';
225 245   50     464 $self->metrics->{MVI} //= 'X';
226 245   50     511 $self->metrics->{MVA} //= 'X';
227 245   50     438 $self->metrics->{MSC} //= 'X';
228 245   50     464 $self->metrics->{MSI} //= 'X';
229 245   50     455 $self->metrics->{MSA} //= 'X';
230              
231 245   50     461 $self->metrics->{S} //= 'X';
232 245   50     439 $self->metrics->{AU} //= 'X';
233 245   50     424 $self->metrics->{R} //= 'X';
234 245   50     429 $self->metrics->{V} //= 'X';
235 245   50     466 $self->metrics->{RE} //= 'X';
236 245   50     467 $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 245         1066 my $AV_levels = {N => 0.0, A => 0.1, L => 0.2, P => 0.3};
243 245         711 my $PR_levels = {N => 0.0, L => 0.1, H => 0.2};
244 245         624 my $UI_levels = {N => 0.0, P => 0.1, A => 0.2};
245              
246 245         617 my $AC_levels = {L => 0.0, H => 0.1};
247 245         582 my $AT_levels = {N => 0.0, P => 0.1};
248              
249 245         578 my $VC_levels = {H => 0.0, L => 0.1, N => 0.2};
250 245         510 my $VI_levels = {H => 0.0, L => 0.1, N => 0.2};
251 245         508 my $VA_levels = {H => 0.0, L => 0.1, N => 0.2};
252              
253 245         528 my $SC_levels = {H => 0.1, L => 0.2, N => 0.3};
254 245         668 my $SI_levels = {S => 0.0, H => 0.1, L => 0.2, N => 0.3};
255 245         590 my $SA_levels = {S => 0.0, H => 0.1, L => 0.2, N => 0.3};
256              
257 245         579 my $CR_levels = {H => 0.0, M => 0.1, L => 0.2};
258 245         596 my $IR_levels = {H => 0.0, M => 0.1, L => 0.2};
259 245         492 my $AR_levels = {H => 0.0, M => 0.1, L => 0.2};
260              
261 245         588 my $E_levels = {U => 0.2, P => 0.1, A => 0.0};
262              
263 245 100 100     683 if ( $self->M('VC') eq 'N'
      100        
      100        
      100        
      66        
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 1         5 $self->{scores}->{base} = '0.0';
271 1         11 return 1;
272             }
273              
274 244         702 my @macro_vector = $self->macro_vector;
275 244         653 my $macro_vector = join '', @macro_vector;
276              
277 244         610 $self->{macro_vector} = $macro_vector;
278              
279 244         642 my ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6) = @macro_vector;
280              
281 244         596 my $value = $CVSS_LOOKUP_GLOBAL->{$macro_vector};
282              
283 244         618 my $eq1_next_lower_macro = join '', ($eq1 + 1, $eq2, $eq3, $eq4, $eq5, $eq6);
284 244         505 my $eq2_next_lower_macro = join '', ($eq1, $eq2 + 1, $eq3, $eq4, $eq5, $eq6);
285 244         369 my $eq3eq6_next_lower_macro = undef;
286 244         313 my $eq3eq6_next_lower_macro_left = undef;
287 244         312 my $eq3eq6_next_lower_macro_right = undef;
288              
289 244 50 66     1476 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 89         209 $eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6 + 1);
297             }
298             elsif ($eq3 == 0 && $eq6 == 0) {
299 85         208 $eq3eq6_next_lower_macro_left = join '', ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6 + 1);
300 85         232 $eq3eq6_next_lower_macro_right = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
301             }
302             else {
303 70         176 $eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6 + 1);
304             }
305              
306 244         560 my $eq4_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4 + 1, $eq5, $eq6);
307 244         451 my $eq5_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4, $eq5 + 1, $eq6);
308              
309 244   100     736 my $score_eq1_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq1_next_lower_macro} || 'NaN';
310 244   100     649 my $score_eq2_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq2_next_lower_macro} || 'NaN';
311 244         317 my $score_eq3eq6_next_lower_macro_left = undef;
312 244         289 my $score_eq3eq6_next_lower_macro_right = undef;
313 244         295 my $score_eq3eq6_next_lower_macro = undef;
314              
315 244 100 66     573 if ($eq3 == 0 && $eq6 == 0) {
316              
317             # multiple path take the one with higher score
318 85   50     201 $score_eq3eq6_next_lower_macro_left = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro_left} || 'NaN';
319 85   50     195 $score_eq3eq6_next_lower_macro_right = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro_right} || 'NaN';
320              
321 85         429 $score_eq3eq6_next_lower_macro = max($score_eq3eq6_next_lower_macro_left, $score_eq3eq6_next_lower_macro_right);
322              
323             }
324             else {
325 159   100     461 $score_eq3eq6_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro} || 'NaN';
326             }
327              
328              
329 244   100     680 my $score_eq4_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq4_next_lower_macro} || 'NaN';
330 244   50     568 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 244         512 my $eq1_maxes = $MAX_COMPOSED->{eq1}->{$eq1};
335 244         418 my $eq2_maxes = $MAX_COMPOSED->{eq2}->{$eq2};
336 244         463 my $eq3_eq6_maxes = $MAX_COMPOSED->{eq3}->{$eq3}->{$eq6};
337 244         438 my $eq4_maxes = $MAX_COMPOSED->{eq4}->{$eq4};
338 244         410 my $eq5_maxes = $MAX_COMPOSED->{eq5}->{$eq5};
339              
340             # compose them
341 244         369 my @max_vectors = ();
342 244         305 for my $eq1_max (@{$eq1_maxes}) {
  244         572  
343 620         727 for my $eq2_max (@{$eq2_maxes}) {
  620         912  
344 858         1036 for my $eq3_eq6_max (@{$eq3_eq6_maxes}) {
  858         1169  
345 1175         2467 for my $eq4_max (@{$eq4_maxes}) {
  1175         1667  
346 1175         1551 for my $eq5_max (@{$eq5_maxes}) {
  1175         1549  
347 1175         4081 push @max_vectors, join '', ($eq1_max, $eq2_max, $eq3_eq6_max, $eq4_max, $eq5_max);
348             }
349             }
350             }
351             }
352             }
353              
354 244         388 DEBUG and say STDERR "-- MaxVectors: @max_vectors";
355              
356 244         326 my $severity_distance_AV = undef;
357 244         298 my $severity_distance_PR = undef;
358 244         304 my $severity_distance_UI = undef;
359              
360 244         296 my $severity_distance_AC = undef;
361 244         264 my $severity_distance_AT = undef;
362              
363 244         300 my $severity_distance_VC = undef;
364 244         327 my $severity_distance_VI = undef;
365 244         278 my $severity_distance_VA = undef;
366              
367 244         301 my $severity_distance_SC = undef;
368 244         319 my $severity_distance_SI = undef;
369 244         307 my $severity_distance_SA = undef;
370              
371 244         348 my $severity_distance_CR = undef;
372 244         329 my $severity_distance_IR = undef;
373 244         315 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 244         338 DISTANCE: foreach my $max_vector (@max_vectors) {
379              
380             $severity_distance_AV
381 620         1386 = $AV_levels->{$self->M("AV")} - $AV_levels->{$self->extract_value_metric("AV", $max_vector)};
382             $severity_distance_PR
383 620         1284 = $PR_levels->{$self->M("PR")} - $PR_levels->{$self->extract_value_metric("PR", $max_vector)};
384             $severity_distance_UI
385 620         1188 = $UI_levels->{$self->M("UI")} - $UI_levels->{$self->extract_value_metric("UI", $max_vector)};
386              
387             $severity_distance_AC
388 620         1295 = $AC_levels->{$self->M("AC")} - $AC_levels->{$self->extract_value_metric("AC", $max_vector)};
389             $severity_distance_AT
390 620         1264 = $AT_levels->{$self->M("AT")} - $AT_levels->{$self->extract_value_metric("AT", $max_vector)};
391              
392             $severity_distance_VC
393 620         1201 = $VC_levels->{$self->M("VC")} - $VC_levels->{$self->extract_value_metric("VC", $max_vector)};
394             $severity_distance_VI
395 620         1235 = $VI_levels->{$self->M("VI")} - $VI_levels->{$self->extract_value_metric("VI", $max_vector)};
396             $severity_distance_VA
397 620         1285 = $VA_levels->{$self->M("VA")} - $VA_levels->{$self->extract_value_metric("VA", $max_vector)};
398              
399             $severity_distance_SC
400 620         1165 = $SC_levels->{$self->M("SC")} - $SC_levels->{$self->extract_value_metric("SC", $max_vector)};
401             $severity_distance_SI
402 620         1304 = $SI_levels->{$self->M("SI")} - $SI_levels->{$self->extract_value_metric("SI", $max_vector)};
403             $severity_distance_SA
404 620         1256 = $SA_levels->{$self->M("SA")} - $SA_levels->{$self->extract_value_metric("SA", $max_vector)};
405              
406             $severity_distance_CR
407 620         1301 = $CR_levels->{$self->M("CR")} - $CR_levels->{$self->extract_value_metric("CR", $max_vector)};
408             $severity_distance_IR
409 620         1260 = $IR_levels->{$self->M("IR")} - $IR_levels->{$self->extract_value_metric("IR", $max_vector)};
410             $severity_distance_AR
411 620         1224 = $AR_levels->{$self->M("AR")} - $AR_levels->{$self->extract_value_metric("AR", $max_vector)};
412              
413              
414 620         2477 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 620         1153 foreach (@check) {
423 4214 100       7886 next DISTANCE if ($_ < 0);
424             }
425              
426             # if multiple maxes exist to reach it it is enough the first one
427 244         593 last;
428             }
429              
430 244         365 my $step = 0.1;
431              
432 244         569 my $current_severity_distance_eq1 = ($severity_distance_AV + $severity_distance_PR + $severity_distance_UI);
433 244         456 my $current_severity_distance_eq2 = ($severity_distance_AC + $severity_distance_AT);
434 244         488 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 244         448 my $current_severity_distance_eq4 = ($severity_distance_SC + $severity_distance_SI + $severity_distance_SA);
442 244         356 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 244         430 my $available_distance_eq1 = $value - $score_eq1_next_lower_macro;
447 244         409 my $available_distance_eq2 = $value - $score_eq2_next_lower_macro;
448 244         379 my $available_distance_eq3eq6 = $value - $score_eq3eq6_next_lower_macro;
449 244         415 my $available_distance_eq4 = $value - $score_eq4_next_lower_macro;
450 244         351 my $available_distance_eq5 = $value - $score_eq5_next_lower_macro;
451              
452 244         321 my $percent_to_next_eq1_severity = 0;
453 244         321 my $percent_to_next_eq2_severity = 0;
454 244         323 my $percent_to_next_eq3eq6_severity = 0;
455 244         320 my $percent_to_next_eq4_severity = 0;
456 244         374 my $percent_to_next_eq5_severity = 0;
457              
458 244         367 my $normalized_severity_eq1 = 0;
459 244         283 my $normalized_severity_eq2 = 0;
460 244         320 my $normalized_severity_eq3eq6 = 0;
461 244         313 my $normalized_severity_eq4 = 0;
462 244         322 my $normalized_severity_eq5 = 0;
463              
464             # multiply by step because distance is pure
465 244         704 my $max_severity_eq1 = $MAX_SEVERITY->{eq1}->{$eq1} * $step;
466 244         454 my $max_severity_eq2 = $MAX_SEVERITY->{eq2}->{$eq2} * $step;
467 244         536 my $max_severity_eq3eq6 = $MAX_SEVERITY->{eq3eq6}->{$eq3}->{$eq6} * $step;
468 244         446 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 244         313 my $n_existing_lower = 0;
478              
479 244 100 66     469 if (!isNaN($available_distance_eq1) && $available_distance_eq1 >= 0) {
480 216         318 $n_existing_lower += 1;
481 216         381 $percent_to_next_eq1_severity = ($current_severity_distance_eq1) / $max_severity_eq1;
482 216         292 $normalized_severity_eq1 = $available_distance_eq1 * $percent_to_next_eq1_severity;
483             }
484              
485 244 100 66     438 if (!isNaN($available_distance_eq2) && $available_distance_eq2 >= 0) {
486 149         210 $n_existing_lower += 1;
487 149         256 $percent_to_next_eq2_severity = ($current_severity_distance_eq2) / $max_severity_eq2;
488 149         206 $normalized_severity_eq2 = $available_distance_eq2 * $percent_to_next_eq2_severity;
489             }
490              
491 244 100 66     396 if (!isNaN($available_distance_eq3eq6) && $available_distance_eq3eq6 >= 0) {
492 174         243 $n_existing_lower += 1;
493 174         270 $percent_to_next_eq3eq6_severity = ($current_severity_distance_eq3eq6) / $max_severity_eq3eq6;
494 174         260 $normalized_severity_eq3eq6 = $available_distance_eq3eq6 * $percent_to_next_eq3eq6_severity;
495             }
496              
497 244 100 66     374 if (!isNaN($available_distance_eq4) && $available_distance_eq4 >= 0) {
498 74         110 $n_existing_lower += 1;
499 74         122 $percent_to_next_eq4_severity = ($current_severity_distance_eq4) / $max_severity_eq4;
500 74         107 $normalized_severity_eq4 = $available_distance_eq4 * $percent_to_next_eq4_severity;
501             }
502              
503 244 50 33     447 if (!isNaN($available_distance_eq5) && $available_distance_eq5 >= 0) {
504 244         400 $n_existing_lower += 1;
505 244         300 $percent_to_next_eq5_severity = 0;
506 244         347 $normalized_severity_eq5 = $available_distance_eq5 * $percent_to_next_eq5_severity;
507             }
508              
509 244         364 my $mean_distance = undef;
510              
511             # 2. The mean of the above computed proportional distances is computed.
512 244 50       469 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 244         545 $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 244         317 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 244         343 $value -= $mean_distance;
534              
535 244         293 DEBUG and say STDERR "-- Value $value";
536              
537 244         683 $value = max(0.0, $value);
538 244         409 $value = min(10.0, $value);
539              
540 244         1829 my $base_score = sprintf('%.1f', $value);
541              
542 244         334 DEBUG and say STDERR "-- BaseScore: $base_score ($value)";
543              
544 244         882 $self->{scores}->{base} = $base_score;
545              
546 244         2949 return 1;
547              
548             }
549              
550             sub extract_value_metric {
551 8680     8680 0 14235 my ($self, $metric, $vector_string) = @_;
552 8680         116393 my %metrics = split /[\/:]/, $vector_string;
553 8680         39430 return $metrics{$metric};
554             }
555              
556 1220     1220 0 3974 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__