package cvss import ( "math" "regexp" "strings" ) // CVSS v3.1 prefix var v31Prefix = "CVSS:3.1/" // CVSS 3.1 vector. type v31Vector []v3Metric // Convert vector to string func (v v31Vector) String() string { // convert to slice of metrics metrics := []v3Metric(v) // build vector r := make([]string, len(metrics)) for i, m := range(metrics) { r[i] = m.String() } // build and return string return v31Prefix + strings.Join(r, "/") } // Return CVSS version. func (v31Vector) Version() Version { return V31 } // Return metrics in this vector. func (v v31Vector) Metrics() []Metric { // build slice of metrics r := make([]Metric, len(v)) for i, m := range(v) { r[i] = m } // return result return r } // create CVSS 3.1 vector from string. func newV31Vector(s string) (v31Vector, error) { // strip version prefix, split into metric strings strs := strings.Split(s[len(v31Prefix):], "/") r := make([]v3Metric, len(strs)) // build results for i, ms := range(strs) { // get metric from string m, err := getV3Metric(V31, ms) if err != nil { return nil, err } // add to results r[i] = m } // return result return v31Vector(r), nil } // // Unmarshal CVSS 3.1 vector from JSON string. // func (me *v31Vector) UnmarshalJSON(b []byte) error { // // decode string, check for error // var s string // if err := json.Unmarshal(b, &s); err != nil { // return err // } // // // parse vector, check for error // r, err := newV31Vector(s) // if err != nil { // return err // } // // // save result, return success // *me = r // return nil // } var v31VecRe = regexp.MustCompile( "\\ACVSS:3\\.1(?:/(?:" + strings.Join([]string { "(?:AV:[NALP])", "(?:AC:[LH])", "(?:PR:[NLH])", "(?:UI:[NR])", "(?:S:[UC])", "(?:C:[HLN])", "(?:I:[HLN])", "(?:A:[HLN])", "(?:E:[XHFPU])", "(?:RL:[XUWTO])", "(?:RC:[XCRU])", "(?:CR:[XHML])", "(?:IR:[XHML])", "(?:AR:[XHML])", "(?:MAV:[XNALP])", "(?:MAC:[XLH])", "(?:MPR:[XNLH])", "(?:MUI:[XNR])", "(?:MS:[XUC])", "(?:MC:[XNLH])", "(?:MI:[XNLH])", "(?:MA:[XNLH])", }, "|") + "))+\\z", ) // Is the given string a CVSSv3.1 vector string? func isV31VectorString(s string) bool { return (len(s) > len(v31Prefix)) && (s[:len(v31Prefix)] == v31Prefix) && v31VecRe.MatchString(s) } // Return numerical scores for this vector. // // Reference implementation: https://www.first.org/cvss/calculator/cvsscalc31.js func (v v31Vector) 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.1 spec, section 7.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.1 spec, section 7.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.1 spec, section 7.1) baseScore := 0.0 { // calculate impact sub-score (cvss v3.1 spec, section 7.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.1 spec, section 7.2) tempScore := 0.0 if hasV3TemporalScoreKeys(keys) { tempScore = roundup(baseScore * ecm * remediationLevel * reportConfidence) } // environmental score (CVSS v3.1 spec, section 7.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 of 13 differs for CVSS v3.0 and CVSS v3.1 impact := 0.0 if modScopeChanged { impact = 7.52 * (miss - 0.029) - 3.25 * math.Pow(miss * 0.9731 - 0.02, 13) } 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) }