aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cvss/v30vector.go162
1 files changed, 162 insertions, 0 deletions
diff --git a/cvss/v30vector.go b/cvss/v30vector.go
index 5ef1ae8..6598c8e 100644
--- a/cvss/v30vector.go
+++ b/cvss/v30vector.go
@@ -2,6 +2,7 @@ package cvss
import (
// "encoding/json"
+ "math"
"regexp"
"strings"
)
@@ -117,3 +118,164 @@ func isV30VectorString(s string) bool {
(s[:len(v30Prefix)] == v30Prefix) &&
v30VecRe.MatchString(s)
}
+
+// Return numerical scores for this vector.
+//
+// Reference implementation: https://www.first.org/cvss/calculator/cvsscalc30.js
+func (v v30Vector) Scores() (Scores, error) {
+ scopeChanged := false
+ modScopeChanged := false
+
+ // default metrics map
+ keys := map[Key]v3Metric {
+ v3ExploitCodeMaturity: v3ENotDefined,
+ v3RemediationLevel: v3RLNotDefined,
+ v3ReportConfidence: v3RCNotDefined,
+ v3ConfidentialityRequirement: v3CRNotDefined,
+ v3IntegrityRequirement: v3IRNotDefined,
+ v3AvailabilityRequirement: v3ARNotDefined,
+ v3ModifiedAttackVector: v3MAVNotDefined,
+ v3ModifiedAttackComplexity: v3MACNotDefined,
+ v3ModifiedPrivilegesRequired: v3MPRNotDefined,
+ v3ModifiedUserInteraction: v3MUINotDefined,
+ v3ModifiedConfidentiality: v3MCNotDefined,
+ v3ModifiedIntegrity: v3MINotDefined,
+ v3ModifiedAvailability: v3MANotDefined,
+ v3ModifiedScope: v3MSNotDefined,
+ }
+
+ // populate metrics map
+ for _, m := range([]v3Metric(v)) {
+ keys[m.Key()] = m
+
+ switch m {
+ case v3SUnchanged: // S:U
+ scopeChanged = false
+ case v3SChanged: // S:C
+ scopeChanged = true
+
+ case v3MSUnchanged: // MS:U
+ modScopeChanged = false
+ case v3MSChanged: // MS:U
+ modScopeChanged = true
+ }
+ }
+
+ attackVector := v3MetricCoefs[keys[v3AttackVector]]
+ attackComplexity := v3MetricCoefs[keys[v3AttackComplexity]]
+ userInteraction := v3MetricCoefs[keys[v3UserInteraction]]
+ conf := v3MetricCoefs[keys[v3Confidentiality]]
+ integ := v3MetricCoefs[keys[v3Integrity]]
+ avail := v3MetricCoefs[keys[v3Availability]]
+ ecm := v3MetricCoefs[keys[v3ExploitCodeMaturity]]
+ remediationLevel := v3MetricCoefs[keys[v3RemediationLevel]]
+ reportConfidence := v3MetricCoefs[keys[v3ReportConfidence]]
+ confReq := v3MetricCoefs[keys[v3ConfidentialityRequirement]]
+ availReq := v3MetricCoefs[keys[v3AvailabilityRequirement]]
+ integReq := v3MetricCoefs[keys[v3IntegrityRequirement]]
+
+ // adjust privsRequired based on scopeChanged
+ // (CVSS v3.0 spec, section 8.4, table 16)
+ privsRequired := v3PrivReqCoefs[keys[v3PrivilegesRequired]][scopeChanged]
+
+ modAttackVector := getV3ModCoef(keys, v3ModifiedAttackVector)
+ modAttackComplexity := getV3ModCoef(keys, v3ModifiedAttackComplexity)
+ modUserInteraction := getV3ModCoef(keys, v3ModifiedUserInteraction)
+ modConf := getV3ModCoef(keys, v3ModifiedConfidentiality)
+ modInteg := getV3ModCoef(keys, v3ModifiedIntegrity)
+ modAvail := getV3ModCoef(keys, v3ModifiedAvailability)
+
+ if v, _ := keys[v3ModifiedScope]; v == v3MSNotDefined {
+ // default to base scopeChanged
+ modScopeChanged = scopeChanged
+ }
+
+ // adjust modPrivsRequired based on scopeChanged
+ // (CVSS v3.0 spec, section 8.4, table 16)
+ modPrivsRequired := 0.0
+ {
+ mpr, _ := keys[v3ModifiedPrivilegesRequired]
+ pr, _ := keys[v3PrivilegesRequired]
+ ms, _ := keys[v3ModifiedScope]
+
+ if mpr != v3MPRNotDefined && ms != v3MSNotDefined {
+ modPrivsRequired = v3PrivReqCoefs[mpr][ms == v3MSChanged]
+ } else if mpr != v3MPRNotDefined && ms == v3MSNotDefined {
+ modPrivsRequired = v3PrivReqCoefs[mpr][scopeChanged]
+ } else if mpr == v3MPRNotDefined && ms != v3MSNotDefined {
+ modPrivsRequired = v3PrivReqCoefs[pr][ms == v3MSChanged]
+ } else {
+ // default to base privsRequired
+ // modPrivsRequired = privsRequired
+ modPrivsRequired = v3PrivReqCoefs[pr][scopeChanged]
+ }
+ }
+
+ // calculate base score (CVSS v3.0 spec, section 8.1)
+ baseScore := 0.0
+ {
+ // calculate impact sub-score (cvss v3.0 spec, section 8.1)
+ iss := 1.0 - ((1.0 - conf) * (1.0 - integ) * (1.0 - avail))
+
+ // calculate impact
+ impact := 0.0
+ if scopeChanged {
+ impact = 7.52 * (iss - 0.029) - 3.25 * math.Pow(iss - 0.02, 15)
+ } else {
+ impact = 6.42 * iss
+ }
+
+ // exploitability
+ expl := 8.22 * attackVector * attackComplexity * privsRequired * userInteraction
+
+ if impact <= 0.0 {
+ baseScore = 0
+ } else if scopeChanged {
+ baseScore = roundup(math.Min(1.08 * (impact + expl), 10.0))
+ } else {
+ baseScore = roundup(math.Min(impact + expl, 10.0))
+ }
+ }
+
+ // temporal score (CVSS v3.0 spec, section 8.2)
+ tempScore := 0.0
+ if hasV3TemporalScoreKeys(keys) {
+ tempScore = roundup(baseScore * ecm * remediationLevel * reportConfidence)
+ }
+
+ // environmental score (CVSS v3.0 spec, section 8.3)
+ envScore := 0.0
+ if hasV3EnvScoreKeys(keys) {
+ // modified impact sub score (ISC_m)
+ miss := math.Min(
+ 1 - (1 - confReq * modConf) * (1 - integReq * modInteg) * (1 - availReq * modAvail),
+ 0.915,
+ )
+
+ // modified impact
+ // NOTE: exponent differs for CVSS v3.0 (13) and CVSS v3.1 (15)
+ impact := 0.0
+ if modScopeChanged {
+ impact = 7.52 * (miss - 0.029) - 3.25 * math.Pow(miss - 0.02, 15)
+ } else {
+ impact = 6.42 * miss
+ }
+
+ // modified exploitability sub score
+ expl := 8.22 * modAttackVector * modAttackComplexity * modPrivsRequired * modUserInteraction
+
+ // calculate env score
+ if impact <= 0.0 {
+ envScore = 0.0
+ } else if modScopeChanged {
+ // Roundup(Roundup[Minimum(1.08 × [ModifiedImpact + ModifiedExploitability], 10)] × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
+ envScore = roundup(roundup(math.Min(1.08 * (impact + expl), 10.0)) * ecm * remediationLevel * reportConfidence)
+ } else {
+ // Roundup(Roundup[Minimum([ModifiedImpact + ModifiedExploitability], 10) ] × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
+ envScore = roundup(roundup(math.Min((impact + expl), 10.0)) * ecm * remediationLevel * reportConfidence)
+ }
+ }
+
+ // build and return new scores
+ return NewScores(baseScore, tempScore, envScore)
+}