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) } // Does the map have at least one of the keys needed for a temporal // score defined? func hasTemporalScoreKeys(keys map[Key]v3Metric) bool { ecm, ecm_ok := keys[v3ExploitCodeMaturity] // E rl, rl_ok := keys[v3RemediationLevel] // RL rc, rc_ok := keys[v3ReportConfidence] // RC return (ecm_ok && ecm != v3ENotDefined) || (rl_ok && rl != v3RLNotDefined) || (rc_ok && rc != v3RCNotDefined) } // roundup implemention (from CVSS v3.1 spec, appendix A) func roundup(val float64) float64 { return math.Ceil(10.0 * val) / 10.0 } // Return numerical scores for this vector. func (v v31Vector) Scores() (Scores, error) { attackVector := 0.0 attackComplexity := 0.0 privsRequired := 0.0 userInteraction := 0.0 scopeChanged := false conf := 0.0 integ := 0.0 avail := 0.0 ecm := 1.0 remediationLevel := 1.0 reportConfidence := 1.0 confReq := 1.0 availReq := 1.0 integReq := 1.0 modAttackVector := 0.0 modAttackComplexity := 0.0 modPrivsRequired := 0.0 modUserInteraction := 0.0 modScopeChanged := false modConf := 0.0 modInteg := 0.0 modAvail := 0.0 keys := make(map[Key]v3Metric) for _, m := range([]v3Metric(v)) { keys[m.Key()] = m switch m { case v3AVNetwork: // AV:N attackVector = 0.85 case v3AVAdjacentNetwork: // AV:A attackVector = 0.62 case v3AVLocal: // AV:L attackVector = 0.55 case v3AVPhysical: // AV:P attackVector = 0.2 case v3ACLow: // AC:L attackComplexity = 0.77 case v3ACHigh: // AC:H attackComplexity = 0.44 case v3PRNone: // PR:N privsRequired = 0.85 case v3PRLow: // PR:L privsRequired = 0.62 case v3PRHigh: // PR:H privsRequired = 0.27 case v3UINone: // UI:N userInteraction = 0.85 case v3UIRequired: // UI:R userInteraction = 0.62 case v3SUnchanged: // S:U scopeChanged = false case v3SChanged: // S:C scopeChanged = true case v3CHigh: // C:H conf = 0.56 case v3CLow: // C:L conf = 0.22 case v3CNone: // C:N conf = 0.0 case v3IHigh: // I:H integ = 0.56 case v3ILow: // I:L integ = 0.22 case v3INone: // I:N integ = 0.0 case v3AHigh: // A:H avail = 0.56 case v3ALow: // A:L avail = 0.22 case v3ANone: // A:N avail = 0.0 case v3ENotDefined: // E:X ecm = 1.0 case v3EHigh: // E:H ecm = 1.0 case v3EFunctional: // E:F ecm = 0.97 case v3EProofOfConcept: // E:P ecm = 0.94 case v3EUnproven: // E:U ecm = 0.91 case v3RLNotDefined: // RL:X remediationLevel = 1.0 case v3RLUnavailable: // RL:U remediationLevel = 1.0 case v3RLWorkaround: // RL:W remediationLevel = 0.97 case v3RLTemporaryFix: // RL:T remediationLevel = 0.96 case v3RLOfficialFix: // RL:O remediationLevel = 0.95 case v3RCNotDefined: // RC:X reportConfidence = 1.0 case v3RCConfirmed: // RC:C reportConfidence = 1.0 case v3RCReasonable: // RC:R reportConfidence = 0.96 case v3RCUnknown: // RC:U reportConfidence = 0.92 case v3CRNotDefined: // CR:X confReq = 1.0 case v3CRHigh: // CR:H confReq = 1.5 case v3CRMedium: // CR:M confReq = 1.0 case v3CRLow: // CR:L confReq = 0.5 case v3IRNotDefined: // IR:X integReq = 1.0 case v3IRHigh: // IR:H integReq = 1.5 case v3IRMedium: // IR:M integReq = 1.0 case v3IRLow: // IR:L integReq = 0.5 case v3ARNotDefined: // AR:X availReq = 1.0 case v3ARHigh: // AR:H availReq = 1.5 case v3ARMedium: // AR:M availReq = 1.0 case v3ARLow: // AR:L availReq = 0.5 case v3MAVNotDefined: // MAV:X modAttackVector = 0.0 case v3MAVNetwork: // MAV:N modAttackVector = 0.85 case v3MAVAdjacentNetwork: // MAV:A modAttackVector = 0.62 case v3MAVLocal: // MAV:L modAttackVector = 0.55 case v3MAVPhysical: // MAV:P modAttackVector = 0.2 case v3MACNotDefined: // MAC:X modAttackComplexity = 0.0 case v3MACLow: // MAC:L modAttackComplexity = 0.77 case v3MACHigh: // MAC:H modAttackComplexity = 0.44 case v3MPRNotDefined: // MPR:X modPrivsRequired = 0.0 case v3MPRLow: // MPR:L modPrivsRequired = 0.62 case v3MPRHigh: // MPR:H modPrivsRequired = 0.27 case v3MUINotDefined: // MUI:X modUserInteraction = 0.85 case v3MUINone: // MUI:N modUserInteraction = 0.85 case v3MUIRequired: // MUI:R modUserInteraction = 0.62 case v3MSNotDefined: // MS:X modScopeChanged = false case v3MSUnchanged: // MS:U modScopeChanged = false case v3MSChanged: // MS:C modScopeChanged = true case v3MCNotDefined: // MC:X modConf = 0.0 case v3MCHigh: // MC:H modConf = 0.56 case v3MCLow: // MC:L modConf = 0.22 case v3MCNone: // MC:N modConf = 0.0 case v3MINotDefined: // MI:X modInteg = 0.0 case v3MIHigh: // MI:H modInteg = 0.56 case v3MILow: // MI:L modInteg = 0.22 case v3MINone: // MI:N modInteg = 0.0 case v3MANotDefined: // MA:X modAvail = 0.0 case v3MAHigh: // MA:H modAvail = 0.56 case v3MALow: // MA:L modAvail = 0.22 case v3MANone: // MA:N modAvail = 0.0 } } // 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) // adjust privileges required based on scopeChanged // (CVSS v3.1 spec, section 7.4, table 16) if pr, ok := keys[v3PrivilegesRequired]; ok { switch pr { case v3PRLow: // PR:L privsRequired = 0.68 case v3PRHigh: // PR:H privsRequired = 0.50 } } } 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 hasTemporalScoreKeys(keys) { tempScore = roundup(baseScore * ecm * remediationLevel * reportConfidence) } // environmental score (CVSS v3.1 spec, section 7.3) // // MISS = Minimum( // 1 - [(1 - ConfidentialityRequirement × ModifiedConfidentiality) × (1 - IntegrityRequirement × ModifiedIntegrity) × (1 - AvailabilityRequirement × ModifiedAvailability)], // 0.915 // ) // // environmental score (CVSS v3.1 spec, section 7.3) envScore := 0.0 { // modified impact sub score 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 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) }