File Coverage

blib/lib/Security/CVSS.pm
Criterion Covered Total %
statement 103 116 88.7
branch 24 32 75.0
condition n/a
subroutine 16 17 94.1
pod 0 6 0.0
total 143 171 83.6


line stmt bran cond sub pod time code
1             package Security::CVSS;
2              
3 1     1   24386 use 5.008;
  1         5  
  1         46  
4 1     1   6 use strict;
  1         3  
  1         40  
5 1     1   7 use warnings;
  1         7  
  1         37  
6              
7 1     1   999 use Module::Check_Args;
  1         2203  
  1         7  
8 1     1   110 use Carp qw( croak );
  1         2  
  1         447  
9              
10             our $VERSION = '0.3';
11              
12             our %BASE_PARAMS =
13             (
14             AccessVector => {Params => {'remote' => 1, 'local' => 0.7},
15             P2V => {'remote' => 'R', 'local' => 'L'}},
16              
17             AccessComplexity => {Params => {'low' => 1, 'high' => 0.8},
18             P2V => {'low' => 'L', 'high' => 'H'}},
19              
20             Authentication => {Params => {'required' => 0.6, 'not-required' => 1},
21             P2V => {'required' => 'R', 'not-required' => 'NR'}},
22              
23             ConfidentialityImpact => {Params => {'none' => 0, 'partial' => 0.7, 'complete' => 1},
24             P2V => {'none' => 'N', 'partial' => 'P', 'complete' => 'C'}},
25              
26             IntegrityImpact => {Params => {'none' => 0, 'partial' => 0.7, 'complete' => 1},
27             P2V => {'none' => 'N', 'partial' => 'P', 'complete' => 'C'}},
28              
29             AvailabilityImpact => {Params => {'none' => 0, 'partial' => 0.7, 'complete' => 1},
30             P2V => {'none' => 'N', 'partial' => 'P', 'complete' => 'C'}},
31              
32             ImpactBias => {Params => {'normal' => 1, 'confidentiality' => 1, 'integrity' => 1, 'availability' => 1},
33             P2V => {'normal' => 'N', 'confidentiality' => 'C', 'integrity' => 'I', 'availability' => 'A'}}
34             );
35              
36             _CreateV2P(\%BASE_PARAMS);
37              
38             our %TEMPORAL_PARAMS =
39             (
40             Exploitability => {Params => {'unproven' => 0.85, 'proof-of-concept' => 0.9, 'functional' => 0.95, 'high' => 1},
41             P2V => {'unproven' => 'U', 'proof-of-concept' => 'P', 'functional' => 'F', 'high' => 'H'}},
42              
43             RemediationLevel => {Params => {'official-fix' => 0.87, 'temporary-fix' => 0.9, 'workaround' => 0.95, 'unavailable' => 1},
44             P2V => {'official-fix' => 'O', 'temporary-fix' => 'T', 'workaround' => 'W', 'unavailable' => 'U'}},
45              
46             ReportConfidence => {Params => {'unconfirmed' => 0.9, 'uncorroborated' => 0.95, 'confirmed' => 1},
47             P2V => {'unconfirmed' => 'U', 'uncorroborated' => 'Uc', 'confirmed' => 'C'}}
48             );
49              
50             _CreateV2P(\%TEMPORAL_PARAMS);
51              
52             our %ENVIRONMENTAL_PARAMS =
53             (
54             CollateralDamagePotential => {Params => {'none' => 0, 'low' => 0.1, 'medium' => 0.3, 'high' => 0.5}},
55             TargetDistribution => {Params => {'none' => 0, 'low' => 0.25, 'medium' => 0.75, 'high' => 1}}
56             );
57              
58             our %ALL_PARAMS = (%BASE_PARAMS, %TEMPORAL_PARAMS, %ENVIRONMENTAL_PARAMS);
59              
60             # Create accessors for all parameters
61             foreach my $Accessor (keys %ALL_PARAMS)
62             {
63 1     1   7 no strict 'refs';
  1         3  
  1         1622  
64             *{"Security::CVSS::$Accessor"} = sub
65             {
66 66     66   182 exact_argcount(2);
67 66         968 my $self = shift;
68 66         188 $self->_ValidateParam($Accessor, @_);
69             };
70             }
71              
72             sub new
73             {
74 4     4 0 30 range_argcount(1, 2);
75 4         93 my $class = shift;
76 4         8 my $Params = shift;
77              
78 4         14 my $self = bless({}, $class);
79              
80 4 100       11 if (defined($Params))
81 2         8 { $self->UpdateFromHash($Params); }
82              
83 4         29 return $self;
84             }
85              
86             # Create the Vector-to-Param hash from the P2V hash
87             sub _CreateV2P
88             {
89 2     2   9 exact_argcount(1);
90 2         46 my $Params = shift;
91              
92 2         8 foreach my $Param (keys %$Params)
93             {
94 10         12 $Params->{$Param}->{V2P} = { map { $Params->{$Param}->{P2V}->{$_} => $_ } keys %{$Params->{$Param}->{P2V}} };
  30         100  
  10         29  
95             }
96             }
97              
98             sub _ValidateParam
99             {
100 66     66   153 exact_argcount(3);
101 66         970 my $self = shift;
102 66         103 my $Param = shift;
103 66         95 my $Value = shift;
104              
105             # If vector value - convert to full value
106 66 100       198 if (exists($ALL_PARAMS{$Param}->{V2P}->{$Value}))
107 35         71 { $Value = $ALL_PARAMS{$Param}->{V2P}->{$Value}; }
108             else
109 31         64 { $Value = lc($Value); }
110              
111 66 50       82 if (!grep(/^$Value$/i, keys %{$ALL_PARAMS{$Param}->{Params}}))
  66         1297  
112 0         0 { croak("Invalid value '$Value' for $Param"); }
113              
114 66         273 $self->{$Param} = $Value;
115             }
116              
117             sub _ConvertToVectorValue
118             {
119 0     0   0 my $Value = shift;
120 0         0 my @Words = split('-', $Value);
121              
122 0         0 my $VectorValue;
123 0         0 foreach my $Word (@Words)
124 0         0 { $VectorValue .= uc(substr($Word, 0, 1)); }
125             }
126              
127             # Sets up the object from a vector in the format at:
128             # http://nvd.nist.gov/cvss.cfm?vectorinfo
129             sub Vector
130             {
131 8     8 0 31 range_argcount(1, 2);
132 8         132 my ($self, $Vector) = @_;
133              
134 8 100       20 if (defined($Vector))
135             {
136 4 50       47 if ($Vector !~ m#^\(AV:([RL])/AC:([HL])/Au:(R|NR)/C:([NPC])/I:([NPC])/A:([NPC])/B:([NCIA])(/E:([UPFH])/RL:([OTWU])/RC:(U|Uc|C))?\)#)
137 0         0 { croak('Invalid CVSS vector'); }
138              
139 4         39 my %Values =
140             (
141             AccessVector => $1,
142             AccessComplexity => $2,
143             Authentication => $3,
144             ConfidentialityImpact => $4,
145             IntegrityImpact => $5,
146             AvailabilityImpact => $6,
147             ImpactBias => $7
148             );
149              
150 4 100       13 if (defined($8))
151             {
152             # Has temporal portion
153 2         30 %Values =
154             (
155             %Values,
156             Exploitability => $9,
157             RemediationLevel => $10,
158             ReportConfidence => $11
159             );
160             }
161              
162 4         15 $self->UpdateFromHash(\%Values);
163             }
164             else
165             {
166             # Check all parameters exist
167 4         13 foreach my $Param (keys %BASE_PARAMS)
168             {
169 28 50       65 if (!defined($self->{$Param}))
170 0         0 { croak("You must set '$Param' to output the CVSS vector"); }
171             }
172              
173             my $VectorValue = sub
174             {
175 34     34   130 return $ALL_PARAMS{$_[0]}->{P2V}->{$self->{$_[0]}};
176 4         18 };
177              
178 4         10 my $Vector = sprintf('AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s/B:%s',
179             &$VectorValue('AccessVector'),
180             &$VectorValue('AccessComplexity'),
181             &$VectorValue('Authentication'),
182             &$VectorValue('ConfidentialityImpact'),
183             &$VectorValue('IntegrityImpact'),
184             &$VectorValue('AvailabilityImpact'),
185             &$VectorValue('ImpactBias'));
186              
187 4         7 my $Environmental = 1;
188 4         10 foreach my $Param (keys %TEMPORAL_PARAMS)
189             {
190 8 100       22 if (!defined($self->{$Param}))
191             {
192 2         3 $Environmental = 0;
193 2         4 last;
194             }
195             }
196              
197 4 100       13 if ($Environmental)
198             {
199 2         5 $Vector .= sprintf('/E:%s/RL:%s/RC:%s',
200             &$VectorValue('Exploitability'),
201             &$VectorValue('RemediationLevel'),
202             &$VectorValue('ReportConfidence'));
203             }
204              
205 4         34 return "($Vector)";
206             }
207             }
208              
209             sub UpdateFromHash
210             {
211 7     7 0 21 exact_argcount(2);
212 7         107 my ($self, $Params) = @_;
213              
214 7 50       23 if (ref($Params) ne 'HASH')
215 0         0 { croak 'Parameter must be a hash reference'; }
216              
217 7         27 foreach my $Param (keys %$Params)
218             {
219 54 50       120 if (!exists($ALL_PARAMS{$Param}))
220 0         0 { croak "$Param is not a valid parameter"; }
221              
222 54         148 $self->$Param($Params->{$Param});
223             }
224             }
225              
226             sub BaseScore
227             {
228 11     11 0 38 exact_argcount(1);
229 11         169 my $self = shift;
230              
231             # Check all parameters exist
232 11         36 foreach my $Param (keys %BASE_PARAMS)
233             {
234 77 50       220 if (!defined($self->{$Param}))
235 0         0 { croak("You must set '$Param' to calculate the Base CVSS score"); }
236             }
237              
238 11         109 my $Score = 10;
239 11         21 foreach my $Param ('AccessVector', 'AccessComplexity', 'Authentication')
240             {
241 33         185 $Score *= $BASE_PARAMS{$Param}->{Params}->{$self->{$Param}};
242             }
243              
244             # Calculate the impact portion of the score taking into account the weighting bias
245 11         20 my $ImpactScore = 0;
246 11         19 foreach my $ImpactType ('ConfidentialityImpact', 'IntegrityImpact', 'AvailabilityImpact')
247             {
248 33         111 my $Value = $BASE_PARAMS{$ImpactType}->{Params}->{$self->{$ImpactType}};
249              
250 33 100       479 if ($self->{ImpactBias} . 'impact' eq lc($ImpactType))
    100          
251 5         11 { $Value *= 0.5; }
252             elsif ($self->{ImpactBias} eq 'normal')
253 18         25 { $Value *= 0.333; }
254             else
255 10         14 { $Value *= 0.25; }
256              
257 33         74 $ImpactScore += $Value;
258             }
259 11         18 $Score *= $ImpactScore;
260              
261             # Round to one sig fig
262 11         138 return sprintf('%.1f', $Score);
263             }
264              
265             sub TemporalScore
266             {
267 4     4 0 16 exact_argcount(1);
268 4         109 my $self = shift;
269              
270             # Check all parameters exist
271 4         13 foreach my $Param (keys %TEMPORAL_PARAMS)
272             {
273 12 50       41 if (!defined($self->{$Param}))
274 0         0 { croak("You must set '$Param' to calculate the Temporal CVSS score"); }
275             }
276              
277 4         13 my $Score = $self->BaseScore();
278              
279 4         15 foreach my $Param (keys %TEMPORAL_PARAMS)
280 12         45 { $Score *= $TEMPORAL_PARAMS{$Param}->{Params}->{$self->{$Param}}; }
281              
282             # Round to one sig fig
283 4         33 return sprintf('%.1f', $Score);
284             }
285              
286             sub EnvironmentalScore
287             {
288 1     1 0 4 exact_argcount(1);
289 1         17 my $self = shift;
290              
291             # Check all parameters exist
292 1         4 foreach my $Param (keys %ENVIRONMENTAL_PARAMS)
293             {
294 2 50       10 if (!defined($self->{$Param}))
295 0         0 { croak("You must set '$Param' to calculate the Environmental CVSS score"); }
296             }
297              
298 1         4 my $TemporalScore = $self->TemporalScore;
299              
300 1         7 my $Score = ($TemporalScore + ((10 - $TemporalScore)
301             * $ENVIRONMENTAL_PARAMS{CollateralDamagePotential}->{Params}->{$self->{CollateralDamagePotential}}))
302             * $ENVIRONMENTAL_PARAMS{TargetDistribution}->{Params}->{$self->{TargetDistribution}};
303              
304             # Round to one sig fig
305 1         9 return sprintf('%.1f', $Score);
306             }
307              
308             1;
309             __END__