diff options
author | Paul Duncan <pabs@pablotron.org> | 2022-02-07 12:30:54 -0500 |
---|---|---|
committer | Paul Duncan <pabs@pablotron.org> | 2022-02-07 12:30:54 -0500 |
commit | 405e10830d8bd5e58bca7f83ea55ccb4f4f4ab26 (patch) | |
tree | 10a8312e47a9479d67c2f5e543f461dfc503864b | |
parent | ee8432df59ac3a966e40b6e1378fa89750170bba (diff) | |
download | cvez-405e10830d8bd5e58bca7f83ea55ccb4f4f4ab26.tar.bz2 cvez-405e10830d8bd5e58bca7f83ea55ccb4f4f4ab26.zip |
cvss: mv newScoresFromV2Vector() to v2Vector.Scores(), update tests
-rw-r--r-- | cvss/scores.go | 230 | ||||
-rw-r--r-- | cvss/scores_test.go | 210 | ||||
-rw-r--r-- | cvss/v2vector.go | 228 | ||||
-rw-r--r-- | cvss/v2vector_test.go | 210 |
4 files changed, 435 insertions, 443 deletions
diff --git a/cvss/scores.go b/cvss/scores.go index 3100b19..0f6b6d3 100644 --- a/cvss/scores.go +++ b/cvss/scores.go @@ -1,9 +1,5 @@ package cvss -import ( - "math" -) - // CVSS score set. type Scores struct { Base Score `json:"base"` // base score @@ -38,229 +34,3 @@ func NewScores(base, temporal, env float64) (Scores, error) { Env: envScore, }, nil } - -// Create new v2 scores from v2 vector. -func newScoresFromV2Vector(v v2Vector) (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) -} diff --git a/cvss/scores_test.go b/cvss/scores_test.go index f35904c..b8fad53 100644 --- a/cvss/scores_test.go +++ b/cvss/scores_test.go @@ -1,7 +1,6 @@ package cvss import ( - "reflect" "testing" ) @@ -30,212 +29,3 @@ func TestNewScores(t *testing.T) { }) } } - -func TestNewScoresFromV2Vector(t *testing.T) { - // test vectors from section 3.3 - passTests := []struct { - name string // test name - val string // test cvss v2 vector - exps []float64 // expected base, temporal, and env scores - } {{ - name: "CVE-2002-0392/base", // 3.3.1 - val: "AV:N/AC:L/Au:N/C:N/I:N/A:C", - exps: []float64 { 7.8, 0.0, 0.0 }, - }, { - name: "CVE-2002-0392/temporal", // 3.3.1 - val: "AV:N/AC:L/Au:N/C:N/I:N/A:C/E:F/RL:OF/RC:C", - exps: []float64 { 7.8, 6.4, 0.0 }, - }, { - name: "CVE-2002-0392/all", // 3.3.1 - val: "AV:N/AC:L/Au:N/C:N/I:N/A:C/E:F/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:H", - exps: []float64 { 7.8, 6.4, 9.2 }, - }, { - name: "CVE-2003-0818/base", // 3.3.2 - val: "AV:N/AC:L/Au:N/C:C/I:C/A:C", - exps: []float64 { 10.0, 0.0, 0.0 }, - }, { - name: "CVE-2003-0818/temporal", // 3.3.2 - val: "AV:N/AC:L/Au:N/C:C/I:C/A:C/E:F/RL:OF/RC:C", - exps: []float64 { 10.0, 8.3, 0.0 }, - }, { - name: "CVE-2003-0818/all", // 3.3.2 - val: "AV:N/AC:L/Au:N/C:C/I:C/A:C/E:F/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:L", - exps: []float64 { 10.0, 8.3, 9.0 }, - }, { - name: "CVE-2003-0062/base", // 3.3.3 - val: "AV:L/AC:H/Au:N/C:C/I:C/A:C", - exps: []float64 { 6.2, 0.0, 0.0 }, - }, { - name: "CVE-2003-0062/temporal", // 3.3.3 - val: "AV:L/AC:H/Au:N/C:C/I:C/A:C/E:POC/RL:OF/RC:C", - exps: []float64 { 6.2, 4.9, 0.0 }, - }, { - name: "CVE-2003-0062/all", // 3.3.3 - val: "AV:L/AC:H/Au:N/C:C/I:C/A:C/E:POC/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:M", - exps: []float64 { 6.2, 4.9, 7.5 }, - }, { - name: "A:N", // from nvd v2 calc - val: "AV:A/AC:M/Au:M/C:P/I:P/A:N", - exps: []float64 { 3.4, 0.0, 0.0 }, - }, { - name: "Au:S", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P", - exps: []float64 { 4.9, 0.0, 0.0 }, - }, { - name: "E:ND", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:ND", - exps: []float64 { 4.9, 0.0, 0.0 }, - }, { - name: "E:U", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:U/RL:ND/RC:ND", - exps: []float64 { 4.9, 4.2, 0.0 }, - }, { - name: "E:H", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:ND/RC:ND", - exps: []float64 { 4.9, 4.9, 0.0 }, - }, { - name: "RL:TF", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:TF/RC:ND", - exps: []float64 { 4.9, 4.4, 0.0 }, - }, { - name: "RL:W", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND", - exps: []float64 { 4.9, 4.7, 0.0 }, - }, { - name: "RL:U", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:U/RC:ND", - exps: []float64 { 4.9, 4.9, 0.0 }, - }, { - name: "RC:UC", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:U/RC:UC", - exps: []float64 { 4.9, 4.4, 0.0 }, - }, { - name: "RC:UR", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:U/RC:UR", - exps: []float64 { 4.9, 4.7, 0.0 }, - }, { - name: "CDP:ND", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:ND/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 2.8 }, - }, { - name: "CDP:N", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:N/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 2.8 }, - }, { - name: "CDP:L", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:L/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 3.5 }, - }, { - name: "CDP:LM", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:LM/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 5.0 }, - }, { - name: "CDP:MH", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:MH/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 5.7 }, - }, { - name: "CDP:H", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.4 }, - }, { - name: "TD:ND", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:ND/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.4 }, - }, { - name: "TD:N", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:N/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 0.0 }, - }, { - name: "TD:L", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:L/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 1.6 }, - }, { - name: "TD:M", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:M/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 4.8 }, - }, { - name: "TD:H", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.4 }, - }, { - name: "CR:ND", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:ND/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.8 }, - }, { - name: "CR:L", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.4 }, - }, { - name: "CR:M", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:M/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.8 }, - }, { - name: "CR:H", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:H/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 7.1 }, - }, { - name: "IR:ND", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:ND/AR:L", - exps: []float64 { 4.9, 4.7, 6.8 }, - }, { - name: "IR:L", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.4 }, - }, { - name: "IR:M", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:M/AR:L", - exps: []float64 { 4.9, 4.7, 6.8 }, - }, { - name: "IR:H", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:H/AR:L", - exps: []float64 { 4.9, 4.7, 7.1 }, - }, { - name: "AR:ND", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:ND", - exps: []float64 { 4.9, 4.7, 6.8 }, - }, { - name: "AR:L", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", - exps: []float64 { 4.9, 4.7, 6.4 }, - }, { - name: "AR:M", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:M", - exps: []float64 { 4.9, 4.7, 6.8 }, - }, { - name: "AR:H", // from nvd v2 calc - val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:H", - exps: []float64 { 4.9, 4.7, 7.1 }, - }} - // TODO: add additional test vectors using v2 calc - - for _, test := range(passTests) { - t.Run(test.name, func(t *testing.T) { - // build expected result - exp, err := NewScores(test.exps[0], test.exps[1], test.exps[2]) - if err != nil { - t.Error(err) - return - } - - // 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 - } - - // compare to expected scores - if !reflect.DeepEqual(got, exp) { - t.Errorf("got %v, exp %v", got, exp) - return - } - }) - } -} diff --git a/cvss/v2vector.go b/cvss/v2vector.go index e015544..f4c26e2 100644 --- a/cvss/v2vector.go +++ b/cvss/v2vector.go @@ -2,6 +2,7 @@ package cvss import ( // "encoding/json" + "math" "regexp" "strings" ) @@ -43,7 +44,228 @@ func (v v2Vector) Metrics() []Metric { // Return numerical scores for this vector. func (v v2Vector) Scores() (Scores, error) { - return newScoresFromV2Vector(v) + // 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. @@ -74,14 +296,14 @@ func newV2Vector(s string) (v2Vector, error) { // 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 diff --git a/cvss/v2vector_test.go b/cvss/v2vector_test.go index f297ba6..6b3f870 100644 --- a/cvss/v2vector_test.go +++ b/cvss/v2vector_test.go @@ -1,6 +1,7 @@ package cvss import ( + "reflect" "testing" ) @@ -67,3 +68,212 @@ func TestNewV2Vector(t *testing.T) { }) } } + +func TestV2VectorScores(t *testing.T) { + // test vectors from section 3.3 + passTests := []struct { + name string // test name + val string // test cvss v2 vector + exps []float64 // expected base, temporal, and env scores + } {{ + name: "CVE-2002-0392/base", // 3.3.1 + val: "AV:N/AC:L/Au:N/C:N/I:N/A:C", + exps: []float64 { 7.8, 0.0, 0.0 }, + }, { + name: "CVE-2002-0392/temporal", // 3.3.1 + val: "AV:N/AC:L/Au:N/C:N/I:N/A:C/E:F/RL:OF/RC:C", + exps: []float64 { 7.8, 6.4, 0.0 }, + }, { + name: "CVE-2002-0392/all", // 3.3.1 + val: "AV:N/AC:L/Au:N/C:N/I:N/A:C/E:F/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:H", + exps: []float64 { 7.8, 6.4, 9.2 }, + }, { + name: "CVE-2003-0818/base", // 3.3.2 + val: "AV:N/AC:L/Au:N/C:C/I:C/A:C", + exps: []float64 { 10.0, 0.0, 0.0 }, + }, { + name: "CVE-2003-0818/temporal", // 3.3.2 + val: "AV:N/AC:L/Au:N/C:C/I:C/A:C/E:F/RL:OF/RC:C", + exps: []float64 { 10.0, 8.3, 0.0 }, + }, { + name: "CVE-2003-0818/all", // 3.3.2 + val: "AV:N/AC:L/Au:N/C:C/I:C/A:C/E:F/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:L", + exps: []float64 { 10.0, 8.3, 9.0 }, + }, { + name: "CVE-2003-0062/base", // 3.3.3 + val: "AV:L/AC:H/Au:N/C:C/I:C/A:C", + exps: []float64 { 6.2, 0.0, 0.0 }, + }, { + name: "CVE-2003-0062/temporal", // 3.3.3 + val: "AV:L/AC:H/Au:N/C:C/I:C/A:C/E:POC/RL:OF/RC:C", + exps: []float64 { 6.2, 4.9, 0.0 }, + }, { + name: "CVE-2003-0062/all", // 3.3.3 + val: "AV:L/AC:H/Au:N/C:C/I:C/A:C/E:POC/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:M", + exps: []float64 { 6.2, 4.9, 7.5 }, + }, { + name: "A:N", // from nvd v2 calc + val: "AV:A/AC:M/Au:M/C:P/I:P/A:N", + exps: []float64 { 3.4, 0.0, 0.0 }, + }, { + name: "Au:S", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P", + exps: []float64 { 4.9, 0.0, 0.0 }, + }, { + name: "E:ND", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:ND", + exps: []float64 { 4.9, 0.0, 0.0 }, + }, { + name: "E:U", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:U/RL:ND/RC:ND", + exps: []float64 { 4.9, 4.2, 0.0 }, + }, { + name: "E:H", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:ND/RC:ND", + exps: []float64 { 4.9, 4.9, 0.0 }, + }, { + name: "RL:TF", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:TF/RC:ND", + exps: []float64 { 4.9, 4.4, 0.0 }, + }, { + name: "RL:W", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND", + exps: []float64 { 4.9, 4.7, 0.0 }, + }, { + name: "RL:U", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:U/RC:ND", + exps: []float64 { 4.9, 4.9, 0.0 }, + }, { + name: "RC:UC", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:U/RC:UC", + exps: []float64 { 4.9, 4.4, 0.0 }, + }, { + name: "RC:UR", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:U/RC:UR", + exps: []float64 { 4.9, 4.7, 0.0 }, + }, { + name: "CDP:ND", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:ND/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 2.8 }, + }, { + name: "CDP:N", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:N/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 2.8 }, + }, { + name: "CDP:L", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:L/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 3.5 }, + }, { + name: "CDP:LM", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:LM/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 5.0 }, + }, { + name: "CDP:MH", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:MH/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 5.7 }, + }, { + name: "CDP:H", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.4 }, + }, { + name: "TD:ND", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:ND/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.4 }, + }, { + name: "TD:N", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:N/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 0.0 }, + }, { + name: "TD:L", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:L/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 1.6 }, + }, { + name: "TD:M", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:M/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 4.8 }, + }, { + name: "TD:H", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.4 }, + }, { + name: "CR:ND", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:ND/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.8 }, + }, { + name: "CR:L", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.4 }, + }, { + name: "CR:M", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:M/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.8 }, + }, { + name: "CR:H", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:H/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 7.1 }, + }, { + name: "IR:ND", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:ND/AR:L", + exps: []float64 { 4.9, 4.7, 6.8 }, + }, { + name: "IR:L", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.4 }, + }, { + name: "IR:M", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:M/AR:L", + exps: []float64 { 4.9, 4.7, 6.8 }, + }, { + name: "IR:H", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:H/AR:L", + exps: []float64 { 4.9, 4.7, 7.1 }, + }, { + name: "AR:ND", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:ND", + exps: []float64 { 4.9, 4.7, 6.8 }, + }, { + name: "AR:L", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:L", + exps: []float64 { 4.9, 4.7, 6.4 }, + }, { + name: "AR:M", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:M", + exps: []float64 { 4.9, 4.7, 6.8 }, + }, { + name: "AR:H", // from nvd v2 calc + val: "AV:A/AC:M/Au:S/C:P/I:P/A:P/E:H/RL:W/RC:ND/CDP:H/TD:H/CR:L/IR:L/AR:H", + exps: []float64 { 4.9, 4.7, 7.1 }, + }} + // TODO: add additional test vectors using v2 calc + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + // build expected result + exp, err := NewScores(test.exps[0], test.exps[1], test.exps[2]) + if err != nil { + t.Error(err) + return + } + + // 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 + } + + // compare to expected scores + if !reflect.DeepEqual(got, exp) { + t.Errorf("got %v, exp %v", got, exp) + return + } + }) + } +} |