File Coverage

blib/lib/Bookings/Security/CVSS/v2.pm
Criterion Covered Total %
statement 9 11 81.8
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 13 15 86.6


line stmt bran cond sub pod time code
1             package Bookings::Security::CVSS::v2;
2              
3 1     1   16265 use 5.006;
  1         2  
4 1     1   4 use strict;
  1         1  
  1         17  
5 1     1   2 use warnings;
  1         9  
  1         55  
6              
7 1     1   326 use Math::Round qw(nearest_ceil);
  0            
  0            
8              
9             use Carp qw( croak );
10              
11             =head1 NAME
12              
13             Bookings::Security::CVSS::v2 - Calculate CVSSv2 values (Common Vulnerability Scoring System)
14              
15             =head1 DESCRIPTION
16              
17             CVSSv2 allows you to calculate all three types of score described
18             under the CVSS system: Base, Temporal and Environmental.
19              
20             You can modify any parameter via its setter and recalculate
21             at any time.
22              
23             The temporal score depends on the base score, and the environmental
24             score depends on the temporal score. Therefore you must remember
25             to supply all necessary parameters.
26              
27             SetVector allows you to parse a CVSSv2 vector as described at:
28             http://nvd.nist.gov/cvss.cfm?vectorinfo
29              
30             Vector() will return the CVSS vector as a string.
31              
32             =head1 POSSIBLE VALUES
33              
34             For meaning of these values see the official CVSS FAQ
35             at https://www.first.org/cvss/faq/#c7
36              
37             =head1 SEE ALSO
38              
39             This module is based on the formulas supplied at:
40             http://www.first.org/cvss/
41              
42             =head1 SYNOPSIS
43              
44             use Bookings::Security::CVSS::v2;
45              
46             my $CVSS = new Bookings::Security::CVSS::v2;
47              
48             # Set metrics individually:
49             $CVSS->AccessVector('Local');
50             $CVSS->AccessComplexity('High');
51             $CVSS->Authentication('None');
52             $CVSS->ConfidentialityImpact('Complete');
53             $CVSS->IntegrityImpact('Complete');
54             $CVSS->AvailabilityImpact('Complete');
55              
56             my $BaseScore = $CVSS->BaseScore();
57              
58             $CVSS->Exploitability('Proof-Of-Concept');
59             $CVSS->RemediationLevel('Official-Fix');
60             $CVSS->ReportConfidence('Confirmed');
61              
62             my $TemporalScore = $CVSS->TemporalScore()
63              
64             $CVSS->CollateralDamagePotential('None');
65             $CVSS->TargetDistribution('None');
66             $CVSS->ConfidentialityRequirement('High');
67             $CVSS->IntegrityRequirement('Medium');
68             $CVSS->AvailabilityRequirement('Low');
69              
70             my $EnvironmentalScore = $CVSS->EnvironmentalScore();
71              
72             # Set lots of metrics simultaneously in the constructor:
73             my $CVSS = new Bookings::Security::CVSS::v2({
74             AccessVector => 'Local',
75             AccessComplexity => 'High',
76             Authentication => 'None',
77             ConfidentialityImpact => 'Complete',
78             IntegrityImpact => 'Complete',
79             AvailabilityImpact => 'Complete',
80             });
81              
82             my $BaseScore = $CVSS->BaseScore();
83              
84             # Update lots of metrics simultaneously with a hashref:
85             $CVSS->UpdateFromHash({
86             AccessVector => 'Network',
87             AccessComplexity => 'Low'
88             });
89              
90              
91             my $NewBaseScore = $CVSS->BaseScore();
92              
93             # Set the entire vector at once:
94             $CVSS->SetVector('(AV:L/AC:H/Au:S/C:N/I:P/A:C)');
95             my $BaseScore = $CVSS->BaseScore();
96              
97             # Get metrics individually:
98             $CVSS->AccessVector; # 'local'
99              
100             # Get the whole vector:
101             my $Vector = $CVSS->Vector();
102              
103             =cut
104              
105             our %BASE_PARAMS = (
106             AccessVector => { Params => {'local' => 0.395, 'adjacent-network' => 0.646, 'network' => 1},
107             P2V => {'local' => 'L', 'adjacent-network' => 'A', 'network' => 'N'},
108             Abbrev => 'AV',
109             },
110              
111             AccessComplexity => { Params => {'low' => 0.71, 'medium' => 0.61, 'high' => 0.35},
112             P2V => {'low' => 'L', 'medium' => 'M', 'high' => 'H'},
113             Abbrev => 'AC',
114             },
115              
116             Authentication => { Params => {'multiple' => 0.45, 'single' => 0.56, 'none' => 0.704},
117             P2V => {'multiple' => 'M', 'single' => 'S', 'none' => 'N'},
118             Abbrev => 'Au',
119             },
120              
121             ConfidentialityImpact => { Params => {'none' => 0, 'partial' => 0.275, 'complete' => 0.660},
122             P2V => {'none' => 'N', 'partial' => 'P', 'complete' => 'C'},
123             Abbrev => 'C',
124             },
125              
126             IntegrityImpact => { Params => {'none' => 0, 'partial' => 0.275, 'complete' => 0.660},
127             P2V => {'none' => 'N', 'partial' => 'P', 'complete' => 'C'},
128             Abbrev => 'I',
129             },
130              
131             AvailabilityImpact => { Params => {'none' => 0, 'partial' => 0.275, 'complete' => 0.660},
132             P2V => {'none' => 'N', 'partial' => 'P', 'complete' => 'C'},
133             Abbrev => 'A',
134             },
135             );
136             _CreateV2P(\%BASE_PARAMS);
137              
138             our %TEMPORAL_PARAMS = (
139             Exploitability => { Params => {'not-defined' => 1, 'unproven' => 0.85, 'proof-of-concept' => 0.9, 'functional' => 0.95, 'high' => 1},
140             P2V => {'not-defined' => 'ND', 'unproven' => 'U', 'proof-of-concept' => 'POC', 'functional' => 'F', 'high' => 'H'},
141             Abbrev => 'E',
142             },
143              
144             RemediationLevel => { Params => {'not-defined' => 1, 'official-fix' => 0.87, 'temporary-fix' => 0.9, 'workaround' => 0.95, 'unavailable' => 1},
145             P2V => {'not-defined' => 'ND', 'official-fix' => 'OF', 'temporary-fix' => 'TF', 'workaround' => 'W', 'unavailable' => 'U'},
146             Abbrev => 'RL',
147             },
148              
149             ReportConfidence => { Params => {'not-defined' => 1, 'unconfirmed' => 0.9, 'uncorroborated' => 0.95, 'confirmed' => 1},
150             P2V => {'not-defined' => 'ND', 'unconfirmed' => 'UC', 'uncorroborated' => 'UR', 'confirmed' => 'C'},
151             Abbrev => 'RC',
152             },
153             );
154             _CreateV2P(\%TEMPORAL_PARAMS);
155              
156             our %ENVIRONMENTAL_PARAMS = (
157             CollateralDamagePotential => { Params => {'not-defined' => 0, 'none' => 0, 'low' => 0.1, 'low-medium' => 0.3, 'medium-high' => 0.4, 'high' => 0.5},
158             P2V => {'not-defined' => 'ND', 'none' => 'N', 'low' => 'L', 'low-medium' => 'LM', 'medium-high' => 'MH', 'high' => 'H'},
159             Abbrev => 'CDP',
160             },
161              
162             TargetDistribution => { Params => {'not-defined' => 1, 'none' => 0, 'low' => 0.25, 'medium' => 0.75, 'high' => 1},
163             P2V => {'not-defined' => 'ND', 'none' => 'N', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
164             Abbrev => 'TD',
165             },
166              
167             ConfidentialityRequirement => { Params => {'not-defined' => 1, 'low' => 0.5, 'medium' => 1, 'high' => 1.51},
168             P2V => {'not-defined' => 'ND', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
169             Abbrev => 'CR',
170             },
171              
172             IntegrityRequirement => { Params => {'not-defined' => 1, 'low' => 0.5, 'medium' => 1, 'high' => 1.51},
173             P2V => {'not-defined' => 'ND', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
174             Abbrev => 'IR',
175             },
176              
177             AvailabilityRequirement => { Params => {'not-defined' => 1, 'low' => 0.5, 'medium' => 1, 'high' => 1.51},
178             P2V => {'not-defined' => 'ND', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
179             Abbrev => 'AR',
180             },
181             );
182             _CreateV2P(\%ENVIRONMENTAL_PARAMS);
183              
184             our %ALL_PARAMS = (%BASE_PARAMS, %TEMPORAL_PARAMS, %ENVIRONMENTAL_PARAMS);
185              
186             # Create getters and setters for all parameters
187             foreach my $key (keys %ALL_PARAMS) {
188             no strict 'refs';
189              
190             # Long-name getter. For example, $self->TargetDistribution might return 'low'
191             *{"Bookings::Security::CVSS::v2::$key"} = sub {
192             my $self = shift;
193             return ($self->{$key} // 'not-defined');
194             };
195              
196             # And create getters for the 'short' values used in the vector.
197             # For example, $CVSS->TD might return 'L'.
198             my $abbrev = $ALL_PARAMS{$key}->{'Abbrev'};
199             *{"Bookings::Security::CVSS::v2::$abbrev"} = sub {
200             my $self = shift;
201             if ($self->{$key}) {
202             return ($ALL_PARAMS{$key}->{P2V}->{ $self->{$key} });
203             } else {
204             return undef;
205             }
206             };
207              
208             # setter
209             *{"Bookings::Security::CVSS::v2::Set$key"} = sub {
210             my $self = shift;
211             $self->_ValidateParam($key, @_);
212             };
213             }
214              
215              
216             # Create the Vector-to-param hash from the P2V hash
217             sub _CreateV2P {
218             my $params = shift;
219              
220             foreach my $param (keys %$params) {
221             $params->{$param}->{V2P} = { map { $params->{$param}->{P2V}->{$_} => $_ } keys %{$params->{$param}->{P2V}} };
222             }
223             }
224              
225             sub _ValidateParam {
226             my $self = shift;
227             my $param = shift;
228             my $value = shift;
229              
230             # If vector value - convert to full value
231             if (exists($ALL_PARAMS{$param}->{V2P}->{$value})) {
232             $value = $ALL_PARAMS{$param}->{V2P}->{$value};
233             } else {
234             $value = lc($value);
235             }
236              
237             if (!grep(/^$value$/i, keys %{$ALL_PARAMS{$param}->{Params}})) {
238             croak("Invalid value '$value' for $param");
239             }
240              
241             $self->{$param} = $value;
242             }
243              
244             sub UpdateFromHash {
245             my ($self, $params) = @_;
246              
247             if (ref($params) ne 'HASH') {
248             croak 'Parameter must be a hash reference';
249             }
250              
251             foreach my $param (keys %$params) {
252             if (!exists($ALL_PARAMS{$param})) {
253             croak "$param is not a valid parameter";
254             }
255              
256             my $setter_name = "Set$param";
257             $self->$setter_name($params->{$param});
258             }
259             }
260              
261             =head1 EXPORT
262              
263             new
264             Vector
265             SetVector
266             OverallScore
267             BaseScore
268             TemporalScore
269             EnvironmentalScore
270              
271             =cut
272              
273             =head1 SUBROUTINES/METHODS
274              
275             =head2 new
276              
277             =cut
278              
279             sub new {
280             my $class = shift;
281             my $params = shift;
282              
283             my $self = bless({}, $class);
284              
285             if (defined($params)) {
286             $self->UpdateFromHash($params);
287             }
288             return $self;
289             }
290              
291             =head2 SetVector
292              
293             =cut
294              
295             sub SetVector {
296              
297             my ($self, $vector) = @_;
298              
299             if (defined($vector)) {
300              
301             if ($vector !~ m/
302             # Base vector
303             ^\(AV:([LAN])\/AC:([HML])\/Au:([MSN])\/C:([NPC])\/I:([NPC])\/A:([NPC])
304              
305             # Temporal vector
306             (\/E:(ND|U|POC|F|H)\/RL:(ND|OF|TF|W|U)\/RC:(ND|UC|UR|C))?
307              
308             # Environmental vector
309             (\/CDP:(ND|N|L|LM|MH|H)\/TD:(ND|N|L|M|H)\/CR:(ND|L|M|H)\/IR:(ND|L|M|H)\/AR:(ND|L|M|H))?\)
310              
311             /x) {
312             croak('Invalid CVSS vector');
313             }
314              
315             my %values = (
316             AccessVector => $1,
317             AccessComplexity => $2,
318             Authentication => $3,
319             ConfidentialityImpact => $4,
320             IntegrityImpact => $5,
321             AvailabilityImpact => $6,
322             );
323              
324             # optional temporal portion
325             if (defined($7)) {
326             %values = (
327             %values,
328             Exploitability => $8,
329             RemediationLevel => $9,
330             ReportConfidence => $10
331             );
332             }
333              
334             # optional environmental portion.
335             if (defined($11)) {
336             %values = (
337             %values,
338             CollateralDamagePotential => $12,
339             TargetDistribution => $13,
340             ConfidentialityRequirement => $14,
341             IntegrityRequirement => $15,
342             AvailabilityRequirement => $16,
343             );
344             }
345              
346             $self->UpdateFromHash(\%values);
347              
348             }
349             else {
350             croak('Must call SetVector() with a $vector!');
351             }
352             }
353              
354             =head2 Vector
355              
356             =cut
357              
358             sub Vector {
359             my ($self) = @_;
360              
361             # Check all parameters exist
362             foreach my $param (keys %BASE_PARAMS) {
363             if (!defined($self->{$param})) {
364             croak("You must set '$param' to output the CVSS vector");
365             }
366             }
367              
368             my $vectorValue = sub {
369             return $ALL_PARAMS{$_[0]}->{P2V}->{$self->{$_[0]}};
370             };
371              
372             my $vector = sprintf('AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s',
373             &$vectorValue('AccessVector'),
374             &$vectorValue('AccessComplexity'),
375             &$vectorValue('Authentication'),
376             &$vectorValue('ConfidentialityImpact'),
377             &$vectorValue('IntegrityImpact'),
378             &$vectorValue('AvailabilityImpact'));
379              
380             my $temporal = 1;
381             foreach my $param (keys %TEMPORAL_PARAMS) {
382             if (!defined($self->{$param})) {
383             $temporal = 0;
384             last;
385             }
386             }
387              
388             if ($temporal) {
389             $vector .= sprintf('/E:%s/RL:%s/RC:%s',
390             &$vectorValue('Exploitability'),
391             &$vectorValue('RemediationLevel'),
392             &$vectorValue('ReportConfidence'));
393             }
394              
395             my $environmental = 1;
396             foreach my $param (keys %ENVIRONMENTAL_PARAMS) {
397             if (!defined($self->{$param})) {
398             $environmental = 0;
399             last;
400             }
401             }
402              
403             if ($environmental) {
404             $vector .= sprintf('/CDP:%s/TD:%s/CR:%s/IR:%s/AR:%s',
405             &$vectorValue('CollateralDamagePotential'),
406             &$vectorValue('TargetDistribution'),
407             &$vectorValue('ConfidentialityRequirement'),
408             &$vectorValue('IntegrityRequirement'),
409             &$vectorValue('AvailabilityRequirement'));
410             }
411              
412             return "($vector)";
413             }
414              
415             =head2 BaseScore
416              
417             =cut
418              
419             sub BaseScore {
420             my $self = shift;
421              
422             # Check all parameters exist
423             foreach my $param (keys %BASE_PARAMS) {
424             if (!defined($self->{$param})) {
425             return "Not Defined";
426             }
427             }
428              
429             my $vectorValue = sub {
430             return $ALL_PARAMS{$_[0]}->{Params}->{$self->{$_[0]}};
431             };
432              
433             # Calculate the impact portion of the score taking into account the weighting bias
434             my $impact = 10.41 * (1 - (1 - &$vectorValue('ConfidentialityImpact')) * (1 - &$vectorValue('IntegrityImpact')) * (1 - &$vectorValue('AvailabilityImpact')));
435              
436             my $exploitability = 20 * &$vectorValue('AccessComplexity') * &$vectorValue('Authentication') * &$vectorValue('AccessVector');
437              
438             my $f = 0;
439             $f = 1.176 if ($impact != 0);
440              
441             my $score = (0.6 * $impact + 0.4 * $exploitability - 1.5) * ( $f );
442              
443             # Round to one sig fig
444             return nearest_ceil(0.1, $score);
445             }
446              
447             =head2 TemporalScore
448              
449             =cut
450              
451             sub TemporalScore {
452             my $self = shift;
453              
454             # Check all parameters exist
455             foreach my $param (keys %TEMPORAL_PARAMS) {
456             if (!defined($self->{$param})) {
457             return "Not Defined";
458             }
459             }
460              
461             my $vectorValue = sub {
462             return $ALL_PARAMS{$_[0]}->{Params}->{$self->{$_[0]}};
463             };
464              
465             my $score = $self->BaseScore() * &$vectorValue('Exploitability') * &$vectorValue('RemediationLevel') * &$vectorValue('ReportConfidence');
466              
467             # Round to one sig fig
468             return nearest_ceil(0.1, $score);
469             }
470              
471             =head2 EnvironmentalScore
472              
473             =cut
474              
475             sub EnvironmentalScore {
476             my $self = shift;
477              
478             # Check all parameters exist
479             foreach my $param (keys %ENVIRONMENTAL_PARAMS) {
480             if (!defined($self->{$param})) {
481             return "Not Defined";
482             }
483             }
484              
485             my $vectorValue = sub {
486             return $ALL_PARAMS{$_[0]}->{Params}->{$self->{$_[0]}};
487             };
488              
489             my $adjusted_impact = 10.41 * (1 - (1 - &$vectorValue('ConfidentialityImpact') * &$vectorValue('ConfidentialityRequirement')) *
490             (1 - &$vectorValue('IntegrityImpact') * &$vectorValue('IntegrityRequirement')) *
491             (1 - &$vectorValue('AvailabilityImpact') * &$vectorValue('AvailabilityRequirement')));
492             $adjusted_impact = 10 if $adjusted_impact > 10;
493              
494             my $exploitability = 20 * &$vectorValue('AccessComplexity') * &$vectorValue('Authentication') * &$vectorValue('AccessVector');
495              
496             my $f = 0;
497             $f = 1.176 if ($adjusted_impact != 0);
498              
499             my $adjusted_base = (0.6 * $adjusted_impact + 0.4 * $exploitability - 1.5) * ( $f );
500             $adjusted_base = nearest_ceil(0.1, $adjusted_base);
501              
502             my $adjusted_temporal = $adjusted_base * &$vectorValue('Exploitability') * &$vectorValue('RemediationLevel') * &$vectorValue('ReportConfidence');
503             $adjusted_temporal = nearest_ceil(0.1, $adjusted_temporal);
504              
505             my $score = ($adjusted_temporal + ((10 - $adjusted_temporal) * &$vectorValue('CollateralDamagePotential'))) * &$vectorValue('TargetDistribution');
506              
507             # Round to one sig fig
508             return nearest_ceil(0.1, $score);
509             }
510              
511             =head2 OverallScore
512              
513             =cut
514              
515             sub OverallScore {
516             my $self = shift;
517              
518             my $overall_score = "Not Defined";
519             my $base_score = $self->BaseScore;
520              
521             if ($base_score eq "Not Defined") {
522             return "Not Defined";
523             }
524              
525             my $environmental_score = $self->EnvironmentalScore;
526             if ($environmental_score ne "Not Defined") {
527             return $environmental_score;
528             }
529              
530             my $temporal_score = $self->TemporalScore;
531             if ($temporal_score ne "Not Defined") {
532             return $temporal_score;
533             }
534             return $base_score;
535             }
536              
537             1;
538              
539             =head1 AUTHORS
540              
541             Thomas Briggs, C<< >>
542             Daniel Ostermeier, C<< >>
543             Farshid Zaker, C<< >>
544              
545             =head1 BUGS
546              
547             Please report any bugs or feature requests to C, or through
548             the web interface at L. I will be notified, and then you'll
549             automatically be notified of progress on your bug as I make changes.
550              
551             =head1 SUPPORT
552              
553             You can find documentation for this module with the perldoc command.
554              
555             perldoc Bookings::Security::CVSS::v2
556              
557              
558             You can also look for information at:
559              
560             =over 4
561              
562             =item * RT: CPAN's request tracker (report bugs here)
563              
564             L
565              
566             =item * AnnoCPAN: Annotated CPAN documentation
567              
568             L
569              
570             =item * CPAN Ratings
571              
572             L
573              
574             =item * Search CPAN
575              
576             L
577              
578             =back
579              
580              
581             =head1 ACKNOWLEDGEMENTS
582              
583              
584             =head1 LICENSE AND COPYRIGHT
585              
586             Copyright 2017 booking.com
587              
588             This program is free software; you can redistribute it and/or modify it
589             under the terms of the the Artistic License (2.0). You may obtain a
590             copy of the full license at:
591              
592             L
593              
594             Any use, modification, and distribution of the Standard or Modified
595             Versions is governed by this Artistic License. By using, modifying or
596             distributing the Package, you accept this license. Do not use, modify,
597             or distribute the Package, if you do not accept this license.
598              
599             If your Modified Version has been derived from a Modified Version made
600             by someone other than you, you are nevertheless required to ensure that
601             your Modified Version complies with the requirements of this license.
602              
603             This license does not grant you the right to use any trademark, service
604             mark, tradename, or logo of the Copyright Holder.
605              
606             This license includes the non-exclusive, worldwide, free-of-charge
607             patent license to make, have made, use, offer to sell, sell, import and
608             otherwise transfer the Package with respect to any patent claims
609             licensable by the Copyright Holder that are necessarily infringed by the
610             Package. If you institute patent litigation (including a cross-claim or
611             counterclaim) against any party alleging that the Package constitutes
612             direct or contributory patent infringement, then this Artistic License
613             to you shall terminate on the date that such litigation is filed.
614              
615             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
616             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
617             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
618             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
619             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
620             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
621             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
622             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
623              
624              
625             =cut
626              
627             1; # End of Bookings::Security::CVSS::v2
628             __END__