CVSS
view release on metacpan or search on metacpan
lib/CVSS/v4.pm view on Meta::CPAN
use strict;
use utf8;
use warnings;
use Carp ();
use List::Util qw(max min);
use base 'CVSS::Base';
use CVSS::Constants ();
our $VERSION = '1.14';
$VERSION =~ tr/_//d; ## no critic
use constant DEBUG => $ENV{CVSS_DEBUG};
sub ATTRIBUTES { CVSS::Constants->CVSS4_ATTRIBUTES }
sub SCORE_SEVERITY { CVSS::Constants->CVSS4_SCORE_SEVERITY }
sub NOT_DEFINED_VALUE { CVSS::Constants->CVSS4_NOT_DEFINED_VALUE }
sub VECTOR_STRING_REGEX { CVSS::Constants->CVSS4_VECTOR_STRING_REGEX }
sub METRIC_GROUPS { CVSS::Constants->CVSS4_METRIC_GROUPS }
sub METRIC_NAMES { CVSS::Constants->CVSS4_METRIC_NAMES }
sub METRIC_VALUES { CVSS::Constants->CVSS4_METRIC_VALUES }
my $MAX_COMPOSED = CVSS::Constants->CVSS4_MAX_COMPOSED;
my $CVSS_LOOKUP_GLOBAL = CVSS::Constants->CVSS4_LOOKUP_GLOBAL;
my $MAX_SEVERITY = CVSS::Constants->CVSS4_MAX_SEVERITY;
sub version {'4.0'}
sub macro_vector {
my ($self) = @_;
my $eq1 = undef;
my $eq2 = undef;
my $eq3 = undef;
my $eq4 = undef;
my $eq5 = undef;
my $eq6 = undef;
# Specification https://www.first.org/cvss/v4.0/specification-document
# EQ1 (Table 24)
# Levels Constraints
# 0 AV:N and PR:N and UI:N
# 1 (AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P
# 2 AV:P or not(AV:N or PR:N or UI:N)
$eq1 = 0 if ($self->M('AV') eq 'N' && $self->M('PR') eq 'N' && $self->M('UI') eq 'N');
$eq1 = 1
if (($self->M('AV') eq 'N' || $self->M('PR') eq 'N' || $self->M('UI') eq 'N')
&& !($self->M('AV') eq 'N' && $self->M('PR') eq 'N' && $self->M('UI') eq 'N')
&& !($self->M('AV') eq 'P'));
$eq1 = 2 if ($self->M('AV') eq 'P' || !($self->M('AV') eq 'N' || $self->M('PR') eq 'N' || $self->M('UI') eq 'N'));
DEBUG and say STDERR "-- MacroVector - EQ1 : $eq1";
# EQ2 (Table 25)
# Levels Constraints
# 0 AC:L and AT:N
# 1 not (AC:L and AT:N)
$eq2 = 0 if ($self->M('AC') eq 'L' && $self->M('AT') eq 'N');
$eq2 = 1 if (!($self->M('AC') eq 'L' && $self->M('AT') eq 'N'));
DEBUG and say STDERR "-- MacroVector - EQ2 : $eq2";
# EQ3 (Table 26)
# Levels Constraints
# 0 VC:H and VI:H
# 1 not (VC:H and VI:H) and (VC:H or VI:H or VA:H)
# 2 not (VC:H or VI:H or VA:H)
$eq3 = 0 if ($self->M('VC') eq 'H' && $self->M('VI') eq 'H');
$eq3 = 1
if (!($self->M('VC') eq 'H' && $self->M('VI') eq 'H')
&& ($self->M('VC') eq 'H' || $self->M('VI') eq 'H' || $self->M('VA') eq 'H'));
$eq3 = 2 if (!($self->M('VC') eq 'H' || $self->M('VI') eq 'H' || $self->M('VA') eq 'H'));
DEBUG and say STDERR "-- MacroVector - EQ3 : $eq3";
# EQ4 (Table 27)
# Levels Constraints
# 0 MSI:S or MSA:S
# 1 not (MSI:S or MSA:S) and (SC:H or SI:H or SA:H)
# 2 not (MSI:S or MSA:S) and not (SC:H or SI:H or SA:H)
$eq4 = 0 if ($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S');
$eq4 = 1
if (!($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S')
&& ($self->M('SC') eq 'H' || $self->M('SI') eq 'H' || $self->M('SA') eq 'H'));
$eq4 = 2
if (!($self->M('MSI') eq 'S' || $self->M('MSA') eq 'S')
&& !(($self->M('SC') eq 'H' || $self->M('SI') eq 'H' || $self->M('SA') eq 'H')));
DEBUG and say STDERR "-- MacroVector - EQ4 : $eq4";
# EQ5 (Table 28)
# Levels Constraints
# 0 E:A
# 1 E:P
# 2 E:U
$eq5 = 0 if ($self->M('E') eq 'A');
$eq5 = 1 if ($self->M('E') eq 'P');
$eq5 = 2 if ($self->M('E') eq 'U');
DEBUG and say STDERR "-- MacroVector - EQ5 : $eq5";
# EQ6 (Table 29)
# Levels Constraints
# 0 (CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)
# 1 not (CR:H and VC:H) and not (IR:H and VI:H) and not (AR:H and VA:H)
$eq6 = 0
if (($self->M('CR') eq 'H' && $self->M('VC') eq 'H')
|| ($self->M('IR') eq 'H' && $self->M('VI') eq 'H')
|| ($self->M('AR') eq 'H' && $self->M('VA') eq 'H'));
$eq6 = 1
if (!($self->M('CR') eq 'H' && $self->M('VC') eq 'H')
&& !($self->M('IR') eq 'H' && $self->M('VI') eq 'H')
&& !($self->M('AR') eq 'H' && $self->M('VA') eq 'H'));
DEBUG and say STDERR "-- MacroVector - EQ6 : $eq6";
my @macro_vector = ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6);
my $macro_vector = join '', @macro_vector;
DEBUG and say STDERR "-- MacroVector : $macro_vector";
my $SEVERITY = {0 => 'HIGH', 1 => 'MEDIUM', 2 => 'LOW'};
$self->{exploitability} = $SEVERITY->{$eq1};
DEBUG and say STDERR "-- MacroVector EQ1 - Exploitability : $self->{exploitability}";
$self->{complexity} = $SEVERITY->{$eq2};
DEBUG and say STDERR "-- MacroVector EQ2 - Complexity : $self->{complexity}";
$self->{vulnerable_system} = $SEVERITY->{$eq3};
DEBUG and say STDERR "-- MacroVector EQ3 - Vulnerable System : $self->{vulnerable_system}";
$self->{subsequent_system} = $SEVERITY->{$eq4};
DEBUG and say STDERR "-- MacroVector EQ4 - Subsequent System : $self->{subsequent_system}";
$self->{exploitation} = $SEVERITY->{$eq5};
DEBUG and say STDERR "-- MacroVector EQ5 - Exploitation : $self->{exploitation}";
$self->{security_requirements} = $SEVERITY->{$eq6};
DEBUG and say STDERR "-- MacroVector EQ6 - Security Requirements : $self->{security_requirements}";
return wantarray ? @macro_vector : "$macro_vector";
}
sub exploitability { shift->{exploitability} }
sub complexity { shift->{complexity} }
sub vulnerable_system { shift->{vulnerable_system} }
sub subsequent_system { shift->{subsequent_system} }
sub exploitation { shift->{exploitation} }
sub security_requirements { shift->{security_requirements} }
sub M {
my ($self, $metric) = @_;
my $value = $self->SUPER::M($metric);
# (From table 12)
# This is the default value and is equivalent to Attacked (A) for the
# purposes of the calculation of the score by assuming the worst case.
return 'A' if ($metric eq 'E' && $value eq 'X');
# (From table 13)
# [...] This is the default value. Assigning this value indicates there is
# insufficient information to choose one of the other values. This has the
# same effect as assigning High as the worst case.
return 'H' if ($metric eq 'CR' && $value eq 'X');
return 'H' if ($metric eq 'IR' && $value eq 'X');
return 'H' if ($metric eq 'AR' && $value eq 'X');
return $value;
}
sub calculate_score {
my ($self) = @_;
if (%{$self->metrics}) {
for (@{$self->METRIC_GROUPS->{base}}) {
Carp::croak sprintf('Missing base metric (%s)', $_) unless ($self->metrics->{$_});
}
}
# Set NOT_DEFINED
$self->metrics->{E} //= 'X';
$self->metrics->{CR} //= 'X';
$self->metrics->{IR} //= 'X';
$self->metrics->{AR} //= 'X';
$self->metrics->{MAV} //= 'X';
$self->metrics->{MAC} //= 'X';
$self->metrics->{MAT} //= 'X';
$self->metrics->{MPR} //= 'X';
$self->metrics->{MUI} //= 'X';
$self->metrics->{MVC} //= 'X';
$self->metrics->{MVI} //= 'X';
$self->metrics->{MVA} //= 'X';
$self->metrics->{MSC} //= 'X';
$self->metrics->{MSI} //= 'X';
$self->metrics->{MSA} //= 'X';
$self->metrics->{S} //= 'X';
$self->metrics->{AU} //= 'X';
$self->metrics->{R} //= 'X';
$self->metrics->{V} //= 'X';
$self->metrics->{RE} //= 'X';
$self->metrics->{U} //= 'X';
# The following defines the index of each metric's values.
# It is used when looking for the highest vector part of the
# combinations produced by the MacroVector respective highest vectors.
my $AV_levels = {N => 0.0, A => 0.1, L => 0.2, P => 0.3};
my $PR_levels = {N => 0.0, L => 0.1, H => 0.2};
my $UI_levels = {N => 0.0, P => 0.1, A => 0.2};
my $AC_levels = {L => 0.0, H => 0.1};
my $AT_levels = {N => 0.0, P => 0.1};
my $VC_levels = {H => 0.0, L => 0.1, N => 0.2};
my $VI_levels = {H => 0.0, L => 0.1, N => 0.2};
my $VA_levels = {H => 0.0, L => 0.1, N => 0.2};
my $SC_levels = {H => 0.1, L => 0.2, N => 0.3};
my $SI_levels = {S => 0.0, H => 0.1, L => 0.2, N => 0.3};
my $SA_levels = {S => 0.0, H => 0.1, L => 0.2, N => 0.3};
my $CR_levels = {H => 0.0, M => 0.1, L => 0.2};
my $IR_levels = {H => 0.0, M => 0.1, L => 0.2};
my $AR_levels = {H => 0.0, M => 0.1, L => 0.2};
my $E_levels = {U => 0.2, P => 0.1, A => 0.0};
if ( $self->M('VC') eq 'N'
&& $self->M('VI') eq 'N'
&& $self->M('VA') eq 'N'
&& $self->M('SC') eq 'N'
&& $self->M('SI') eq 'N'
&& $self->M('SA') eq 'N')
{
$self->{scores}->{base} = '0.0';
return 1;
}
my @macro_vector = $self->macro_vector;
my $macro_vector = join '', @macro_vector;
$self->{macro_vector} = $macro_vector;
my ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6) = @macro_vector;
my $value = $CVSS_LOOKUP_GLOBAL->{$macro_vector};
my $eq1_next_lower_macro = join '', ($eq1 + 1, $eq2, $eq3, $eq4, $eq5, $eq6);
my $eq2_next_lower_macro = join '', ($eq1, $eq2 + 1, $eq3, $eq4, $eq5, $eq6);
my $eq3eq6_next_lower_macro = undef;
my $eq3eq6_next_lower_macro_left = undef;
my $eq3eq6_next_lower_macro_right = undef;
if ($eq3 == 1 && $eq6 == 1) {
$eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
}
elsif ($eq3 == 0 && $eq6 == 1) {
$eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
}
elsif ($eq3 == 1 && $eq6 == 0) {
$eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6 + 1);
}
elsif ($eq3 == 0 && $eq6 == 0) {
$eq3eq6_next_lower_macro_left = join '', ($eq1, $eq2, $eq3, $eq4, $eq5, $eq6 + 1);
$eq3eq6_next_lower_macro_right = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6);
}
else {
$eq3eq6_next_lower_macro = join '', ($eq1, $eq2, $eq3 + 1, $eq4, $eq5, $eq6 + 1);
}
my $eq4_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4 + 1, $eq5, $eq6);
my $eq5_next_lower_macro = join '', ($eq1, $eq2, $eq3, $eq4, $eq5 + 1, $eq6);
my $score_eq1_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq1_next_lower_macro} || 'NaN';
my $score_eq2_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq2_next_lower_macro} || 'NaN';
my $score_eq3eq6_next_lower_macro_left = undef;
my $score_eq3eq6_next_lower_macro_right = undef;
my $score_eq3eq6_next_lower_macro = undef;
if ($eq3 == 0 && $eq6 == 0) {
# multiple path take the one with higher score
$score_eq3eq6_next_lower_macro_left = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro_left} || 'NaN';
$score_eq3eq6_next_lower_macro_right = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro_right} || 'NaN';
$score_eq3eq6_next_lower_macro = max($score_eq3eq6_next_lower_macro_left, $score_eq3eq6_next_lower_macro_right);
}
else {
$score_eq3eq6_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq3eq6_next_lower_macro} || 'NaN';
}
my $score_eq4_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq4_next_lower_macro} || 'NaN';
my $score_eq5_next_lower_macro = $CVSS_LOOKUP_GLOBAL->{$eq5_next_lower_macro} || 'NaN';
# b. The severity distance of the to-be scored vector from a
# highest severity vector in the same MacroVector is determined.
my $eq1_maxes = $MAX_COMPOSED->{eq1}->{$eq1};
my $eq2_maxes = $MAX_COMPOSED->{eq2}->{$eq2};
my $eq3_eq6_maxes = $MAX_COMPOSED->{eq3}->{$eq3}->{$eq6};
my $eq4_maxes = $MAX_COMPOSED->{eq4}->{$eq4};
my $eq5_maxes = $MAX_COMPOSED->{eq5}->{$eq5};
# compose them
my @max_vectors = ();
for my $eq1_max (@{$eq1_maxes}) {
for my $eq2_max (@{$eq2_maxes}) {
for my $eq3_eq6_max (@{$eq3_eq6_maxes}) {
for my $eq4_max (@{$eq4_maxes}) {
for my $eq5_max (@{$eq5_maxes}) {
push @max_vectors, join '', ($eq1_max, $eq2_max, $eq3_eq6_max, $eq4_max, $eq5_max);
}
}
}
}
}
DEBUG and say STDERR "-- MaxVectors: @max_vectors";
my $severity_distance_AV = undef;
my $severity_distance_PR = undef;
my $severity_distance_UI = undef;
my $severity_distance_AC = undef;
my $severity_distance_AT = undef;
my $severity_distance_VC = undef;
my $severity_distance_VI = undef;
my $severity_distance_VA = undef;
my $severity_distance_SC = undef;
my $severity_distance_SI = undef;
my $severity_distance_SA = undef;
my $severity_distance_CR = undef;
my $severity_distance_IR = undef;
my $severity_distance_AR = undef;
# Find the max vector to use i.e. one in the combination of all the highests
# that is greater or equal (severity distance) than the to-be scored vector.
DISTANCE: foreach my $max_vector (@max_vectors) {
$severity_distance_AV
= $AV_levels->{$self->M("AV")} - $AV_levels->{$self->extract_value_metric("AV", $max_vector)};
$severity_distance_PR
= $PR_levels->{$self->M("PR")} - $PR_levels->{$self->extract_value_metric("PR", $max_vector)};
$severity_distance_UI
= $UI_levels->{$self->M("UI")} - $UI_levels->{$self->extract_value_metric("UI", $max_vector)};
$severity_distance_AC
= $AC_levels->{$self->M("AC")} - $AC_levels->{$self->extract_value_metric("AC", $max_vector)};
$severity_distance_AT
= $AT_levels->{$self->M("AT")} - $AT_levels->{$self->extract_value_metric("AT", $max_vector)};
$severity_distance_VC
= $VC_levels->{$self->M("VC")} - $VC_levels->{$self->extract_value_metric("VC", $max_vector)};
lib/CVSS/v4.pm view on Meta::CPAN
my @check = (
$severity_distance_AV, $severity_distance_PR, $severity_distance_UI, $severity_distance_AC,
$severity_distance_AT, $severity_distance_VC, $severity_distance_VI, $severity_distance_VA,
$severity_distance_SC, $severity_distance_SI, $severity_distance_SA, $severity_distance_CR,
$severity_distance_IR, $severity_distance_AR
);
# if any is less than zero this is not the right max
foreach (@check) {
next DISTANCE if ($_ < 0);
}
# if multiple maxes exist to reach it it is enough the first one
last;
}
my $step = 0.1;
my $current_severity_distance_eq1 = ($severity_distance_AV + $severity_distance_PR + $severity_distance_UI);
my $current_severity_distance_eq2 = ($severity_distance_AC + $severity_distance_AT);
my $current_severity_distance_eq3eq6
= ( $severity_distance_VC
+ $severity_distance_VI
+ $severity_distance_VA
+ $severity_distance_CR
+ $severity_distance_IR
+ $severity_distance_AR);
my $current_severity_distance_eq4 = ($severity_distance_SC + $severity_distance_SI + $severity_distance_SA);
my $current_severity_distance_eq5 = 0;
# if the next lower macro score do not exist the result is Nan
# Rename to maximal scoring difference (aka MSD)
my $available_distance_eq1 = $value - $score_eq1_next_lower_macro;
my $available_distance_eq2 = $value - $score_eq2_next_lower_macro;
my $available_distance_eq3eq6 = $value - $score_eq3eq6_next_lower_macro;
my $available_distance_eq4 = $value - $score_eq4_next_lower_macro;
my $available_distance_eq5 = $value - $score_eq5_next_lower_macro;
my $percent_to_next_eq1_severity = 0;
my $percent_to_next_eq2_severity = 0;
my $percent_to_next_eq3eq6_severity = 0;
my $percent_to_next_eq4_severity = 0;
my $percent_to_next_eq5_severity = 0;
my $normalized_severity_eq1 = 0;
my $normalized_severity_eq2 = 0;
my $normalized_severity_eq3eq6 = 0;
my $normalized_severity_eq4 = 0;
my $normalized_severity_eq5 = 0;
# multiply by step because distance is pure
my $max_severity_eq1 = $MAX_SEVERITY->{eq1}->{$eq1} * $step;
my $max_severity_eq2 = $MAX_SEVERITY->{eq2}->{$eq2} * $step;
my $max_severity_eq3eq6 = $MAX_SEVERITY->{eq3eq6}->{$eq3}->{$eq6} * $step;
my $max_severity_eq4 = $MAX_SEVERITY->{eq4}->{$eq4} * $step;
# c. The proportion of the distance is determined by dividing
# the severity distance of the to-be-scored vector by the depth
# of the MacroVector.
# d. The maximal scoring difference is multiplied by the proportion of
# distance.
my $n_existing_lower = 0;
if (!isNaN($available_distance_eq1) && $available_distance_eq1 >= 0) {
$n_existing_lower += 1;
$percent_to_next_eq1_severity = ($current_severity_distance_eq1) / $max_severity_eq1;
$normalized_severity_eq1 = $available_distance_eq1 * $percent_to_next_eq1_severity;
}
if (!isNaN($available_distance_eq2) && $available_distance_eq2 >= 0) {
$n_existing_lower += 1;
$percent_to_next_eq2_severity = ($current_severity_distance_eq2) / $max_severity_eq2;
$normalized_severity_eq2 = $available_distance_eq2 * $percent_to_next_eq2_severity;
}
if (!isNaN($available_distance_eq3eq6) && $available_distance_eq3eq6 >= 0) {
$n_existing_lower += 1;
$percent_to_next_eq3eq6_severity = ($current_severity_distance_eq3eq6) / $max_severity_eq3eq6;
$normalized_severity_eq3eq6 = $available_distance_eq3eq6 * $percent_to_next_eq3eq6_severity;
}
if (!isNaN($available_distance_eq4) && $available_distance_eq4 >= 0) {
$n_existing_lower += 1;
$percent_to_next_eq4_severity = ($current_severity_distance_eq4) / $max_severity_eq4;
$normalized_severity_eq4 = $available_distance_eq4 * $percent_to_next_eq4_severity;
}
if (!isNaN($available_distance_eq5) && $available_distance_eq5 >= 0) {
$n_existing_lower += 1;
$percent_to_next_eq5_severity = 0;
$normalized_severity_eq5 = $available_distance_eq5 * $percent_to_next_eq5_severity;
}
my $mean_distance = undef;
# 2. The mean of the above computed proportional distances is computed.
if ($n_existing_lower == 0) {
$mean_distance = 0;
}
else {
# sometimes we need to go up but there is nothing there, or down but there is nothing there so it's a change of 0.
$mean_distance
= ( $normalized_severity_eq1
+ $normalized_severity_eq2
+ $normalized_severity_eq3eq6
+ $normalized_severity_eq4
+ $normalized_severity_eq5)
/ $n_existing_lower;
}
# /
DEBUG and say STDERR "-- Value: $value - MeanDistance: $mean_distance";
# 3. The score of the vector is the score of the MacroVector
# (i.e. the score of the highest severity vector) minus the mean
# distance so computed. This score is rounded to one decimal place.
$value -= $mean_distance;
DEBUG and say STDERR "-- Value $value";
$value = max(0.0, $value);
$value = min(10.0, $value);
my $base_score = sprintf('%.1f', $value);
DEBUG and say STDERR "-- BaseScore: $base_score ($value)";
$self->{scores}->{base} = $base_score;
return 1;
}
sub extract_value_metric {
my ($self, $metric, $vector_string) = @_;
my %metrics = split /[\/:]/, $vector_string;
return $metrics{$metric};
}
sub isNaN { !defined($_[0] <=> 9**9**9) }
sub to_xml {
my ($self) = @_;
my $metric_value_names = $self->METRIC_NAMES;
$self->calculate_score unless ($self->base_score);
my $version = $self->version;
my $metrics = $self->metrics;
my $base_score = $self->base_score;
my $base_severity = $self->base_severity;
my $environmental_score = '';
my $environmental_severity = '';
my $xml_metrics = <<"XML";
<base_metrics>
<attack-vector>$metric_value_names->{AV}->{values}->{$metrics->{AV}}</attack-vector>
<attack-complexity>$metric_value_names->{AC}->{values}->{$metrics->{AC}}</attack-complexity>
<attack-requirements>$metric_value_names->{AT}->{values}->{$metrics->{AT}}</attack-requirements>
<privileges-required>$metric_value_names->{PR}->{values}->{$metrics->{PR}}</privileges-required>
<user-interaction>$metric_value_names->{UI}->{values}->{$metrics->{UI}}</user-interaction>
<confidentiality-of-vulnerable-system>$metric_value_names->{VC}->{values}->{$metrics->{VC}}</confidentiality-of-vulnerable-system>
<integrity-of-vulnerable-system>$metric_value_names->{VI}->{values}->{$metrics->{VI}}</integrity-of-vulnerable-system>
<availability-of-vulnerable-system>$metric_value_names->{VA}->{values}->{$metrics->{VA}}</availability-of-vulnerable-system>
<confidentiality-of-subsequent-system>$metric_value_names->{SC}->{values}->{$metrics->{SC}}</confidentiality-of-subsequent-system>
<integrity-of-subsequent-system>$metric_value_names->{SI}->{values}->{$metrics->{SI}}</integrity-of-subsequent-system>
<availability-of-subsequent-system>$metric_value_names->{SA}->{values}->{$metrics->{SA}}</availability-of-subsequent-system>
<base-score>$base_score</base-score>
<base-severity>$base_severity</base-severity>
</base_metrics>
XML
( run in 0.546 second using v1.01-cache-2.11-cpan-39bf76dae61 )