diff options
Diffstat (limited to 'cvss/v30vector.go')
-rw-r--r-- | cvss/v30vector.go | 162 |
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) +} |