File Coverage

blib/lib/Bookings/Security/CVSS/v3.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::v3;
2              
3 1     1   2959 use 5.006;
  1         3  
4 1     1   4 use strict;
  1         2  
  1         17  
5 1     1   3 use warnings;
  1         1  
  1         24  
6              
7 1     1   183 use Math::Round qw(nearest_ceil);
  0            
  0            
8              
9             use Carp qw( croak );
10              
11             =head1 NAME
12              
13             Bookings::Security::CVSS::v3 - Calculate CVSSv3 values (Common Vulnerability Scoring System)
14              
15             =head1 DESCRIPTION
16              
17             CVSSv3 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 CVSSv3 vector as described at:
28             https://www.first.org/cvss/specification-document#i6
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             Quick summary of what the module does.
45              
46             Perhaps a little code snippet.
47              
48             use Bookings::Security::CVSS::v3;
49              
50             my $foo = Bookings::Security::CVSS::v3->new();
51             ...
52             =cut
53              
54             our %BASE_PARAMS = (
55             AttackVector => { Params => {'local' => 0.55, 'adjacent-network' => 0.62, 'network' => 0.85, 'physical' => 0.2},
56             P2V => {'local' => 'L', 'adjacent-network' => 'A', 'network' => 'N', 'physical' => 'P'},
57             Abbrev => 'AV',
58             },
59              
60             AttackComplexity => { Params => {'low' => 0.77, 'high' => 0.44},
61             P2V => {'low' => 'L', 'high' => 'H'},
62             Abbrev => 'AC',
63             },
64              
65             PrivilegesRequired => { Params => {'high' => 0.27, 'low' => 0.62, 'none' => 0.85},
66             P2V => {'high' => 'H', 'low' => 'L', 'none' => 'N'},
67             Abbrev => 'PR',
68             },
69              
70             UserInteraction => { Params => {'required' => 0.62, 'none' => 0.85},
71             P2V => {'required' => 'R', 'none' => 'N'},
72             Abbrev => 'UI',
73             },
74              
75             Scope => { Params => {'unchanged' => 0, 'changed' => 1},
76             P2V => {'unchanged' => 'U', 'changed' => 'C'},
77             Abbrev => 'S',
78             },
79              
80             ConfidentialityImpact => { Params => {'none' => 0, 'low' => 0.22, 'high' => 0.56},
81             P2V => {'none' => 'N', 'low' => 'L', 'high' => 'H'},
82             Abbrev => 'C',
83             },
84              
85             IntegrityImpact => { Params => {'none' => 0, 'low' => 0.22, 'high' => 0.56},
86             P2V => {'none' => 'N', 'low' => 'L', 'high' => 'H'},
87             Abbrev => 'I',
88             },
89              
90             AvailabilityImpact => { Params => {'none' => 0, 'low' => 0.22, 'high' => 0.56},
91             P2V => {'none' => 'N', 'low' => 'L', 'high' => 'H'},
92             Abbrev => 'A',
93             },
94             );
95             _CreateV2P(\%BASE_PARAMS);
96              
97             our %TEMPORAL_PARAMS = (
98             Exploitability => { Params => {'not-defined' => 1, 'unproven' => 0.91, 'proof-of-concept' => 0.94, 'functional' => 0.97, 'high' => 1},
99             P2V => {'not-defined' => 'X', 'unproven' => 'U', 'proof-of-concept' => 'P', 'functional' => 'F', 'high' => 'H'},
100             Abbrev => 'E',
101             },
102              
103             RemediationLevel => { Params => {'not-defined' => 1, 'official-fix' => 0.95, 'temporary-fix' => 0.96, 'workaround' => 0.97, 'unavailable' => 1},
104             P2V => {'not-defined' => 'X', 'official-fix' => 'O', 'temporary-fix' => 'T', 'workaround' => 'W', 'unavailable' => 'U'},
105             Abbrev => 'RL',
106             },
107              
108             ReportConfidence => { Params => {'not-defined' => 1, 'unknown' => 0.92, 'reasonable' => 0.96, 'confirmed' => 1},
109             P2V => {'not-defined' => 'X', 'unknown' => 'U', 'reasonable' => 'R', 'confirmed' => 'C'},
110             Abbrev => 'RC',
111             },
112             );
113             _CreateV2P(\%TEMPORAL_PARAMS);
114              
115             our %ENVIRONMENTAL_PARAMS = (
116             ConfidentialityRequirement => { Params => {'not-defined' => 1, 'low' => 0.5, 'medium' => 1, 'high' => 1.5},
117             P2V => {'not-defined' => 'X', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
118             Abbrev => 'CR',
119             },
120              
121             IntegrityRequirement => { Params => {'not-defined' => 1, 'low' => 0.5, 'medium' => 1, 'high' => 1.5},
122             P2V => {'not-defined' => 'X', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
123             Abbrev => 'IR',
124             },
125              
126             AvailabilityRequirement => { Params => {'not-defined' => 1, 'low' => 0.5, 'medium' => 1, 'high' => 1.5},
127             P2V => {'not-defined' => 'X', 'low' => 'L', 'medium' => 'M', 'high' => 'H'},
128             Abbrev => 'AR',
129             },
130              
131             ModifiedAttackVector => { Params => {'not-defined' => -1, 'local' => 0.55, 'adjacent-network' => 0.62, 'network' => 0.85, 'physical' => 0.2},
132             P2V => {'not-defined' => 'X', 'local' => 'L', 'adjacent-network' => 'A', 'network' => 'N', 'physical' => 'P'},
133             Abbrev => 'MAV',
134             },
135              
136             ModifiedAttackComplexity => { Params => {'not-defined' => -1, 'low' => 0.77, 'high' => 0.44},
137             P2V => {'not-defined' => 'X', 'low' => 'L', 'high' => 'H'},
138             Abbrev => 'MAC',
139             },
140              
141             ModifiedPrivilegesRequired => { Params => {'not-defined' => -1, 'high' => 0.27, 'low' => 0.62, 'none' => 0.85},
142             P2V => {'not-defined' => 'X', 'high' => 'H', 'low' => 'L', 'none' => 'N'},
143             Abbrev => 'MPR',
144             },
145              
146             ModifiedUserInteraction => { Params => {'not-defined' => -1, 'required' => 0.62, 'none' => 0.85},
147             P2V => {'not-defined' => 'X', 'required' => 'R', 'none' => 'N'},
148             Abbrev => 'MUI',
149             },
150              
151             ModifiedScope => { Params => {'not-defined' => -1, 'unchanged' => 0, 'changed' => 1},
152             P2V => {'not-defined' => 'X', 'unchanged' => 'U', 'changed' => 'C'},
153             Abbrev => 'MS',
154             },
155              
156             ModifiedConfidentialityImpact => { Params => {'not-defined' => -1, 'none' => 0, 'low' => 0.22, 'high' => 0.56},
157             P2V => {'not-defined' => 'X', 'none' => 'N', 'low' => 'L', 'high' => 'H'},
158             Abbrev => 'MC',
159             },
160              
161             ModifiedIntegrityImpact => { Params => {'not-defined' => -1, 'none' => 0, 'low' => 0.22, 'high' => 0.56},
162             P2V => {'not-defined' => 'X', 'none' => 'N', 'low' => 'L', 'high' => 'H'},
163             Abbrev => 'MI',
164             },
165              
166             ModifiedAvailabilityImpact => { Params => {'not-defined' => -1, 'none' => 0, 'low' => 0.22, 'high' => 0.56},
167             P2V => {'not-defined' => 'X', 'none' => 'N', 'low' => 'L', 'high' => 'H'},
168             Abbrev => 'MA',
169             },
170             );
171             _CreateV2P(\%ENVIRONMENTAL_PARAMS);
172              
173             our %ALL_PARAMS = (%BASE_PARAMS, %TEMPORAL_PARAMS, %ENVIRONMENTAL_PARAMS);
174              
175             # Create getters and setters for all parameters
176             foreach my $key (keys %ALL_PARAMS) {
177             no strict 'refs';
178              
179             # Long-name getter. For example, $self->Exploitability might return 'proof-of-concept'.
180             *{"Bookings::Security::CVSS::v3::$key"} = sub {
181             my $self = shift;
182             return ($self->{$key} // 'not-defined');
183             };
184              
185             # And create getters for the 'short' values used in the vector.
186             # For example, $CVSS->MI might return N.
187             my $abbrev = $ALL_PARAMS{$key}->{'Abbrev'};
188             *{"Bookings::Security::CVSS::v3::$abbrev"} = sub {
189             my $self = shift;
190             if ($self->{$key}) {
191             return $ALL_PARAMS{$key}->{P2V}->{ $self->{$key} };
192             } else {
193             return undef;
194             }
195             };
196              
197             # setter
198             *{"Bookings::Security::CVSS::v3::Set$key"} = sub {
199             my $self = shift;
200             $self->_ValidateParam($key, @_);
201             };
202             }
203              
204             # Create the Vector-to-param hash from the P2V hash
205             sub _CreateV2P {
206             my $params = shift;
207              
208             foreach my $param (keys %$params) {
209             $params->{$param}->{V2P} = { map { $params->{$param}->{P2V}->{$_} => $_ } keys %{$params->{$param}->{P2V}} };
210             }
211             }
212              
213             sub _ValidateParam {
214             my $self = shift;
215             my $param = shift;
216             my $value = shift;
217              
218             # If vector value - convert to full value
219             if (exists($ALL_PARAMS{$param}->{V2P}->{$value})) {
220             $value = $ALL_PARAMS{$param}->{V2P}->{$value};
221             } else {
222             $value = lc($value);
223             }
224              
225             if (!grep(/^$value$/i, keys %{$ALL_PARAMS{$param}->{Params}})) {
226             croak("Invalid value '$value' for $param");
227             }
228              
229             $self->{$param} = $value;
230             }
231              
232             sub UpdateFromHash {
233             my ($self, $params) = @_;
234              
235             if (ref($params) ne 'HASH') {
236             croak 'Parameter must be a hash reference';
237             }
238              
239             foreach my $param (keys %$params) {
240             if (!exists($ALL_PARAMS{$param})) {
241             croak "$param is not a valid parameter";
242             }
243              
244             my $setter_name = "Set$param";
245             $self->$setter_name($params->{$param});
246             }
247             }
248              
249              
250             =head1 EXPORT
251             new
252             Vector
253             SetVector
254             OverallScore
255             BaseScore
256             TemporalScore
257             EnvironmentalScore
258              
259             =head1 SUBROUTINES/METHODS
260              
261             =head2 new
262              
263             =cut
264              
265             sub new {
266             my $class = shift;
267             my $params = shift;
268              
269             my $self = bless({}, $class);
270              
271             if (defined($params)) {
272             $self->UpdateFromHash($params);
273             }
274             return $self;
275             }
276              
277             =head2 SetVector
278              
279             =cut
280             sub SetVector {
281             my ($self, $vector) = @_;
282              
283             if (defined($vector)) {
284              
285             if ($vector !~ m/
286             # Base vector
287             ^CVSS:3\.0\/AV:([NALP])\/AC:([LH])\/PR:([NLH])\/UI:([NR])\/S:([UC])\/C:([NLH])\/I:([NLH])\/A:([NLH])
288              
289             # Temporal vector
290             (\/E:([XUPFH])\/RL:([XOTWU])\/RC:([XURC]))?
291              
292             # Environmental vector
293             (\/CR:([XLMH])\/IR:([XLMH])\/AR:([XLMH])\/MAV:([XNALP])\/MAC:([XLH])\/MPR:([XNLH])\/MUI:([XNR])\/MS:([XUC])\/MC:([XNLH])\/MI:([XNLH])\/MA:([XNLH]))?
294              
295             /x) {
296             croak("Invalid CVSS vector $vector");
297             }
298              
299             my %values = (
300             AttackVector => $1,
301             AttackComplexity => $2,
302             PrivilegesRequired => $3,
303             UserInteraction => $4,
304             Scope => $5,
305             ConfidentialityImpact => $6,
306             IntegrityImpact => $7,
307             AvailabilityImpact => $8,
308             );
309              
310             # optional temporal portion
311             if (defined($9)) {
312             %values = (
313             %values,
314             Exploitability => $10,
315             RemediationLevel => $11,
316             ReportConfidence => $12,
317             );
318             }
319              
320             # optional environmental portion.
321             if (defined($13)) {
322             %values = (
323             %values,
324             ConfidentialityRequirement => $14,
325             IntegrityRequirement => $15,
326             AvailabilityRequirement => $16,
327             ModifiedAttackVector => $17,
328             ModifiedAttackComplexity => $18,
329             ModifiedPrivilegesRequired => $19,
330             ModifiedUserInteraction => $20,
331             ModifiedScope => $21,
332             ModifiedConfidentialityImpact => $22,
333             ModifiedIntegrityImpact => $23,
334             ModifiedAvailabilityImpact => $24,
335             );
336             }
337              
338             $self->UpdateFromHash(\%values);
339              
340             }
341             else {
342             croak('Must call SetVector() with a $vector!');
343             }
344             }
345              
346             =head2 Vector
347              
348             =cut
349             sub Vector {
350             my ($self) = @_;
351              
352             # Check all parameters exist
353             foreach my $param (keys %BASE_PARAMS) {
354             if (!defined($self->{$param})) {
355             croak("You must set '$param' to output the CVSS vector");
356             }
357             }
358              
359             my $vectorValue = sub {
360             return $ALL_PARAMS{$_[0]}->{P2V}->{$self->{$_[0]}};
361             };
362              
363             my $vector = sprintf('AV:%s/AC:%s/PR:%s/UI:%s/S:%s/C:%s/I:%s/A:%s',
364             &$vectorValue('AttackVector'),
365             &$vectorValue('AttackComplexity'),
366             &$vectorValue('PrivilegesRequired'),
367             &$vectorValue('UserInteraction'),
368             &$vectorValue('Scope'),
369             &$vectorValue('ConfidentialityImpact'),
370             &$vectorValue('IntegrityImpact'),
371             &$vectorValue('AvailabilityImpact'));
372              
373             my $temporal = 1;
374             foreach my $param (keys %TEMPORAL_PARAMS) {
375             if (!defined($self->{$param})) {
376             $temporal = 0;
377             last;
378             }
379             }
380              
381             if ($temporal) {
382             $vector .= sprintf('/E:%s/RL:%s/RC:%s',
383             &$vectorValue('Exploitability'),
384             &$vectorValue('RemediationLevel'),
385             &$vectorValue('ReportConfidence'));
386             }
387              
388             my $environmental = 1;
389             foreach my $param (keys %ENVIRONMENTAL_PARAMS) {
390             if (!defined($self->{$param})) {
391             $environmental = 0;
392             last;
393             }
394             }
395              
396             if ($environmental) {
397             $vector .= sprintf('/CR:%s/IR:%s/AR:%s/MAV:%s/MAC:%s/MPR:%s/MUI:%s/MS:%s/MC:%s/MI:%s/MA:%s',
398             &$vectorValue('ConfidentialityRequirement'),
399             &$vectorValue('IntegrityRequirement'),
400             &$vectorValue('AvailabilityRequirement'),
401             &$vectorValue('ModifiedAttackVector'),
402             &$vectorValue('ModifiedAttackComplexity'),
403             &$vectorValue('ModifiedPrivilegesRequired'),
404             &$vectorValue('ModifiedUserInteraction'),
405             &$vectorValue('ModifiedScope'),
406             &$vectorValue('ModifiedConfidentialityImpact'),
407             &$vectorValue('ModifiedIntegrityImpact'),
408             &$vectorValue('ModifiedAvailabilityImpact'));
409             }
410              
411             return "CVSS:3.0/$vector";
412             }
413              
414             =head2 BaseScore
415              
416             =cut
417              
418             sub BaseScore {
419             my $self = shift;
420              
421             # Check all parameters exist
422             foreach my $param (keys %BASE_PARAMS) {
423             if (!defined($self->{$param})) {
424             return "Not Defined";
425             }
426             }
427              
428             my $vectorValue = sub {
429             return $ALL_PARAMS{$_[0]}->{Params}->{$self->{$_[0]}};
430             };
431             my $vectorKey = sub {
432             return $ALL_PARAMS{$_[0]}->{P2V}->{$self->{$_[0]}};
433             };
434              
435             my $sc = &$vectorValue('Scope');
436             my $pr = &$vectorValue('PrivilegesRequired');
437              
438             if ($sc) {
439             my $prk = &$vectorKey('PrivilegesRequired');
440             $pr = 0.68 if $prk eq 'L';
441             $pr = 0.50 if $prk eq 'H';
442             }
443              
444             # Calculate the impact subscore: Spec: Impact = 1 - (1 - ConfImpact)*(1 - IntegImpact) * (1 - AvailImpact)
445             my $impact = 1 - (1 - &$vectorValue('ConfidentialityImpact')) * (1 - &$vectorValue('IntegrityImpact')) * (1 - &$vectorValue('AvailabilityImpact'));
446              
447             # Calculate the exploitability subscore: Spec: Exploitability = 8.22 * AttackVector * AttackComplexity * PrivilegesRequired * UserInteraction
448             my $exploitability = 8.22 * &$vectorValue('AttackVector') * &$vectorValue('AttackComplexity') * $pr * &$vectorValue('UserInteraction');
449             my $base = 0;
450              
451             # Calculations change based on scope.
452             if ($sc == 0) { # unchanged
453             $impact = 6.42 * $impact;
454             $base = $impact + $exploitability;
455             } else {
456             $impact = 7.52 * ( $impact - 0.029) - 3.25 * ( ($impact - 0.02) ** 15 );
457             $base = 1.08 * ($impact + $exploitability);
458             }
459              
460             if ($impact < 0) {
461             $base = 0;
462             $impact = 0;
463             } else {
464             $base = nearest_ceil(0.1, $base + 0.05); # Always round up.
465             if ($base > 10) {
466             $base = 10;
467             }
468             }
469              
470             return $base;
471             }
472              
473             =head2 TemporalScore
474              
475             =cut
476              
477             sub TemporalScore {
478             my $self = shift;
479              
480             # Check all parameters exist
481             foreach my $param (keys %TEMPORAL_PARAMS) {
482             if (!defined($self->{$param})) {
483             return "Not Defined";
484             }
485             }
486              
487             my $vectorValue = sub {
488             return $ALL_PARAMS{$_[0]}->{Params}->{$self->{$_[0]}};
489             };
490              
491             my $score = $self->BaseScore() * &$vectorValue('Exploitability') * &$vectorValue('RemediationLevel') * &$vectorValue('ReportConfidence');
492              
493             # Round to one sig fig
494             return nearest_ceil(0.1, $score + 0.05); # Always round up.
495             }
496              
497             =head2 EnvironmentalScore
498              
499             =cut
500              
501             sub EnvironmentalScore {
502             my $self = shift;
503              
504             # Check all parameters exist
505             foreach my $param (keys %ENVIRONMENTAL_PARAMS) {
506             if (!defined($self->{$param})) {
507             return "Not Defined";
508             }
509             }
510              
511             my $vectorValue = sub {
512             return $ALL_PARAMS{$_[0]}->{Params}->{$self->{$_[0]}};
513             };
514             my $vectorKey = sub {
515             return $ALL_PARAMS{$_[0]}->{P2V}->{$self->{$_[0]}};
516             };
517              
518             # If the environment specifies a modified version of the base metric, then use the modified version.
519             # Otherwise, use the base version.
520             my $ci = (&$vectorValue('ModifiedConfidentialityImpact') != -1) ? &$vectorValue('ModifiedConfidentialityImpact') : &$vectorValue('ConfidentialityImpact');
521             my $ii = (&$vectorValue('ModifiedIntegrityImpact') != -1) ? &$vectorValue('ModifiedIntegrityImpact') : &$vectorValue('IntegrityImpact');
522             my $ai = (&$vectorValue('ModifiedAvailabilityImpact') != -1) ? &$vectorValue('ModifiedAvailabilityImpact') : &$vectorValue('AvailabilityImpact');
523              
524             # Modified impact subscore
525             my $mimpact = 1.0 - (1.0 - $ci * &$vectorValue('ConfidentialityRequirement')) *
526             (1.0 - $ii * &$vectorValue('IntegrityRequirement')) *
527             (1.0 - $ai * &$vectorValue('AvailabilityRequirement'));
528              
529             $mimpact = 0.915 if ($mimpact > 0.915);
530              
531             my $av = (&$vectorValue('ModifiedAttackVector') != -1) ? &$vectorValue('ModifiedAttackVector') : &$vectorValue('AttackVector');
532             my $ac = (&$vectorValue('ModifiedAttackComplexity') != -1) ? &$vectorValue('ModifiedAttackComplexity') : &$vectorValue('AttackComplexity');
533             my $ui = (&$vectorValue('ModifiedUserInteraction') != -1) ? &$vectorValue('ModifiedUserInteraction') : &$vectorValue('UserInteraction');
534              
535             my $ms = (&$vectorValue('ModifiedScope') != -1) ? &$vectorValue('ModifiedScope') : &$vectorValue('Scope');
536             my $pr = (&$vectorValue('ModifiedPrivilegesRequired') != -1) ? &$vectorValue('ModifiedPrivilegesRequired') : &$vectorValue('PrivilegesRequired');
537              
538             if ($ms != 0) { # changed
539             my $prk = (&$vectorKey('ModifiedPrivilegesRequired') ne 'X') ? &$vectorKey('ModifiedPrivilegesRequired') : &$vectorKey('PrivilegesRequired');
540             $pr = 0.68 if $prk eq 'L';
541             $pr = 0.50 if $prk eq 'H';
542             }
543              
544             # Exploitability score
545             my $mexploitability = 8.22 * $av * $ac * $pr * $ui;
546              
547             my $environment = 0;
548             if ($ms == 0) { # Unchanged
549             $mimpact = 6.42 * $mimpact;
550             $environment = $mimpact + $mexploitability;
551             } else { # changed
552             $mimpact = 7.52 * ($mimpact - 0.029) - 3.25 * (($mimpact - 0.02) ** 15);
553             $environment = 1.08 * ($mimpact + $mexploitability);
554             }
555              
556             if ($mimpact < 0) {
557             $mimpact = 0;
558             $environment = 0;
559             } else {
560             if ($environment > 10) {
561             $environment = 10;
562             }
563             $environment = nearest_ceil(0.1, $environment + 0.05); # Always round up.
564             my $temporal_product = &$vectorValue('Exploitability') * &$vectorValue('RemediationLevel') * &$vectorValue('ReportConfidence');
565             $environment = nearest_ceil(0.1, $environment * $temporal_product + 0.05);
566             }
567              
568             # Round to one sig fig
569             return $environment; # always round up.
570             }
571              
572             =head2 OverallScore
573              
574             =cut
575              
576             sub OverallScore {
577             my $self = shift;
578              
579             my $overall_score = "Not Defined";
580             my $base_score = $self->BaseScore;
581              
582             if ($base_score eq "Not Defined") {
583             return "Not Defined";
584             }
585              
586             my $environmental_score = $self->EnvironmentalScore;
587             if ($environmental_score ne "Not Defined") {
588             return $environmental_score;
589             }
590              
591             my $temporal_score = $self->TemporalScore;
592             if ($temporal_score ne "Not Defined") {
593             return $temporal_score;
594             }
595             return $base_score;
596             }
597              
598             =head1 AUTHORS
599              
600             Thomas Briggs, C<< >>
601             Daniel Ostermeier, C<< >>
602             Farshid Zaker, C<< >>
603              
604              
605             =head1 BUGS
606              
607             Please report any bugs or feature requests to C, or through
608             the web interface at L. I will be notified, and then you'll
609             automatically be notified of progress on your bug as I make changes.
610              
611              
612              
613              
614             =head1 SUPPORT
615              
616             You can find documentation for this module with the perldoc command.
617              
618             perldoc Bookings::Security::CVSS::v3
619              
620              
621             You can also look for information at:
622              
623             =over 4
624              
625             =item * RT: CPAN's request tracker (report bugs here)
626              
627             L
628              
629             =item * AnnoCPAN: Annotated CPAN documentation
630              
631             L
632              
633             =item * CPAN Ratings
634              
635             L
636              
637             =item * Search CPAN
638              
639             L
640              
641             =back
642              
643              
644             =head1 ACKNOWLEDGEMENTS
645              
646              
647             =head1 LICENSE AND COPYRIGHT
648              
649             Copyright 2017 booking.com
650              
651             This program is free software; you can redistribute it and/or modify it
652             under the terms of the the Artistic License (2.0). You may obtain a
653             copy of the full license at:
654              
655             L
656              
657             Any use, modification, and distribution of the Standard or Modified
658             Versions is governed by this Artistic License. By using, modifying or
659             distributing the Package, you accept this license. Do not use, modify,
660             or distribute the Package, if you do not accept this license.
661              
662             If your Modified Version has been derived from a Modified Version made
663             by someone other than you, you are nevertheless required to ensure that
664             your Modified Version complies with the requirements of this license.
665              
666             This license does not grant you the right to use any trademark, service
667             mark, tradename, or logo of the Copyright Holder.
668              
669             This license includes the non-exclusive, worldwide, free-of-charge
670             patent license to make, have made, use, offer to sell, sell, import and
671             otherwise transfer the Package with respect to any patent claims
672             licensable by the Copyright Holder that are necessarily infringed by the
673             Package. If you institute patent litigation (including a cross-claim or
674             counterclaim) against any party alleging that the Package constitutes
675             direct or contributory patent infringement, then this Artistic License
676             to you shall terminate on the date that such litigation is filed.
677              
678             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
679             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
680             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
681             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
682             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
683             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
684             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
685             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
686              
687              
688             =cut
689              
690             1; # End of Bookings::Security::CVSS::v3
691             __END__