use super::*;
use crate::v4_0::lookup::lookup_global;
use crate::v4_0::lookup::{max_composed, max_severity};
#[derive(Hash, Debug, Clone, PartialEq, Eq)]
pub(crate) enum VectorEq {
Eq1(u8),
Eq2(u8),
Eq3Eq6(u8, u8),
Eq4(u8),
Eq5(u8),
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub(crate) struct MacroVector {
pub eq1: u8,
pub eq2: u8,
pub eq3: u8,
pub eq4: u8,
pub eq5: u8,
pub eq6: u8,
}
impl MacroVector {
pub fn new(eq1: u8, eq2: u8, eq3: u8, eq4: u8, eq5: u8, eq6: u8) -> Self {
MacroVector {
eq1,
eq2,
eq3,
eq4,
eq5,
eq6,
}
}
pub fn as_tuple(&self) -> (u8, u8, u8, u8, u8, u8) {
(self.eq1, self.eq2, self.eq3, self.eq4, self.eq5, self.eq6)
}
pub fn incr_eq1(&self) -> Self {
Self {
eq1: self.eq1 + 1,
..*self
}
}
pub fn incr_eq2(&self) -> Self {
Self {
eq2: self.eq2 + 1,
..*self
}
}
pub fn incr_eq3(&self) -> Self {
Self {
eq3: self.eq3 + 1,
..*self
}
}
pub fn incr_eq4(&self) -> Self {
Self {
eq4: self.eq4 + 1,
..*self
}
}
pub fn incr_eq5(&self) -> Self {
Self {
eq5: self.eq5 + 1,
..*self
}
}
pub fn incr_eq6(&self) -> Self {
Self {
eq6: self.eq6 + 1,
..*self
}
}
}
fn merge_av(base: AttackVector, modified: Option<ModifiedAttackVector>) -> AttackVector {
match modified {
Some(ModifiedAttackVector::NotDefined) | None => base,
Some(ModifiedAttackVector::Network) => AttackVector::Network,
Some(ModifiedAttackVector::Adjacent) => AttackVector::Adjacent,
Some(ModifiedAttackVector::Local) => AttackVector::Local,
Some(ModifiedAttackVector::Physical) => AttackVector::Physical,
}
}
fn merge_ac(
base: AttackComplexity,
modified: Option<ModifiedAttackComplexity>,
) -> AttackComplexity {
match modified {
Some(ModifiedAttackComplexity::NotDefined) | None => base,
Some(ModifiedAttackComplexity::Low) => AttackComplexity::Low,
Some(ModifiedAttackComplexity::High) => AttackComplexity::High,
}
}
fn merge_at(
base: AttackRequirements,
modified: Option<ModifiedAttackRequirements>,
) -> AttackRequirements {
match modified {
Some(ModifiedAttackRequirements::NotDefined) | None => base,
Some(ModifiedAttackRequirements::None) => AttackRequirements::None,
Some(ModifiedAttackRequirements::Present) => AttackRequirements::Present,
}
}
fn merge_pr(
base: PrivilegesRequired,
modified: Option<ModifiedPrivilegesRequired>,
) -> PrivilegesRequired {
match modified {
Some(ModifiedPrivilegesRequired::NotDefined) | None => base,
Some(ModifiedPrivilegesRequired::None) => PrivilegesRequired::None,
Some(ModifiedPrivilegesRequired::Low) => PrivilegesRequired::Low,
Some(ModifiedPrivilegesRequired::High) => PrivilegesRequired::High,
}
}
fn merge_ui(base: UserInteraction, modified: Option<ModifiedUserInteraction>) -> UserInteraction {
match modified {
Some(ModifiedUserInteraction::NotDefined) | None => base,
Some(ModifiedUserInteraction::None) => UserInteraction::None,
Some(ModifiedUserInteraction::Passive) => UserInteraction::Passive,
Some(ModifiedUserInteraction::Active) => UserInteraction::Active,
}
}
fn merge_impact(base: Impact, modified: Option<ModifiedImpact>) -> Impact {
match modified {
Some(ModifiedImpact::NotDefined) | None => base,
Some(ModifiedImpact::High) => Impact::High,
Some(ModifiedImpact::Low) => Impact::Low,
Some(ModifiedImpact::None) => Impact::None,
}
}
fn merge_subsequent_impact(
base: SubsequentImpact,
modified: Option<ModifiedSubsequentImpact>,
) -> SubsequentImpact {
match modified {
Some(ModifiedSubsequentImpact::NotDefined) | None => base,
Some(ModifiedSubsequentImpact::Safety) => SubsequentImpact::Safety,
Some(ModifiedSubsequentImpact::High) => SubsequentImpact::High,
Some(ModifiedSubsequentImpact::Low) => SubsequentImpact::Low,
Some(ModifiedSubsequentImpact::Negligible) => SubsequentImpact::None,
}
}
fn merge_exploit_maturity(e: Option<ExploitMaturity>) -> ExploitMaturity {
match e {
Some(ExploitMaturity::NotDefined) | None => ExploitMaturity::Attacked,
Some(other) => other,
}
}
fn merge_requirement(r: Option<Requirement>) -> Requirement {
match r {
Some(Requirement::NotDefined) | None => Requirement::High,
Some(other) => other,
}
}
fn eq1(av: AttackVector, pr: PrivilegesRequired, ui: UserInteraction) -> u8 {
if av == AttackVector::Network && pr == PrivilegesRequired::None && ui == UserInteraction::None
{
0
} else if (av == AttackVector::Network
|| pr == PrivilegesRequired::None
|| ui == UserInteraction::None)
&& av != AttackVector::Physical
{
1
} else {
2
}
}
fn eq2(ac: AttackComplexity, at: AttackRequirements) -> u8 {
if ac == AttackComplexity::Low && at == AttackRequirements::None {
0
} else {
1
}
}
fn eq3(vc: Impact, vi: Impact, va: Impact) -> u8 {
if vc == Impact::High && vi == Impact::High {
0
} else if vc == Impact::High || vi == Impact::High || va == Impact::High {
1
} else {
2
}
}
fn eq4(sc: SubsequentImpact, si: SubsequentImpact, sa: SubsequentImpact) -> u8 {
if sc == SubsequentImpact::Safety
|| si == SubsequentImpact::Safety
|| sa == SubsequentImpact::Safety
{
0
} else if sc == SubsequentImpact::High
|| si == SubsequentImpact::High
|| sa == SubsequentImpact::High
{
1
} else {
2
}
}
fn eq5(e: ExploitMaturity) -> u8 {
match e {
ExploitMaturity::Attacked => 0,
ExploitMaturity::ProofOfConcept => 1,
ExploitMaturity::Unreported | ExploitMaturity::NotDefined => 2,
}
}
fn eq6(
cr: Requirement,
vc: Impact,
ir: Requirement,
vi: Impact,
ar: Requirement,
va: Impact,
) -> u8 {
if (cr == Requirement::High && vc == Impact::High)
|| (ir == Requirement::High && vi == Impact::High)
|| (ar == Requirement::High && va == Impact::High)
{
0
} else {
1
}
}
pub fn calculate_score_internal(cvss: &CvssV4, include_threat_metrics: bool) -> Option<f64> {
let base_av = cvss.attack_vector.as_ref()?;
let base_ac = cvss.attack_complexity.as_ref()?;
let base_at = cvss.attack_requirements.as_ref()?;
let base_pr = cvss.privileges_required.as_ref()?;
let base_ui = cvss.user_interaction.as_ref()?;
let base_vc = cvss.vuln_confidentiality_impact.as_ref()?;
let base_vi = cvss.vuln_integrity_impact.as_ref()?;
let base_va = cvss.vuln_availability_impact.as_ref()?;
let base_sc = cvss.sub_confidentiality_impact.as_ref()?;
let base_si = cvss.sub_integrity_impact.as_ref()?;
let base_sa = cvss.sub_availability_impact.as_ref()?;
let av = merge_av(*base_av, cvss.modified_attack_vector);
let ac = merge_ac(*base_ac, cvss.modified_attack_complexity);
let at = merge_at(*base_at, cvss.modified_attack_requirements);
let pr = merge_pr(*base_pr, cvss.modified_privileges_required);
let ui = merge_ui(*base_ui, cvss.modified_user_interaction);
let vc = merge_impact(*base_vc, cvss.modified_vuln_confidentiality_impact);
let vi = merge_impact(*base_vi, cvss.modified_vuln_integrity_impact);
let va = merge_impact(*base_va, cvss.modified_vuln_availability_impact);
let sc = merge_subsequent_impact(*base_sc, cvss.modified_sub_confidentiality_impact);
let si = merge_subsequent_impact(*base_si, cvss.modified_sub_integrity_impact);
let sa = merge_subsequent_impact(*base_sa, cvss.modified_sub_availability_impact);
let e = if include_threat_metrics {
merge_exploit_maturity(cvss.exploit_maturity)
} else {
ExploitMaturity::Attacked };
let cr = merge_requirement(cvss.confidentiality_requirement);
let ir = merge_requirement(cvss.integrity_requirement);
let ar = merge_requirement(cvss.availability_requirement);
if vc == Impact::None
&& vi == Impact::None
&& va == Impact::None
&& sc == SubsequentImpact::None
&& si == SubsequentImpact::None
&& sa == SubsequentImpact::None
{
return Some(0.0);
}
let macro_vector = MacroVector::new(
eq1(av, pr, ui),
eq2(ac, at),
eq3(vc, vi, va),
eq4(sc, si, sa),
eq5(e),
eq6(cr, vc, ir, vi, ar, va),
);
let value = lookup_global(¯o_vector)?;
let score_eq1_next_lower = lookup_global(¯o_vector.incr_eq1());
let score_eq2_next_lower = lookup_global(¯o_vector.incr_eq2());
let score_eq3eq6_next_lower =
if (macro_vector.eq3 == 0 || macro_vector.eq3 == 1) && macro_vector.eq6 == 1 {
lookup_global(¯o_vector.incr_eq3())
} else if macro_vector.eq3 == 1 && macro_vector.eq6 == 0 {
lookup_global(¯o_vector.incr_eq6())
} else if macro_vector.eq3 == 0 && macro_vector.eq6 == 0 {
let left = lookup_global(¯o_vector.incr_eq6());
let right = lookup_global(¯o_vector.incr_eq3());
if left > right {
left
} else {
right
}
} else {
lookup_global(¯o_vector.incr_eq3())
};
let score_eq4_next_lower = lookup_global(¯o_vector.incr_eq4());
let score_eq5_next_lower = lookup_global(¯o_vector.incr_eq5());
let eq1_maxes = max_composed(VectorEq::Eq1(macro_vector.eq1));
let eq2_maxes = max_composed(VectorEq::Eq2(macro_vector.eq2));
let eq3_eq6_maxes = max_composed(VectorEq::Eq3Eq6(macro_vector.eq3, macro_vector.eq6));
let eq4_maxes = max_composed(VectorEq::Eq4(macro_vector.eq4));
let eq5_maxes = max_composed(VectorEq::Eq5(macro_vector.eq5));
let mut max_vectors = Vec::new();
for eq1_max in &eq1_maxes {
for eq2_max in &eq2_maxes {
for eq3_eq6_max in &eq3_eq6_maxes {
for eq4_max in &eq4_maxes {
for eq5_max in &eq5_maxes {
max_vectors.push(format!(
"{}{}{}{}{}",
eq1_max, eq2_max, eq3_eq6_max, eq4_max, eq5_max
));
}
}
}
}
}
let mut severity_distance_av = 0.0;
let mut severity_distance_pr = 0.0;
let mut severity_distance_ui = 0.0;
let mut severity_distance_ac = 0.0;
let mut severity_distance_at = 0.0;
let mut severity_distance_vc = 0.0;
let mut severity_distance_vi = 0.0;
let mut severity_distance_va = 0.0;
let mut severity_distance_sc = 0.0;
let mut severity_distance_si = 0.0;
let mut severity_distance_sa = 0.0;
let mut severity_distance_cr = 0.0;
let mut severity_distance_ir = 0.0;
let mut severity_distance_ar = 0.0;
for max_vector_str in &max_vectors {
let max_vector_metrics = parse_max_vector(max_vector_str)?;
severity_distance_av = av.level() - max_vector_metrics.av.level();
severity_distance_pr = pr.level() - max_vector_metrics.pr.level();
severity_distance_ui = ui.level() - max_vector_metrics.ui.level();
severity_distance_ac = ac.level() - max_vector_metrics.ac.level();
severity_distance_at = at.level() - max_vector_metrics.at.level();
severity_distance_vc = vc.level() - max_vector_metrics.vc.level();
severity_distance_vi = vi.level() - max_vector_metrics.vi.level();
severity_distance_va = va.level() - max_vector_metrics.va.level();
severity_distance_sc = sc.level() - max_vector_metrics.sc.level();
severity_distance_si = si.level() - max_vector_metrics.si.level();
severity_distance_sa = sa.level() - max_vector_metrics.sa.level();
severity_distance_cr = cr.level() - max_vector_metrics.cr.level();
severity_distance_ir = ir.level() - max_vector_metrics.ir.level();
severity_distance_ar = ar.level() - max_vector_metrics.ar.level();
if severity_distance_av < 0.0
|| severity_distance_pr < 0.0
|| severity_distance_ui < 0.0
|| severity_distance_ac < 0.0
|| severity_distance_at < 0.0
|| severity_distance_vc < 0.0
|| severity_distance_vi < 0.0
|| severity_distance_va < 0.0
|| severity_distance_sc < 0.0
|| severity_distance_si < 0.0
|| severity_distance_sa < 0.0
|| severity_distance_cr < 0.0
|| severity_distance_ir < 0.0
|| severity_distance_ar < 0.0
{
continue;
} else {
break;
}
}
let current_severity_distance_eq1 =
severity_distance_av + severity_distance_pr + severity_distance_ui;
let current_severity_distance_eq2 = severity_distance_ac + severity_distance_at;
let current_severity_distance_eq3eq6 = severity_distance_vc
+ severity_distance_vi
+ severity_distance_va
+ severity_distance_cr
+ severity_distance_ir
+ severity_distance_ar;
let current_severity_distance_eq4 =
severity_distance_sc + severity_distance_si + severity_distance_sa;
let step = 0.1;
let available_distance_eq1 = score_eq1_next_lower.map(|v| value - v);
let available_distance_eq2 = score_eq2_next_lower.map(|v| value - v);
let available_distance_eq3eq6 = score_eq3eq6_next_lower.map(|v| value - v);
let available_distance_eq4 = score_eq4_next_lower.map(|v| value - v);
let available_distance_eq5 = score_eq5_next_lower.map(|v| value - v);
let mut n_existing_lower = 0;
let max_severity_eq1 = max_severity(VectorEq::Eq1(macro_vector.eq1)) as f64 * step;
let max_severity_eq2 = max_severity(VectorEq::Eq2(macro_vector.eq2)) as f64 * step;
let max_severity_eq3eq6 =
max_severity(VectorEq::Eq3Eq6(macro_vector.eq3, macro_vector.eq6)) as f64 * step;
let max_severity_eq4 = max_severity(VectorEq::Eq4(macro_vector.eq4)) as f64 * step;
let normalized_severity_eq1 = if let Some(a) = available_distance_eq1 {
n_existing_lower += 1;
let percent = current_severity_distance_eq1 / max_severity_eq1;
a * percent
} else {
0.0
};
let normalized_severity_eq2 = if let Some(a) = available_distance_eq2 {
n_existing_lower += 1;
let percent = current_severity_distance_eq2 / max_severity_eq2;
a * percent
} else {
0.0
};
let normalized_severity_eq3eq6 = if let Some(a) = available_distance_eq3eq6 {
n_existing_lower += 1;
let percent = current_severity_distance_eq3eq6 / max_severity_eq3eq6;
a * percent
} else {
0.0
};
let normalized_severity_eq4 = if let Some(a) = available_distance_eq4 {
n_existing_lower += 1;
let percent = current_severity_distance_eq4 / max_severity_eq4;
a * percent
} else {
0.0
};
let normalized_severity_eq5 = if available_distance_eq5.is_some() {
n_existing_lower += 1;
0.0
} else {
0.0
};
let mean_distance = if n_existing_lower == 0 {
0.0
} else {
(normalized_severity_eq1
+ normalized_severity_eq2
+ normalized_severity_eq3eq6
+ normalized_severity_eq4
+ normalized_severity_eq5)
/ n_existing_lower as f64
};
Some(value - mean_distance)
}
struct MaxVectorMetrics {
av: AttackVector,
ac: AttackComplexity,
at: AttackRequirements,
pr: PrivilegesRequired,
ui: UserInteraction,
vc: Impact,
vi: Impact,
va: Impact,
sc: SubsequentImpact,
si: SubsequentImpact,
sa: SubsequentImpact,
cr: Requirement,
ir: Requirement,
ar: Requirement,
}
fn parse_max_vector(s: &str) -> Option<MaxVectorMetrics> {
let mut av = AttackVector::Network;
let mut ac = AttackComplexity::Low;
let mut at = AttackRequirements::None;
let mut pr = PrivilegesRequired::None;
let mut ui = UserInteraction::None;
let mut vc = Impact::None;
let mut vi = Impact::None;
let mut va = Impact::None;
let mut sc = SubsequentImpact::None;
let mut si = SubsequentImpact::None;
let mut sa = SubsequentImpact::None;
let mut cr = Requirement::Medium;
let mut ir = Requirement::Medium;
let mut ar = Requirement::Medium;
let s = s.trim_end_matches('/');
for component in s.split('/') {
let mut parts = component.split(':');
let key = parts.next()?.to_ascii_uppercase();
let value = parts.next()?.to_ascii_uppercase();
match key.as_str() {
"AV" => av = value.parse().ok()?,
"AC" => ac = value.parse().ok()?,
"AT" => at = value.parse().ok()?,
"PR" => pr = value.parse().ok()?,
"UI" => ui = value.parse().ok()?,
"VC" => vc = value.parse().ok()?,
"VI" => vi = value.parse().ok()?,
"VA" => va = value.parse().ok()?,
"SC" => sc = value.parse().ok()?,
"SI" => si = value.parse().ok()?,
"SA" => sa = value.parse().ok()?,
"CR" => cr = value.parse().ok()?,
"IR" => ir = value.parse().ok()?,
"AR" => ar = value.parse().ok()?,
_ => {}
}
}
Some(MaxVectorMetrics {
av,
ac,
at,
pr,
ui,
vc,
vi,
va,
sc,
si,
sa,
cr,
ir,
ar,
})
}
pub fn calculate_score(cvss: &CvssV4) -> Option<f64> {
calculate_score_internal(cvss, true)
}
pub fn calculate_base_score(cvss: &CvssV4) -> Option<f64> {
calculate_score_internal(cvss, false)
}