diff --git a/src/main/java/us/springett/cvss/CvssV3.java b/src/main/java/us/springett/cvss/CvssV3.java index f4a7f6b..a887c1c 100644 --- a/src/main/java/us/springett/cvss/CvssV3.java +++ b/src/main/java/us/springett/cvss/CvssV3.java @@ -333,6 +333,10 @@ private double roundUp1(double d) { } protected double roundNearestTenth(double d) { + if (d < 0) { + return 0; + } + return Math.round(d * 10.0) / 10.0; } diff --git a/src/main/java/us/springett/cvss/CvssV3_1.java b/src/main/java/us/springett/cvss/CvssV3_1.java index e16ed48..da85dd8 100644 --- a/src/main/java/us/springett/cvss/CvssV3_1.java +++ b/src/main/java/us/springett/cvss/CvssV3_1.java @@ -130,79 +130,78 @@ public CvssV3_1 modifiedAvailabilityImpact(ModifiedCIA ma) { * {@inheritDoc} */ public Score calculateScore() { + // PrivilegesRequired (PR) depends on the value of Scope (S) final double prWeight = (Scope.UNCHANGED == s) ? pr.weight : pr.scopeChangedWeight; - final double mprWeight; - final double exploitabilitySubScore = exploitabilityCoefficient * av.weight * ac.weight * prWeight * ui.weight; - final double modifiedExploitabilitySubScore; - final double impactSubScoreMultiplier = 1 - ((1 - c.weight) * (1 - i.weight) * (1 - a.weight)); - final double modifiedImpactSubScoreMultiplier = Math.min(1 - ((1 - cr.weight * mc.weight) * (1 - ir.weight * mi.weight) * (1 - ar.weight * ma.weight)), 0.915); + // For metrics that are modified versions of Base Score metrics, + // use the value of the Base Score metric if the modified version value is "X" ("not defined"). + final double mavWeight = (mav == ModifiedAttackVector.NOT_DEFINED) ? av.weight : mav.weight; + final double macWeight = (mac == ModifiedAttackComplexity.NOT_DEFINED) ? ac.weight : mac.weight; + final double muiWeight = (mui == ModifiedUserInteraction.NOT_DEFINED) ? ui.weight : mui.weight; + final double mcWeight = (mc == ModifiedCIA.NOT_DEFINED) ? c.weight : mc.weight; + final double miWeight = (mi == ModifiedCIA.NOT_DEFINED) ? i.weight : mi.weight; + final double maWeight = (ma == ModifiedCIA.NOT_DEFINED) ? a.weight : ma.weight; + final double msWeight = (ms == ModifiedScope.NOT_DEFINED) ? s.weight : ms.weight; + + // ModifiedPrivilegesRequired (MPR) depends on the value of Modified Scope (MS), + // or Scope (S) if MS is "X" (not defined). + final double mprWeight; + if (ms == ModifiedScope.UNCHANGED || (ms == ModifiedScope.NOT_DEFINED && s == Scope.UNCHANGED)) { + mprWeight = (mpr == ModifiedPrivilegesRequired.NOT_DEFINED) ? pr.weight : mpr.weight; + } else { + mprWeight = (mpr == ModifiedPrivilegesRequired.NOT_DEFINED) ? pr.scopeChangedWeight : mpr.scopeChangedWeight; + } - final double baseScore; - double impactSubScore; - final double temporalScore; - final double environmentalScore; - double modifiedImpactSubScore; + final double impactSubScore = (1 - ((1 - c.weight) * (1 - i.weight) * (1 - a.weight))); + final double exploitability = exploitabilityCoefficient * av.weight * ac.weight * prWeight * ui.weight; - if (Scope.UNCHANGED == s) { - impactSubScore = s.weight * impactSubScoreMultiplier; + final double impact; + if (s == Scope.UNCHANGED) { + impact = s.weight * impactSubScore; } else { - impactSubScore = s.weight * (impactSubScoreMultiplier - 0.029) - 3.25 * Math.pow(impactSubScoreMultiplier - 0.02, 15); + impact = s.weight * (impactSubScore - 0.029) - 3.25 * Math.pow((impactSubScore - 0.02), 15); } - if (impactSubScore <= 0) { + final double baseScore; + if (impact <= 0) { baseScore = 0; - impactSubScore = 0; } else { - if (Scope.UNCHANGED == s) { - baseScore = roundUp1(Math.min((impactSubScore + exploitabilitySubScore), 10)); + if (s == Scope.UNCHANGED) { + baseScore = roundUp1(Math.min((exploitability + impact), 10)); } else { - baseScore = roundUp1(Math.min((impactSubScore + exploitabilitySubScore) * scopeCoefficient, 10)); + baseScore = roundUp1(Math.min((scopeCoefficient * (exploitability + impact)), 10)); } } - temporalScore = roundUp1(baseScore * e.weight * rl.weight * rc.weight); + final double temporalScore = roundUp1(baseScore * e.weight * rl.weight * rc.weight); - boolean mprNotDefined = mpr == ModifiedPrivilegesRequired.NOT_DEFINED; - if (ModifiedScope.UNCHANGED == ms) { - mprWeight = mprNotDefined ? pr.weight : mpr.weight; - modifiedImpactSubScore = ms.weight * modifiedImpactSubScoreMultiplier; - } else if (ModifiedScope.CHANGED == ms) { - mprWeight = mprNotDefined ? pr.scopeChangedWeight : mpr.scopeChangedWeight; - modifiedImpactSubScore = ms.weight * (modifiedImpactSubScoreMultiplier - 0.029) - 3.25 * Math.pow((modifiedImpactSubScoreMultiplier * 0.9731 - 0.02), 13); + final double modifiedImpactSubScore = Math.min(1 - ((1 - mcWeight * cr.weight) * (1 - miWeight * ir.weight) * (1 - maWeight * ar.weight)), 0.915); + final double modifiedExploitability = exploitabilityCoefficient * mavWeight * macWeight * mprWeight * muiWeight; + + final double modifiedImpact; + if (ms == ModifiedScope.UNCHANGED || (ms == ModifiedScope.NOT_DEFINED && s == Scope.UNCHANGED)) { + modifiedImpact = msWeight * modifiedImpactSubScore; } else { - if (Scope.UNCHANGED == s){ - mprWeight = mprNotDefined ? pr.weight : mpr.weight; - modifiedImpactSubScore = s.weight * modifiedImpactSubScoreMultiplier; - } else { - mprWeight = mprNotDefined ? pr.scopeChangedWeight : mpr.scopeChangedWeight; - modifiedImpactSubScore = s.weight * (modifiedImpactSubScoreMultiplier - 0.029) - 3.25 * Math.pow((modifiedImpactSubScoreMultiplier * 0.9731 - 0.02), 13); - } -// mprWeight = 0; -// modifiedImpactSubScore = 0; + modifiedImpact = msWeight * (modifiedImpactSubScore - 0.029) - 3.25 * Math.pow((modifiedImpactSubScore * 0.9731 - 0.02), 13); } - double mavWeight = mav == ModifiedAttackVector.NOT_DEFINED ? av.weight : mav.weight; - double macWeight = mac == ModifiedAttackComplexity.NOT_DEFINED ? ac.weight : mac.weight; - double muiWeight = mui == ModifiedUserInteraction.NOT_DEFINED ? ui.weight : mui.weight; - modifiedExploitabilitySubScore = exploitabilityCoefficient * mavWeight * macWeight * mprWeight * muiWeight; - - if (modifiedImpactSubScore <= 0) { - environmentalScore = 0; - modifiedImpactSubScore = 0; + final double envScore; + if (modifiedImpact <= 0) { + envScore = 0; + } else if (ms == ModifiedScope.UNCHANGED || (ms == ModifiedScope.NOT_DEFINED && s == Scope.UNCHANGED)) { + envScore = roundUp1(roundUp1(Math.min((modifiedImpact + modifiedExploitability), 10)) * e.weight * rl.weight * rc.weight); } else { - if (ModifiedScope.UNCHANGED == ms || (ModifiedScope.NOT_DEFINED == ms && Scope.UNCHANGED == s)) { - environmentalScore = roundUp1(roundUp1(Math.min((modifiedImpactSubScore + modifiedExploitabilitySubScore), 10)) * e.weight * rl.weight * rc.weight); - } else if (ModifiedScope.CHANGED == ms || (ModifiedScope.NOT_DEFINED == ms && Scope.CHANGED == s)) { - environmentalScore = roundUp1(roundUp1(Math.min(1.08 * (modifiedImpactSubScore + modifiedExploitabilitySubScore), 10)) * e.weight * rl.weight * rc.weight); - } else { - // throw new RuntimeException("This should never happen"); - // This should never happen - environmentalScore = 0; - } + envScore = roundUp1(roundUp1(Math.min(scopeCoefficient * (modifiedImpact + modifiedExploitability), 10)) * e.weight * rl.weight * rc.weight); } - return new Score(baseScore, roundNearestTenth(impactSubScore), roundNearestTenth(exploitabilitySubScore), temporalScore, environmentalScore, roundNearestTenth(modifiedImpactSubScore)); + return new Score( + roundNearestTenth(baseScore), + roundNearestTenth(impact), + roundNearestTenth(exploitability), + roundNearestTenth(temporalScore), + roundNearestTenth(envScore), + roundNearestTenth(modifiedImpact) + ); } private double roundUp1(double d) { diff --git a/src/test/java/us/springett/cvss/CvssV3_1Test.java b/src/test/java/us/springett/cvss/CvssV3_1Test.java index 1c92e00..d24e4ce 100644 --- a/src/test/java/us/springett/cvss/CvssV3_1Test.java +++ b/src/test/java/us/springett/cvss/CvssV3_1Test.java @@ -1014,6 +1014,29 @@ public void modifiedAvailabilityImpactTest() { assertEquals(CvssV3_1.ModifiedCIA.HIGH, cvssV3_1.getModifiedAvailabilityImpact()); } + @Test + public void environmentalMetricsTest() { + Cvss cvss; + + // All environmental metrics are provided. + cvss = Cvss.fromVector("CVSS:3.1/AV:A/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:L/E:U/RL:O/RC:U/CR:L/IR:L/AR:L/MAV:N/MAC:L/MPR:N/MUI:N/MS:U/MC:N/MI:N/MA:L"); + assertEquals(5.9, cvss.calculateScore().getBaseScore(), 0); + assertEquals(4.7, cvss.calculateScore().getTemporalScore(), 0); + assertEquals(3.7, cvss.calculateScore().getEnvironmentalScore(), 0); + + // Environmental metrics are explicitly not defined. + cvss = Cvss.fromVector("CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:L/E:U/RL:O/RC:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MPR:X/MUI:X/MS:X/MC:X/MI:X/MA:X"); + assertEquals(5.8, cvss.calculateScore().getBaseScore(), 0); + assertEquals(4.7, cvss.calculateScore().getTemporalScore(), 0); + assertEquals(4.7, cvss.calculateScore().getEnvironmentalScore(), 0); + + // Environmental metrics are partially provided (only CR, IR, and MAV). + cvss = Cvss.fromVector("CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:L/E:X/RL:X/RC:X/CR:M/IR:M/AR:X/MAV:A/MAC:X/MPR:X/MUI:X/MS:X/MC:X/MI:X/MA:X"); + assertEquals(6.2, cvss.calculateScore().getBaseScore(), 0); + assertEquals(6.2, cvss.calculateScore().getTemporalScore(), 0); + assertEquals(5.9, cvss.calculateScore().getEnvironmentalScore(), 0); + } + @Test public void testRegexPattern() { // Without temporal vector elements