aboutsummaryrefslogtreecommitdiff
path: root/cvss
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2022-02-06 15:07:17 -0500
committerPaul Duncan <pabs@pablotron.org>2022-02-06 15:07:17 -0500
commit336b2474298dda59807ff19aac184d0b1b69a611 (patch)
tree2f72116ddafd53036e007aa3dbb94a6c78a614ac /cvss
parent210c57382dfcd1e1d307609d7a34afd4d58b5c1d (diff)
downloadcvez-336b2474298dda59807ff19aac184d0b1b69a611.tar.bz2
cvez-336b2474298dda59807ff19aac184d0b1b69a611.zip
cvss: add v2scores and tests (busted atm)
Diffstat (limited to 'cvss')
-rw-r--r--cvss/v2scores.go253
-rw-r--r--cvss/v2scores_test.go80
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
+ }
+ })
+ }
+}