File Coverage

blib/lib/CVSS/Base.pm
Criterion Covered Total %
statement 131 166 78.9
branch 34 56 60.7
condition 13 20 65.0
subroutine 30 53 56.6
pod 28 38 73.6
total 236 333 70.8


line stmt bran cond sub pod time code
1             package CVSS::Base;
2              
3 6     6   47 use feature ':5.10';
  6         14  
  6         775  
4 6     6   33 use strict;
  6         8  
  6         134  
5 6     6   23 use utf8;
  6         8  
  6         26  
6 6     6   130 use warnings;
  6         9  
  6         309  
7              
8 6     6   27 use Carp ();
  6         8  
  6         489  
9              
10             our $VERSION = '1.14';
11             $VERSION =~ tr/_//d; ## no critic
12              
13 6     6   2504 use overload '""' => \&to_string, fallback => 1;
  6         5831  
  6         52  
14              
15 6     6   631 use constant DEBUG => $ENV{CVSS_DEBUG};
  6         20  
  6         984  
16              
17             sub import {
18              
19 7     7   2969 my $class = shift;
20              
21 7         31 my $ATTRIBUTES = $class->ATTRIBUTES;
22              
23 7         15 for my $method (keys %{$ATTRIBUTES}) {
  7         57  
24              
25 6     6   54 no strict 'refs';
  6         9  
  6         226  
26 6     6   21 no warnings 'uninitialized';
  6         10  
  6         245  
27 6     6   21 no warnings 'redefine';
  6         8  
  6         15884  
28              
29 158         285 my $metric = $ATTRIBUTES->{$method};
30              
31             # Long method name
32 158         545 *{"${class}::${method}"} = sub {
33 16 100   16   122 @_ > 1 ? $_[0]->_metric_name_to_value($metric, $_[1]) : $_[0]->_metric_value_to_name($metric);
34 158         436 };
35              
36             # Create metric alias
37 158     0   780 *{"${class}::${metric}"} = sub { $_[0]->M($metric) };
  158         6783  
  0         0  
38              
39             }
40              
41             }
42              
43             sub new {
44              
45 2446     2446 0 173002 my ($class, %params) = @_;
46              
47 2446   100     10005 $params{metrics} //= {};
48 2446   50     13989 $params{scores} //= {};
49 2446   100     5826 $params{vector_string} //= undef;
50              
51 2446         17725 my $self = bless {%params}, $class;
52              
53 2446 50       12925 if ($self->version !~ /(2.0|3.[0-1]|4.0)/) {
54 0         0 Carp::croak 'Invalid CVSS version';
55             }
56              
57 2446 100       8619 if ($self->{vector_string}) {
58              
59 2445         3510 DEBUG and say STDERR sprintf('-- Validate vector string: %s', $self->VECTOR_STRING_REGEX);
60              
61 2445 50       9369 if ($self->{vector_string} !~ $self->VECTOR_STRING_REGEX) {
62 0         0 Carp::croak 'Invalid CVSS vector string';
63             }
64              
65 2445         9050 $self->calculate_score;
66              
67             }
68              
69 2446         13410 return $self;
70              
71             }
72              
73             sub from_vector_string {
74              
75 2443     2443 0 2584147 my ($class, $vector_string) = @_;
76              
77 2443         37126 my %metrics = split /[\/:]/, $vector_string;
78 2443   100     14660 my $version = delete $metrics{CVSS} || '2.0';
79              
80 2443         4406 DEBUG and say STDERR "-- Vector String: $vector_string";
81 2443         8256 return $class->new(version => $version, metrics => \%metrics, vector_string => $vector_string);
82              
83             }
84              
85 0     0 1 0 sub ATTRIBUTES { {} }
86 0     0 1 0 sub SCORE_SEVERITY { {} }
87       0 1   sub NOT_DEFINED_VALUE { }
88 0     0 1 0 sub VECTOR_STRING_REGEX {qw{}}
89 0     0 1 0 sub METRIC_GROUPS { {} }
90 0     0 1 0 sub METRIC_NAMES { {} }
91 0     0 1 0 sub METRIC_VALUES { {} }
92              
93              
94             sub _metric_name_to_value {
95 8     8   48 my ($self, $metric, $name) = @_;
96 8         24 $name =~ s/\s/_/g;
97 8         26 $self->metrics->{$metric} = $self->METRIC_NAMES->{$metric}->{names}->{$name};
98 8         291 return $self;
99             }
100              
101             sub _metric_value_to_name {
102 8     8   20 my ($self, $metric) = @_;
103 8         32 $self->METRIC_NAMES->{$metric}->{values}->{$self->metrics->{$metric}};
104             }
105              
106 5501 50   5501 1 44323 sub version { shift->{version} || Carp::croak 'Missing CVSS version' }
107 1 50   1 1 5 sub vector_string { $_[0]->{vector_string} || $_[0]->to_vector_string }
108 153080     153080 1 456685 sub metrics { shift->{metrics} }
109 0     0 1 0 sub scores { shift->{scores} }
110              
111              
112             # Scores & severities
113 2450     2450 1 23632 sub base_score { shift->{scores}->{base} }
114 2     2 1 5 sub base_severity { $_[0]->score_to_severity($_[0]->base_score) }
115              
116             # CVSS 2.0/3.x scores & severities
117 2     2 1 4 sub temporal_score { shift->{scores}->{temporal} }
118 1     1 1 2 sub temporal_severity { $_[0]->score_to_severity($_[0]->temporal_score) }
119 2     2 1 5 sub environmental_score { shift->{scores}->{environmental} }
120 1     1 1 3 sub environmental_severity { $_[0]->score_to_severity($_[0]->environmental_score) }
121              
122             # Extra 2.0/3.x scores
123 0     0 1 0 sub exploitability_score { shift->{scores}->{exploitability} }
124 0     0 1 0 sub impact_score { shift->{scores}->{impact} }
125 0     0 1 0 sub modified_impact_score { shift->{scores}->{modified_impact} }
126              
127              
128             # JSON-style alias
129 0     0 0 0 sub vectorString { shift->vector_string }
130 0     0 0 0 sub baseScore { shift->base_score }
131 0     0 0 0 sub baseSeverity { shift->base_severity }
132 0     0 0 0 sub temporalScore { shift->temporal_score }
133 0     0 0 0 sub temporalSeverity { shift->temporal_severity }
134 0     0 0 0 sub environmentalScore { shift->environmental_score }
135 0     0 0 0 sub environmentalSeverity { shift->environmental_severity }
136              
137              
138             sub metric_group_is_set {
139              
140 4038     4038 1 7136 my ($self, $type) = @_;
141              
142 4038         6325 for (@{$self->METRIC_GROUPS->{$type}}) {
  4038         9218  
143 26794 50 33     41939 return 1 if ($self->M($_) && $self->M($_) ne $self->NOT_DEFINED_VALUE);
144             }
145              
146             }
147              
148             sub metric {
149 0     0 1 0 my ($self, $metric) = @_;
150 0         0 my $value = $self->M($metric);
151              
152 0         0 return $self->METRIC_NAMES->{$metric}->{values}->{$value};
153             }
154              
155 95222     95222 1 158902 sub M { $_[0]->metrics->{$_[1]} }
156              
157             sub score_to_severity {
158              
159 4     4 1 6 my ($self, $score) = @_;
160              
161 4 100       15 return unless (!!$score);
162              
163 2         6 my $SCORE_SEVERITY = $self->SCORE_SEVERITY;
164              
165 2         3 foreach (keys %{$SCORE_SEVERITY}) {
  2         9  
166 4         5 my $range = $SCORE_SEVERITY->{$_};
167 4 100 66     18 if ($score >= $range->{min} && $score <= $range->{max}) {
168 2         8 return $_;
169             }
170             }
171              
172 0         0 Carp::croak 'Unknown severity';
173              
174             }
175              
176 0     0 1 0 sub calculate_score { Carp::croak sprintf('%s->calculate_score() is not implemented in subclass', ref(shift)) }
177              
178 0     0 1 0 sub to_xml { Carp::croak sprintf('%s->to_xml() is not implemented in subclass', ref(shift)) }
179              
180 0     0 0 0 sub to_string { shift->to_vector_string }
181              
182             sub to_vector_string {
183              
184 2444     2444 1 5224 my ($self) = @_;
185              
186 2444         6779 my $metrics = $self->metrics;
187 2444         4211 my @vectors = ();
188              
189 2444 100       6055 if ($self->version > 2.0) {
190 2076         4738 push @vectors, sprintf('CVSS:%s', $self->version);
191             }
192              
193 2444         4690 foreach my $metric (@{$self->METRIC_GROUPS->{base}}) {
  2444         7899  
194 19551 50       42047 return if (!$metrics->{$metric});
195 19551         48013 push @vectors, sprintf('%s:%s', $metric, $metrics->{$metric});
196             }
197              
198 2444         4120 my @other_metrics = ();
199              
200 2444 100       4002 push @other_metrics, @{$self->METRIC_GROUPS->{threat} || []}; # CVSS 4.0
  2444         5527  
201 2444 100       4191 push @other_metrics, @{$self->METRIC_GROUPS->{temporal} || []}; # CVSS 2.0-3.x
  2444         4843  
202 2444 50       4239 push @other_metrics, @{$self->METRIC_GROUPS->{environmental} || []}; # CVSS 2.0-3.x-4.0
  2444         4798  
203 2444 100       3523 push @other_metrics, @{$self->METRIC_GROUPS->{supplemental} || []}; # CVSS 4.0
  2444         4698  
204              
205 2444         4729 foreach my $metric (@other_metrics) {
206 33723 50 66     94057 if (defined $metrics->{$metric} && $metrics->{$metric} ne $self->NOT_DEFINED_VALUE) {
207 0         0 push @vectors, sprintf('%s:%s', $metric, $metrics->{$metric});
208             }
209             }
210              
211 2444         21353 return join '/', @vectors;
212              
213             }
214              
215             sub TO_JSON {
216              
217 1     1 1 228 my ($self) = @_;
218              
219             # Required JSON fields:
220             # CVSS == v2.0: version, vectorString and baseScore
221             # CVSS >= v3.0: version, vectorString, baseScore and baseSeverity
222              
223 1 50       5 $self->calculate_score unless ($self->base_score);
224              
225 1         3 my $json = {
226             version => sprintf('%.1f', $self->version),
227             vectorString => $self->vector_string,
228             baseScore => $self->base_score
229             };
230              
231 1 50       3 if ($self->version > 2.0) {
232 1         17 $json->{baseSeverity} = $self->base_severity;
233             }
234              
235 1         3 my $metrics = $self->metrics;
236 1         2 my %attributes = reverse(%{$self->ATTRIBUTES});
  1         3  
237              
238 1         1 foreach my $metric (@{$self->METRIC_GROUPS->{base}}) {
  1         3  
239 8         31 $json->{$attributes{$metric}} = $self->METRIC_NAMES->{$metric}->{values}->{$metrics->{$metric}};
240             }
241              
242 1         5 my @other_metrics = ();
243              
244 1 50       3 push @other_metrics, @{$self->METRIC_GROUPS->{threat} || []}; # CVSS 4.0
  1         4  
245 1 50       3 push @other_metrics, @{$self->METRIC_GROUPS->{temporal} || []}; # CVSS 2.0-3.x
  1         3  
246 1 50       2 push @other_metrics, @{$self->METRIC_GROUPS->{environmental} || []}; # CVSS 2.0-3.x-4.0
  1         4  
247 1 50       3 push @other_metrics, @{$self->METRIC_GROUPS->{supplemental} || []}; # CVSS 4.0
  1         3  
248              
249 1         3 foreach my $metric (@other_metrics) {
250 14 50 33     48 if ($metrics->{$metric} && $metrics->{$metric} ne $self->NOT_DEFINED_VALUE) {
251 0         0 $json->{$attributes{$metric}} = $self->METRIC_NAMES->{$metric}->{values}->{$metrics->{$metric}};
252             }
253             }
254              
255 1 50       6 if ($self->version <= 3.1) {
256              
257 1 50       4 if ($self->metric_group_is_set('temporal')) {
258              
259 0         0 $json->{temporalScore} = $self->temporal_score;
260              
261 0 0       0 if ($self->version != 2.0) {
262 0         0 $json->{temporalSeverity} = $self->temporal_severity;
263             }
264              
265             }
266              
267 1 50       4 if ($self->metric_group_is_set('environmental')) {
268              
269 0         0 $json->{environmentalScore} = $self->environmental_score;
270              
271 0 0       0 if ($self->version != 2.0) {
272 0         0 $json->{environmentalSeverity} = $self->environmental_severity;
273             }
274              
275             }
276              
277             }
278              
279             # CVSS 4.0 ???
280              
281             # environmentalScore
282             # environmentalSeverity
283             # threatScore
284             # threatSeverity
285              
286 1         12 return $json;
287              
288             }
289              
290             1;
291             __END__