diff options
-rw-r--r-- | cvss/v2scores.go | 253 | ||||
-rw-r--r-- | cvss/v2scores_test.go | 80 |
2 files changed, 333 insertions, 0 deletions
diff --git a/cvss/v2scores.go b/cvss/v2scores.go new file mode 100644 index 0000000..c4643d4 --- /dev/null +++ b/cvss/v2scores.go @@ -0,0 +1,253 @@ +package cvss + +import ( + "math" +) + +// CVSS v2 base, temporal, and environmental scores. +type v2Scores struct { + base v2Score // base score + temporal v2Score // temporal score + env v2Score // environmental score +} + +// Create new v2 scores from v2 vector. +func newV2Scores(v v2Vector) (v2Scores, 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 + 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) + temporalScore := 0.0 + { + temporalScore = baseScore * exploitability * remediationLevel * reportConfidence + temporalScore = math.Round(10.0 * temporalScore) / 10.0 + } + + // calculate environmental score (3.2.4 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 + { + impact := 10.41 * (1 - (1 - confImpact) * (1 - integImpact) * (1 - availImpact)) + adjImpact := math.Min( + 10.0, + 10.41 * (1 - (1 - confImpact * confReq) * (1 - integImpact * integReq) * (1 - availImpact * availReq)), + ) + + baseExpl := 20 * accessVector * accessComplexity * auth + envBaseScore := ((0.6 * impact + 0.4 * baseExpl) - 1.5) * adjImpact + envBaseScore = 10.0 * math.Round(envBaseScore) / 10.0 + + adjTemporalScore := envBaseScore * exploitability * remediationLevel * reportConfidence + adjTemporalScore = 10.0 * math.Round(adjTemporalScore) / 10.0 + + envScore = adjTemporalScore + (10 - adjTemporalScore) * cdp * td + envScore = math.Round(10.0 * envScore) / 10.0 + } + + // convert base score from float to v2score + rBaseScore, err := newV2Score(baseScore) + if err != nil { + return v2Scores{}, err + } + + // convert temporal score from float to v2score + rTemporalScore, err := newV2Score(temporalScore) + if err != nil { + return v2Scores{}, err + } + + // convert env score from float to v2score + rEnvScore, err := newV2Score(envScore) + if err != nil { + return v2Scores{}, err + } + + // return success + return v2Scores { + base: rBaseScore, + temporal: rTemporalScore, + env: rEnvScore, + }, nil +} diff --git a/cvss/v2scores_test.go b/cvss/v2scores_test.go new file mode 100644 index 0000000..b47cf05 --- /dev/null +++ b/cvss/v2scores_test.go @@ -0,0 +1,80 @@ +package cvss + +import ( + "reflect" + "testing" +) + +// build v2scores from slice of floats +func getTestScores(vals []float64) (v2Scores, error) { + // build expected score list + scores := make([]v2Score, 3) + for i, val := range(vals) { + if score, err := newV2Score(val); err != nil { + return v2Scores{}, err + } else { + scores[i] = score + } + } + + // build expected scores + return v2Scores { + scores[0], + scores[1], + scores[2], + }, nil +} + +func TestNewV2Scores(t *testing.T) { + passTests := []struct { + name string // test name + val string // test cvss v2 vector + exps []float64 // expected base, temporal, and env scores + } {{ + name: "CVE-2002-0392", + val: "AV:N/AC:L/Au:N/C:N/I:N/A:C", + exps: []float64 { 7.8, 6.4, 9.2 }, + }} + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + // build expected score list + expScores := make([]v2Score, 3) + for i, val := range(test.exps) { + if s, err := newV2Score(val); err != nil { + t.Error(err) + return + } else { + expScores[i] = s + } + } + + // build expected scores + exp := v2Scores { + expScores[0], + expScores[1], + expScores[2], + } + + // create vector, check for error + vec, err := newV2Vector(test.val) + if err != nil { + t.Error(err) + return + } + + // get scores + got, err := vec.Scores() + if err != nil { + t.Error(err) + return + } + + + if !reflect.DeepEqual(got, exp) { + t.Errorf("got %v, exp %v", got, exp) + return + } + }) + } +} |