aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cvss/scores.go230
-rw-r--r--cvss/scores_test.go210
-rw-r--r--cvss/v2vector.go228
-rw-r--r--cvss/v2vector_test.go210
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
+ }
+ })
+ }
+}