package cvss import ( // "encoding/json" "math" "regexp" "strings" ) // CVSS 2.0 vector. type v2Vector []v2Metric // Convert vector to string. func (v v2Vector) String() string { // convert to slice of metrics metrics := []v2Metric(v) // build vector r := make([]string, len(metrics)) for i, m := range(metrics) { r[i] = m.String() } // build and return string return strings.Join(r, "/") } // Return CVSS version. func (v2Vector) Version() Version { return V20 } // Return metrics in this vector. func (v v2Vector) Metrics() []Metric { // build result r := make([]Metric, len(v)) for i, m := range(v) { r[i] = m } // return result return r } // Return numerical scores for this vector. func (v v2Vector) Scores() (Scores, error) { // CVSS v2 (https://www.first.org/cvss/v2/guide 3.2.1) // // Impact = 10.41*(1-(1-ConfImpact)*(1-IntegImpact)*(1-AvailImpact)) // Exploitability = 20* AccessVector*AccessComplexity*Authentication // f(impact)= 0 if Impact=0, 1.176 otherwise // BaseScore = round_to_1_decimal(((0.6*Impact)+(0.4*Exploitability)-1.5)*f(Impact)) // base score values confImpact := 0.0 integImpact := 0.0 availImpact := 0.0 accessVector := 0.0 accessComplexity := 0.0 auth := 0.0 // temporal score values // (FIXME: should these be set to 1.0?) exploitability := 0.0 remediationLevel := 0.0 reportConfidence := 0.0 // env score values cdp := 0.0 td := 0.0 confReq := 0.0 integReq := 0.0 availReq := 0.0 for _, m := range([]v2Metric(v)) { switch m { case v2AVNetwork: // AV:N accessVector = 1.0 case v2AVAdjacentNetwork: // AV:A accessVector = 0.646 case v2AVLocal: // AV:L accessVector = 0.395 case v2ACLow: // AC:L accessComplexity = 0.71 case v2ACMedium: // AC:M accessComplexity = 0.61 case v2ACHigh: // AC:H accessComplexity = 0.35 case v2AuMultiple: // Au:M auth = 0.45 case v2AuSingle: // Au:S auth = 0.56 case v2AuNone: // Au:N auth = 0.704 case v2CNone: // C:N confImpact = 0.0 case v2CPartial: // C:P confImpact = 0.275 case v2CComplete: // C:C confImpact = 0.660 case v2INone: // I:N integImpact = 0.0 case v2IPartial: // I:P integImpact = 0.275 case v2IComplete: // I:C integImpact = 0.660 case v2ANone: // A:N availImpact = 0.0 case v2APartial: // A:P availImpact = 0.275 case v2AComplete: // A:C availImpact = 0.660 case v2ENotDefined: // E:ND exploitability = 1.0 case v2EUnproven: // E:U exploitability = 0.85 case v2EProofOfConcept: // E:POC exploitability = 0.9 case v2EFunctional: // E:F exploitability = 0.95 case v2EHigh: // E:H exploitability = 1.0 case v2RLOfficialFix: // RL:OF remediationLevel = 0.87 case v2RLTemporaryFix: // RL:TF remediationLevel = 0.9 case v2RLWorkaround: // RL:W remediationLevel = 0.95 case v2RLUnavailable: // RL:U remediationLevel = 1.0 case v2RLNotDefined: // RL:ND remediationLevel = 1.0 case v2RCUnconfirmed: // RC:UC reportConfidence = 0.9 case v2RCUncorroborated: // RC:UR reportConfidence = 0.95 case v2RCConfirmed: // RC:C reportConfidence = 1.0 case v2RCNotDefined: // RC:ND reportConfidence = 1.0 case v2CDPNone: // CDP:N cdp = 0.0 case v2CDPLow: // CDP:L cdp = 0.1 case v2CDPLowMedium: // CDP:LM cdp = 0.3 case v2CDPMediumHigh: // CDP:MH cdp = 0.4 case v2CDPHigh: // CDP:H cdp = 0.5 case v2CDPNotDefined: // CDP:ND cdp = 0.0 case v2TDNone: // TD:N td = 0.0 case v2TDLow: // TD:L td = 0.25 case v2TDMedium: // TD:M td = 0.75 case v2TDHigh: // TD:H td = 1.0 case v2TDNotDefined: // TD:ND td = 1.0 case v2CRLow: // CR:L confReq = 0.5 case v2CRMedium: // CR:M confReq = 1.0 case v2CRHigh: // CR:H confReq = 1.51 case v2CRNotDefined: // CR:ND confReq = 1.0 case v2IRLow: // IR:L integReq = 0.5 case v2IRMedium: // IR:M integReq = 1.0 case v2IRHigh: // IR:H integReq = 1.51 case v2IRNotDefined: // IR:ND integReq = 1.0 case v2ARLow: // AR:L availReq = 0.5 case v2ARMedium: // AR:M availReq = 1.0 case v2ARHigh: // AR:H availReq = 1.51 case v2ARNotDefined: // AR:ND availReq = 1.0 } } // calculate base score (3.2.1 Base Equation) // // Impact = 10.41*(1-(1-ConfImpact)*(1-IntegImpact)*(1-AvailImpact)) // Exploitability = 20* AccessVector*AccessComplexity*Authentication // f(impact)= 0 if Impact=0, 1.176 otherwise // BaseScore = round_to_1_decimal(((0.6*Impact)+(0.4*Exploitability)-1.5)*f(Impact)) baseScore := 0.0 { impact := 10.41 * (1 - (1 - confImpact) * (1 - integImpact) * (1 - availImpact)) fImpact := 0.0 if impact > 0.0 { fImpact = 1.176 } baseExpl := 20 * accessVector * accessComplexity * auth baseScore = ((0.6 * impact + 0.4 * baseExpl) - 1.5) * fImpact baseScore = math.Round(10.0 * baseScore) / 10.0 } // calculate temporal score (3.2.2 Temporal Equation) // // TemporalScore = round_to_1_decimal(BaseScore*Exploitability // *RemediationLevel*ReportConfidence) tempScore := 0.0 { tempScore = baseScore * exploitability * remediationLevel * reportConfidence tempScore = math.Round(10.0 * tempScore) / 10.0 } // calculate environmental score (3.2.3 Environmental Equation) // // AdjustedImpact = min(10,10.41*(1-(1-ConfImpact*ConfReq)*(1-IntegImpact*IntegReq) // *(1-AvailImpact*AvailReq))) // // AdjustedTemporal = TemporalScore recomputed with the BaseScore's // Impact sub-equation replaced with the AdjustedImpact equation // // EnvironmentalScore = round_to_1_decimal((AdjustedTemporal+ // (10-AdjustedTemporal)*CollateralDamagePotential)*TargetDistribution) // envScore := 0.0 { // calc adjusted impact adjImpact := math.Min( 10.0, 10.41 * (1 - (1 - confImpact * confReq) * (1 - integImpact * integReq) * (1 - availImpact * availReq)), ) fImpact := 0.0 if adjImpact > 0.0 { fImpact = 1.176 } // calculate environmental base score using adjusted impact baseExpl := 20 * accessVector * accessComplexity * auth envBaseScore := ((0.6 * adjImpact + 0.4 * baseExpl) - 1.5) * fImpact envBaseScore = (10.0 * envBaseScore) / 10.0 // calculate adjusted temporal score adjTempScore := envBaseScore * exploitability * remediationLevel * reportConfidence adjTempScore = math.Round(10.0 * adjTempScore) / 10.0 envScore = (adjTempScore + (10 - adjTempScore) * cdp) * td envScore = math.Round(10.0 * envScore) / 10.0 } // build and return result return NewScores(baseScore, tempScore, envScore) } // Create CVSS 2.0 vector from string. func newV2Vector(s string) (v2Vector, error) { strs := strings.Split(s, "/") r := make([]v2Metric, len(strs)) // walk metric strings for i, ms := range(strs) { // convert string to vector m, err := getV2Metric(ms) if err != nil { return nil, err } // add to results r[i] = m } // build and return vector return v2Vector(r), nil } // // Unmarshal CVSS 2.0 vector from JSON string. // func (me *v2Vector) 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 := newV2Vector(s) // if err != nil { // // return error // return err // } // // // save result, return success // *me = r // return nil // } var v2MetricRe = "(?:" + strings.Join([]string { "(?:AV:[NAL])", "(?:AC:[LMH])", "(?:Au:[MSN])", "(?:C:[NPC])", "(?:I:[NPC])", "(?:A:[NPC])", "(?:E:(?:ND|U|POC|F|H))", "(?:RL:(?:OF|TF|W|U|ND))", "(?:RC:(?:UC|UR|C|ND))", "(?:CDP:(?:N|L|LM|MH|H|ND))", "(?:TD:(?:N|L|M|H|ND))", "(?:CR:(?:L|M|H|ND))", "(?:IR:(?:L|M|H|ND))", }, "|") + ")" var v2VecRe = regexp.MustCompile( "\\A" + v2MetricRe + "(?:/" + v2MetricRe + ")*\\z", ) // Is the given string a CVSS v2 vector string? func isV2VectorString(s string) bool { return len(s) > 0 && v2VecRe.MatchString(s) }