diff --git a/pom.xml b/pom.xml
index 5a50839..8fa6219 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
UTF-8
false
- 2.7.10
+ 2.7.11
3.6.3
3.3.0
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