File Coverage

blib/lib/CVSS/v3.pm
Criterion Covered Total %
statement 114 153 74.5
branch 19 46 41.3
condition 14 68 20.5
subroutine 22 23 95.6
pod 9 13 69.2
total 178 303 58.7


line stmt bran cond sub pod time code
1             package CVSS::v3;
2              
3 4     4   196906 use feature ':5.10';
  4         7  
  4         571  
4 4     4   21 use strict;
  4         7  
  4         89  
5 4     4   797 use utf8;
  4         432  
  4         20  
6 4     4   89 use warnings;
  4         7  
  4         189  
7              
8 4     4   23 use List::Util qw(min);
  4         4  
  4         277  
9 4     4   1998 use POSIX ();
  4         30173  
  4         119  
10 4     4   25 use Carp ();
  4         5  
  4         89  
11              
12 4     4   19 use base 'CVSS::Base';
  4         9  
  4         1083  
13 4     4   940 use CVSS::Constants ();
  4         9  
  4         345  
14              
15             our $VERSION = '1.14';
16             $VERSION =~ tr/_//d; ## no critic
17              
18 4     4   23 use constant DEBUG => $ENV{CVSS_DEBUG};
  4         5  
  4         10837  
19              
20             my $WEIGHTS = CVSS::Constants->CVSS3_WEIGHTS;
21              
22 4     4 1 37 sub ATTRIBUTES { CVSS::Constants->CVSS3_ATTRIBUTES }
23 2     2 1 28 sub SCORE_SEVERITY { CVSS::Constants->CVSS3_SCORE_SEVERITY }
24 51338     51338 1 196652 sub NOT_DEFINED_VALUE { CVSS::Constants->CVSS3_NOT_DEFINED_VALUE }
25 1832     1832 1 30077 sub VECTOR_STRING_REGEX { CVSS::Constants->CVSS3_VECTOR_STRING_REGEX }
26 14663     14663 1 64934 sub METRIC_GROUPS { CVSS::Constants->CVSS3_METRIC_GROUPS }
27 25     25 1 103 sub METRIC_NAMES { CVSS::Constants->CVSS3_METRIC_NAMES }
28 0     0 1 0 sub METRIC_VALUES { CVSS::Constants->CVSS3_METRIC_VALUES }
29              
30             sub weight {
31              
32 14664     14664 0 24152 my ($self, $metric) = @_;
33              
34             # Modified Base Score weight
35 14664 50       28363 if ($metric =~ /M(AV|AC|PR|UI|S|C|I|A)/) {
36              
37 0 0       0 if ($metric eq 'MPR') {
38              
39 0         0 DEBUG and say STDERR '-- MPR depends on the value of Scope (MS)';
40              
41 0         0 my $ms_value = $self->M('MS');
42 0         0 my $mpr_value = $self->M('MPR');
43              
44 0 0       0 $ms_value = $self->M('S') if ($ms_value eq 'X');
45 0 0       0 $mpr_value = $self->M('PR') if ($mpr_value eq 'X');
46              
47 0         0 my $weight = $WEIGHTS->{MPR}{$ms_value}{$mpr_value};
48              
49 0         0 DEBUG and say STDERR "-- Weight : $metric:$mpr_value = $weight (MS:$ms_value)";
50              
51 0         0 return $weight;
52              
53             }
54              
55 0         0 my $value = $self->M($metric);
56 0 0       0 $value = $self->M($1) if ($value eq 'X');
57              
58 0         0 my $weight = $WEIGHTS->{$metric}{$value};
59              
60 0         0 DEBUG and say STDERR "-- Weight : $metric:$value = $weight";
61              
62 0         0 return $weight;
63              
64             }
65              
66              
67             # PR depends on the value of Scope (S).
68 14664 100       28601 if ($metric eq 'PR') {
69              
70 1833         3579 DEBUG and say STDERR '-- PR depends on the value of Scope (S)';
71              
72 1833         3847 my $s_value = $self->M('S');
73 1833         3656 my $pr_value = $self->M('PR');
74 1833         4098 my $weight = $WEIGHTS->{PR}{$s_value}{$pr_value};
75              
76 1833         2079 DEBUG and say STDERR "-- Weight : $metric:$pr_value = $weight (S:$s_value)";
77              
78 1833         5737 return $weight;
79              
80             }
81              
82 12831         30292 my $value = $self->M($metric);
83 12831         23545 my $weight = $WEIGHTS->{$metric}{$value};
84              
85 12831         14535 DEBUG and say STDERR "-- Weight : $metric:$value = $weight";
86              
87 12831         34591 return $weight;
88              
89             }
90              
91 14664     14664 0 23658 sub W { weight(@_) }
92              
93             sub calculate_score {
94              
95 1833     1833 1 3737 my ($self) = @_;
96              
97 1833 50       2774 if (%{$self->metrics}) {
  1833         5745  
98 1833         3536 for (@{$self->METRIC_GROUPS->{base}}) {
  1833         4666  
99 14664 50       33932 Carp::croak sprintf('Missing base metric (%s)', $_) unless ($self->metrics->{$_});
100             }
101             }
102              
103             # Set NOT_DEFINED
104 1833   50     4815 $self->metrics->{E} //= 'X';
105 1833   50     3932 $self->metrics->{RL} //= 'X';
106 1833   50     4338 $self->metrics->{RC} //= 'X';
107              
108 1833   50     3834 $self->metrics->{CR} //= 'X';
109 1833   50     3646 $self->metrics->{IR} //= 'X';
110 1833   50     3649 $self->metrics->{AR} //= 'X';
111 1833   50     3860 $self->metrics->{MAV} //= 'X';
112 1833   50     3489 $self->metrics->{MAC} //= 'X';
113 1833   50     3296 $self->metrics->{MPR} //= 'X';
114 1833   50     3763 $self->metrics->{MUI} //= 'X';
115 1833   50     3212 $self->metrics->{MS} //= 'X';
116 1833   50     3667 $self->metrics->{MC} //= 'X';
117 1833   50     3551 $self->metrics->{MI} //= 'X';
118 1833   50     3817 $self->metrics->{MA} //= 'X';
119              
120              
121             # Base Metrics Equations
122              
123             # The Base Score formula depends on sub-formulas for Impact Sub-Score (ISS),
124             # Impact, and Exploitability, all of which are defined below:
125              
126             # ISS = 1 - [ (1 - Confidentiality) × (1 - Integrity) × (1 - Availability) ]
127              
128             # Impact =
129             # If Scope is Unchanged 6.42 × ISS
130             # If Scope is Changed 7.52 × (ISS - 0.029) - 3.25 × (ISS - 0.02) ** 15
131              
132             # Exploitability = 8.22 × AttackVector × AttackComplexity ×
133             # PrivilegesRequired × UserInteraction
134              
135             # BaseScore =
136             # If Impact \<= 0 0, else
137             # If Scope is Unchanged Roundup (Minimum [(Impact + Exploitability), 10])
138             # If Scope is Changed Roundup (Minimum [1.08 × (Impact + Exploitability), 10])
139              
140 1833         6641 my $iss = (1 - ((1 - $self->W('C')) * (1 - $self->W('I')) * (1 - $self->W('A'))));
141 1833         3260 my $impact = 0;
142 1833         3151 my $exploitability = 8.22 * $self->W('AV') * $self->W('AC') * $self->W('PR') * $self->W('UI');
143 1833         2906 my $base_score = 0;
144              
145 1833 100       4002 if ($self->M('S') eq 'U') {
146 1140         2329 $impact = $self->W('S') * $iss;
147             }
148             else {
149 693         2012 $impact = $self->W('S') * ($iss - 0.029) - 3.25 * ($iss - 0.02)**15;
150             }
151              
152 1833 100       10228 if ($impact <= 0) {
    100          
153 9         20 $base_score = 0;
154             }
155             elsif ($self->M('S') eq 'U') {
156 1131         5012 $base_score = round_up(min(($impact + $exploitability), 10));
157             }
158             else {
159 693         3421 $base_score = round_up(min((1.08 * ($impact + $exploitability)), 10));
160             }
161              
162 1833         4070 DEBUG and say STDERR "-- Impact Sub-Score (ISS): $iss";
163 1833         2499 DEBUG and say STDERR "-- Impact: $impact";
164 1833         2257 DEBUG and say STDERR "-- Exploitability: $exploitability";
165 1833         2200 DEBUG and say STDERR "-- BaseScore: $base_score";
166              
167 1833         4967 $self->{scores}->{base} = $base_score;
168 1833         14627 $self->{scores}->{exploitability} = sprintf('%.1f', $exploitability);
169 1833         5929 $self->{scores}->{impact} = sprintf('%.1f', $impact);
170              
171 1833 50       4805 if ($self->metric_group_is_set('temporal')) {
172              
173             # Temporal Metrics Equations
174              
175             # TemporalScore = Roundup (BaseScore × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
176              
177 0         0 my $temporal_score = round_up($base_score * $self->W('E') * $self->W('RL') * $self->W('RC'));
178              
179 0         0 DEBUG and say STDERR "-- TemporalScore: $temporal_score";
180              
181 0         0 $self->{scores}->{temporal} = $temporal_score;
182              
183             }
184              
185              
186 1833 50       4482 if ($self->metric_group_is_set('environmental')) {
187              
188             # Environmental Metrics Equations
189              
190             # The Environmental Score formula depends on sub-formulas for Modified Impact
191             # Sub-Score (MISS), ModifiedImpact, and ModifiedExploitability, all of which
192             # are defined below:
193              
194             # MISS = Minimum ( 1 - [
195             # (1 - ConfidentialityRequirement × ModifiedConfidentiality) ×
196             # (1 - IntegrityRequirement × ModifiedIntegrity) ×
197             # (1 - AvailabilityRequirement × ModifiedAvailability)
198             # ], 0.915)
199              
200             # ModifiedImpact =
201             # If ModifiedScope is Unchanged 6.42 × MISS
202             # If ModifiedScope is Changed 7.52 × (MISS - 0.029) - 3.25 × (MISS × 0.9731 - 0.02) ** 13
203             # CVSS v3.0 --> 7.52 × (MISS - 0.029) - 3.25 × (MISS - 0.02) ** 15
204              
205             # ModifiedExploitability = 8.22 × ModifiedAttackVector ×
206             # ModifiedAttackComplexity ×
207             # ModifiedPrivilegesRequired ×
208             # ModifiedUserInteraction
209              
210             # EnvironmentalScore =
211             # If ModifiedImpact \<= 0 0, else
212             # If ModifiedScope is Unchanged Roundup ( Roundup [Minimum (
213             # [ModifiedImpact + ModifiedExploitability], 10) ] ×
214             # ExploitCodeMaturity × RemediationLevel × ReportConfidence)
215              
216             # If ModifiedScope is Changed Roundup ( Roundup [Minimum (1.08 ×
217             # [ModifiedImpact + ModifiedExploitability], 10) ] ×
218             # ExploitCodeMaturity × RemediationLevel × ReportConfidence)
219              
220 0         0 my $modified_impact = 0;
221 0         0 my $environmental_score = 0;
222 0         0 my $modified_exploitability = 8.22 * $self->W('MAV') * $self->W('MAC') * $self->W('MPR') * $self->W('MUI');
223              
224 0         0 my $miss = min(
225             (
226             1 - (
227             (1 - $self->W('MC') * $self->W('CR'))
228             * (1 - $self->W('MI') * $self->W('IR'))
229             * (1 - $self->W('MA') * $self->W('AR'))
230             )
231             ),
232             0.915
233             );
234              
235 0         0 DEBUG and say STDERR "-- Modified Impact Sub-Score (MISS): $miss";
236              
237 0 0 0     0 if ($self->M('MS') eq 'U' || ($self->M('MS') eq 'X' && $self->M('S') eq 'U')) {
      0        
238 0         0 $modified_impact = $self->W('MS') * $miss;
239             }
240             else {
241 0 0       0 if ($self->version == 3.0) {
    0          
242 0         0 $modified_impact = $self->W('MS') * ($miss - 0.029) - 3.25 * (($miss - 0.02)**15);
243             }
244             elsif ($self->version == 3.1) {
245 0         0 $modified_impact = $self->W('MS') * ($miss - 0.029) - 3.25 * (($miss * 0.9731 - 0.02)**13);
246             }
247             }
248              
249              
250 0 0 0     0 if ($modified_impact <= 0) {
    0 0        
251 0         0 $environmental_score = 0;
252             }
253             elsif ($self->M('MS') eq 'U' || ($self->M('MS') eq 'X' && $self->M('S') eq 'U')) {
254 0         0 $environmental_score
255             = round_up(round_up(min(($modified_impact + $modified_exploitability), 10))
256             * $self->W('E')
257             * $self->W('RL')
258             * $self->W('RC'));
259             }
260             else {
261 0         0 $environmental_score
262             = round_up(round_up(min(1.08 * ($modified_impact + $modified_exploitability), 10))
263             * $self->W('E')
264             * $self->W('RL')
265             * $self->W('RC'));
266             }
267              
268 0         0 DEBUG and say STDERR "-- ModifiedImpact: $modified_impact";
269 0         0 DEBUG and say STDERR "-- ModifiedExploitability: $modified_exploitability";
270 0         0 DEBUG and say STDERR "-- EnvironmentalScore: $environmental_score";
271              
272 0         0 $self->{scores}->{modified_impact} = round_up($modified_impact);
273 0         0 $self->{scores}->{environmental} = $environmental_score;
274              
275              
276             }
277              
278 1833         4487 return 1;
279              
280             }
281              
282             sub round {
283 1824     1824 0 3432 my ($input) = @_;
284 1824 50       7453 return ($input < 0) ? POSIX::ceil($input - 0.5) : POSIX::floor($input + 0.5);
285             }
286              
287             sub round_up {
288              
289 1824     1824 0 3388 my ($input) = @_;
290              
291 1824         4198 my $int_input = round($input * 100_000);
292              
293 1824 100       5014 if ($int_input % 10_000 == 0) {
294 15         45 return $int_input / 100_000;
295             }
296             else {
297 1809         7007 return (POSIX::floor($int_input / 10_000) + 1) / 10;
298             }
299              
300             }
301              
302             sub to_xml {
303              
304 1     1 1 2 my ($self) = @_;
305              
306 1         3 my $metric_value_names = $self->METRIC_NAMES;
307              
308 1 50       4 $self->calculate_score unless ($self->base_score);
309              
310 1         3 my $version = $self->version;
311 1         2 my $metrics = $self->metrics;
312 1         2 my $base_score = $self->base_score;
313 1         3 my $base_severity = $self->base_severity;
314 1         8 my $temporal_score = $self->temporal_score;
315 1         4 my $temporal_severity = $self->temporal_severity;
316 1         20 my $environmental_score = $self->environmental_score;
317 1         4 my $environmental_severity = $self->environmental_severity;
318              
319 1         16 my $xml_metrics = <<"XML";
320            
321             $metric_value_names->{AV}->{values}->{$metrics->{AV}}
322             $metric_value_names->{AC}->{values}->{$metrics->{AC}}
323             $metric_value_names->{PR}->{values}->{$metrics->{PR}}
324             $metric_value_names->{UI}->{values}->{$metrics->{UI}}
325             $metric_value_names->{S}->{values}->{$metrics->{S}}
326             $metric_value_names->{C}->{values}->{$metrics->{C}}
327             $metric_value_names->{I}->{values}->{$metrics->{I}}
328             $metric_value_names->{A}->{values}->{$metrics->{A}}
329             $base_score
330             $base_severity
331            
332             XML
333              
334 1 50       3 if ($self->metric_group_is_set('temporal')) {
335 0   0     0 $xml_metrics .= <<"XML";
      0        
      0        
336            
337             $metric_value_names->{E}->{values}->{$metrics->{E} || 'X'}
338             $metric_value_names->{RL}->{values}->{$metrics->{RL} || 'X'}
339             $metric_value_names->{RC}->{values}->{$metrics->{RC} || 'X'}
340             $temporal_score
341             $temporal_severity
342            
343             XML
344             }
345              
346 1 50       4 if ($self->metric_group_is_set('environmental')) {
347 0   0     0 $xml_metrics .= <<"XML";
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
348            
349             $metric_value_names->{CR}->{values}->{$metrics->{CR} || 'X'}
350             $metric_value_names->{IR}->{values}->{$metrics->{IR} || 'X'}
351             $metric_value_names->{AR}->{values}->{$metrics->{AR} || 'X'}
352             $metric_value_names->{MAV}->{values}->{$metrics->{MAV} || 'X'}
353             $metric_value_names->{MAC}->{values}->{$metrics->{MAC} || 'X'}
354             $metric_value_names->{MPR}->{values}->{$metrics->{MPR} || 'X'}
355             $metric_value_names->{MUI}->{values}->{$metrics->{MUI} || 'X'}
356             $metric_value_names->{MS}->{$metrics->{values}->{MS} || 'X'}
357             $metric_value_names->{MC}->{values}->{$metrics->{MC} || 'X'}
358             $metric_value_names->{MI}->{values}->{$metrics->{MI} || 'X'}
359             $metric_value_names->{MA}->{values}->{$metrics->{MA} || 'X'}
360             $environmental_score
361             $environmental_severity
362            
363             XML
364             }
365              
366 1         4 my $xml = <<"XML";
367            
368            
369             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
370             xsi:schemaLocation="https://www.first.org/cvss/cvss-v$version.xsd https://www.first.org/cvss/cvss-v$version.xsd"
371             >
372              
373             $xml_metrics
374            
375             XML
376              
377 1         30 return $xml;
378              
379             }
380              
381             1;
382              
383             1;
384             __END__