From d4b400e50e02e668b92806a4e20c8c9f643dd491 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Thu, 17 Feb 2022 20:28:58 -0500 Subject: cvss: move v31 utility functions to work with all 3.x vectors --- cvss/v31vector.go | 228 +++++++----------------------------------------------- cvss/v3metric.go | 181 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 202 deletions(-) diff --git a/cvss/v31vector.go b/cvss/v31vector.go index 1950e5a..e5e3dff 100644 --- a/cvss/v31vector.go +++ b/cvss/v31vector.go @@ -119,183 +119,9 @@ func isV31VectorString(s string) bool { v31VecRe.MatchString(s) } -// Does the map have at least one of the keys needed for a temporal -// score defined? -func hasTemporalScoreKeys(keys map[Key]v3Metric) bool { - ecm, ecm_ok := keys[v3ExploitCodeMaturity] // E - rl, rl_ok := keys[v3RemediationLevel] // RL - rc, rc_ok := keys[v3ReportConfidence] // RC - - return (ecm_ok && ecm != v3ENotDefined) || - (rl_ok && rl != v3RLNotDefined) || - (rc_ok && rc != v3RCNotDefined) -} - -// Does the map have at least one of the keys needed for an env score to -// be defined? -func hasEnvScoreKeys(keys map[Key]v3Metric) bool { - mav, mav_ok := keys[v3ModifiedAttackVector] // MAV - mac, mac_ok := keys[v3ModifiedAttackComplexity] // MAC - mpr, mpr_ok := keys[v3ModifiedPrivilegesRequired] // MPR - mui, mui_ok := keys[v3ModifiedUserInteraction] // MUI - ms, ms_ok := keys[v3ModifiedScope] // MS - mc, mc_ok := keys[v3ModifiedConfidentiality] // MC - mi, mi_ok := keys[v3ModifiedIntegrity] // MI - ma, ma_ok := keys[v3ModifiedAvailability] // MA - cr, cr_ok := keys[v3ConfidentialityRequirement] // CR - ir, ir_ok := keys[v3IntegrityRequirement] // IR - ar, ar_ok := keys[v3AvailabilityRequirement] // AR - - return (mav_ok && mav != v3MAVNotDefined) || - (mac_ok && mac != v3MACNotDefined) || - (mpr_ok && mpr != v3MPRNotDefined) || - (mui_ok && mui != v3MUINotDefined) || - (ms_ok && ms != v3MSNotDefined) || - (mc_ok && mc != v3MCNotDefined) || - (mi_ok && mi != v3MINotDefined) || - (ma_ok && ma != v3MANotDefined) || - (cr_ok && cr != v3CRNotDefined) || - (ir_ok && ir != v3IRNotDefined) || - (ar_ok && ar != v3ARNotDefined); -} - -// roundup implemention (from CVSS v3.1 spec, appendix A) -func roundup(val float64) float64 { - return math.Ceil(10.0 * val) / 10.0 -} - -var v31MetricCoefs = map[v3Metric]float64 { - v3AVNetwork: 0.85, // AV:N - v3AVAdjacentNetwork: 0.62, // AV:A - v3AVLocal: 0.55, // AV:L - v3AVPhysical: 0.2, // AV:P - - v3ACLow: 0.77, // AC:L - v3ACHigh: 0.44, // AC:H - - v3PRNone: 0.85, // PR:N - v3PRLow: 0.62, // PR:L - v3PRHigh: 0.27, // PR:H - - v3UINone: 0.85, // UI:N - v3UIRequired: 0.62, // UI:R - - v3CHigh: 0.56, // C:H - v3CLow: 0.22, // C:L - v3CNone: 0.0, // C:N - - v3IHigh: 0.56, // I:H - v3ILow: 0.22, // I:L - v3INone: 0.0, // I:N - - v3AHigh: 0.56, // A:H - v3ALow: 0.22, // A:L - v3ANone: 0.0, // A:N - - v3ENotDefined: 1.0, // E:X - v3EHigh: 1.0, // E:H - v3EFunctional: 0.97, // E:F - v3EProofOfConcept: 0.94, // E:P - v3EUnproven: 0.91, // E:U - - v3RLNotDefined: 1.0, // RL:X - v3RLUnavailable: 1.0, // RL:U - v3RLWorkaround: 0.97, // RL:W - v3RLTemporaryFix: 0.96, // RL:T - v3RLOfficialFix: 0.95, // RL:O - - v3RCNotDefined: 1.0, // RC:X - v3RCConfirmed: 1.0, // RC:C - v3RCReasonable: 0.96, // RC:R - v3RCUnknown: 0.92, // RC:U - - v3CRNotDefined: 1.0, // CR:X - v3CRHigh: 1.5, // CR:H - v3CRMedium: 1.0, // CR:M - v3CRLow: 0.5, // CR:L - - v3IRNotDefined: 1.0, // IR:X - v3IRHigh: 1.5, // IR:H - v3IRMedium: 1.0, // IR:M - v3IRLow: 0.5, // IR:L - - v3ARNotDefined: 1.0, // AR:X - v3ARHigh: 1.5, // AR:H - v3ARMedium: 1.0, // AR:M - v3ARLow: 0.5, // AR:L - - v3MAVNotDefined: 1.0, // MAV:X - v3MAVNetwork: 0.85, // MAV:N - v3MAVAdjacentNetwork: 0.62, // MAV:A - v3MAVLocal: 0.55, // MAV:L - v3MAVPhysical: 0.2, // MAV:P - - v3MACNotDefined: 1.0, // MAC:X - v3MACLow: 0.77, // MAC:L - v3MACHigh: 0.44, // MAC:H - - v3MPRNotDefined: 1.0, // MPR:X - v3MPRNone: 0.85, // MPR:N - v3MPRLow: 0.62, // MPR:L - v3MPRHigh: 0.27, // MPR:H - - v3MUINotDefined: 1.0, // MUI:X - v3MUINone: 0.85, // MUI:N - v3MUIRequired: 0.62, // MUI:R - - v3MCNotDefined: 1.0, // MC:X - v3MCHigh: 0.56, // MC:H - v3MCLow: 0.22, // MC:L - v3MCNone: 0.0, // MC:N - - v3MINotDefined: 1.0, // MI:X - v3MIHigh: 0.56, // MI:H - v3MILow: 0.22, // MI:L - v3MINone: 0.0, // MI:N - - v3MANotDefined: 1.0, // MA:X - v3MAHigh: 0.56, // MA:H - v3MALow: 0.22, // MA:L - v3MANone: 0.0, // MA:N -} - -// map of modified metrics to not defined value and base key -var v31ModMetricDefaults = map[v3Key]struct { - notDefined v3Metric - baseKey v3Key -} { - v3ModifiedAttackVector: { v3MAVNotDefined, v3AttackVector }, - v3ModifiedAttackComplexity: { v3MACNotDefined, v3AttackComplexity }, - v3ModifiedUserInteraction: { v3MUINotDefined, v3UserInteraction }, - v3ModifiedConfidentiality: { v3MCNotDefined, v3Confidentiality }, - v3ModifiedIntegrity: { v3MINotDefined, v3Integrity }, - v3ModifiedAvailability: { v3MANotDefined, v3Availability }, -} - -// Get modified metric coefficient, or fall back to base coefficient if -// modified metric is not defined. -func getModCoef(keys map[Key]v3Metric, key v3Key) float64 { - d := v31ModMetricDefaults[key] - if v, _ := keys[key]; v != d.notDefined { - // return modified metric coefficient - return v31MetricCoefs[v] - } else { - // return base coefficient - return v31MetricCoefs[keys[d.baseKey]] - } -} -// var metricWeightMPR = CVSS31.Weight.PR [MS !== "X" ? MS : S] [MPR !== "X" ? MPR : PR]; // Depends on MS. - -var v31PrivReqCoefs = map[v3Metric]map[bool]float64 { - v3PRNone: map[bool]float64 { false: 0.85, true: 0.85 }, - v3PRLow: map[bool]float64 { false: 0.62, true: 0.68 }, - v3PRHigh: map[bool]float64 { false: 0.27, true: 0.50 }, - v3MPRNone: map[bool]float64 { false: 0.85, true: 0.85 }, - v3MPRLow: map[bool]float64 { false: 0.62, true: 0.68 }, - v3MPRHigh: map[bool]float64 { false: 0.27, true: 0.50 }, -} - // Return numerical scores for this vector. +// +// Reference implementation: https://www.first.org/cvss/calculator/cvsscalc31.js func (v v31Vector) Scores() (Scores, error) { scopeChanged := false modScopeChanged := false @@ -335,29 +161,29 @@ func (v v31Vector) Scores() (Scores, error) { } } - attackVector := v31MetricCoefs[keys[v3AttackVector]] - attackComplexity := v31MetricCoefs[keys[v3AttackComplexity]] - userInteraction := v31MetricCoefs[keys[v3UserInteraction]] - conf := v31MetricCoefs[keys[v3Confidentiality]] - integ := v31MetricCoefs[keys[v3Integrity]] - avail := v31MetricCoefs[keys[v3Availability]] - ecm := v31MetricCoefs[keys[v3ExploitCodeMaturity]] - remediationLevel := v31MetricCoefs[keys[v3RemediationLevel]] - reportConfidence := v31MetricCoefs[keys[v3ReportConfidence]] - confReq := v31MetricCoefs[keys[v3ConfidentialityRequirement]] - availReq := v31MetricCoefs[keys[v3AvailabilityRequirement]] - integReq := v31MetricCoefs[keys[v3IntegrityRequirement]] + attackVector := v3MetricCoefs[keys[v3AttackVector]] + attackComplexity := v3MetricCoefs[keys[v3AttackComplexity]] + userInteraction := v3MetricCoefs[keys[v3UserInteraction]] + conf := v3MetricCoefs[keys[v3Confidentiality]] + integ := v3MetricCoefs[keys[v3Integrity]] + avail := v3MetricCoefs[keys[v3Availability]] + ecm := v3MetricCoefs[keys[v3ExploitCodeMaturity]] + remediationLevel := v3MetricCoefs[keys[v3RemediationLevel]] + reportConfidence := v3MetricCoefs[keys[v3ReportConfidence]] + confReq := v3MetricCoefs[keys[v3ConfidentialityRequirement]] + availReq := v3MetricCoefs[keys[v3AvailabilityRequirement]] + integReq := v3MetricCoefs[keys[v3IntegrityRequirement]] // adjust privsRequired based on scopeChanged // (CVSS v3.1 spec, section 7.4, table 16) - privsRequired := v31PrivReqCoefs[keys[v3PrivilegesRequired]][scopeChanged] + privsRequired := v3PrivReqCoefs[keys[v3PrivilegesRequired]][scopeChanged] - modAttackVector := getModCoef(keys, v3ModifiedAttackVector) - modAttackComplexity := getModCoef(keys, v3ModifiedAttackComplexity) - modUserInteraction := getModCoef(keys, v3ModifiedUserInteraction) - modConf := getModCoef(keys, v3ModifiedConfidentiality) - modInteg := getModCoef(keys, v3ModifiedIntegrity) - modAvail := getModCoef(keys, v3ModifiedAvailability) + modAttackVector := getV3ModCoef(keys, v3ModifiedAttackVector) + modAttackComplexity := getV3ModCoef(keys, v3ModifiedAttackComplexity) + modUserInteraction := getV3ModCoef(keys, v3ModifiedUserInteraction) + modConf := getV3ModCoef(keys, v3ModifiedConfidentiality) + modInteg := getV3ModCoef(keys, v3ModifiedIntegrity) + modAvail := getV3ModCoef(keys, v3ModifiedAvailability) if v, _ := keys[v3ModifiedScope]; v == v3MSNotDefined { // default to base scopeChanged @@ -373,15 +199,15 @@ func (v v31Vector) Scores() (Scores, error) { ms, _ := keys[v3ModifiedScope] if mpr != v3MPRNotDefined && ms != v3MSNotDefined { - modPrivsRequired = v31PrivReqCoefs[mpr][ms == v3MSChanged] + modPrivsRequired = v3PrivReqCoefs[mpr][ms == v3MSChanged] } else if mpr != v3MPRNotDefined && ms == v3MSNotDefined { - modPrivsRequired = v31PrivReqCoefs[mpr][scopeChanged] + modPrivsRequired = v3PrivReqCoefs[mpr][scopeChanged] } else if mpr == v3MPRNotDefined && ms != v3MSNotDefined { - modPrivsRequired = v31PrivReqCoefs[pr][ms == v3MSChanged] + modPrivsRequired = v3PrivReqCoefs[pr][ms == v3MSChanged] } else { // default to base privsRequired // modPrivsRequired = privsRequired - modPrivsRequired = v31PrivReqCoefs[pr][scopeChanged] + modPrivsRequired = v3PrivReqCoefs[pr][scopeChanged] } } @@ -413,13 +239,13 @@ func (v v31Vector) Scores() (Scores, error) { // temporal score (CVSS v3.1 spec, section 7.2) tempScore := 0.0 - if hasTemporalScoreKeys(keys) { + if hasV3TemporalScoreKeys(keys) { tempScore = roundup(baseScore * ecm * remediationLevel * reportConfidence) } // environmental score (CVSS v3.1 spec, section 7.3) envScore := 0.0 - if hasEnvScoreKeys(keys) { + if hasV3EnvScoreKeys(keys) { // modified impact sub score (ISC_m) miss := math.Min( 1 - (1 - confReq * modConf) * (1 - integReq * modInteg) * (1 - availReq * modAvail), diff --git a/cvss/v3metric.go b/cvss/v3metric.go index c0f00be..b4bb89b 100644 --- a/cvss/v3metric.go +++ b/cvss/v3metric.go @@ -1,5 +1,9 @@ package cvss +import ( + "math" +) + //go:generate stringer -linecomment -type=v3Metric // metric value @@ -324,7 +328,7 @@ func (m v3Metric) Key() Key { } } -// Convert string to CVSS 3.1 metric. +// Convert string to CVSS 3.x metric. func getV3Metric(version Version, s string) (v3Metric, error) { if m, ok := v3MetricStrLut[s]; ok { return m, nil @@ -332,3 +336,178 @@ func getV3Metric(version Version, s string) (v3Metric, error) { return v3InvalidMetric, newBadMetric(version, s) } } + +// Does the map have at least one of the keys needed for a temporal +// score defined? +func hasV3TemporalScoreKeys(keys map[Key]v3Metric) bool { + ecm, ecm_ok := keys[v3ExploitCodeMaturity] // E + rl, rl_ok := keys[v3RemediationLevel] // RL + rc, rc_ok := keys[v3ReportConfidence] // RC + + return (ecm_ok && ecm != v3ENotDefined) || + (rl_ok && rl != v3RLNotDefined) || + (rc_ok && rc != v3RCNotDefined) +} + +// Does the map have at least one of the keys needed for an env score to +// be defined? +func hasV3EnvScoreKeys(keys map[Key]v3Metric) bool { + mav, mav_ok := keys[v3ModifiedAttackVector] // MAV + mac, mac_ok := keys[v3ModifiedAttackComplexity] // MAC + mpr, mpr_ok := keys[v3ModifiedPrivilegesRequired] // MPR + mui, mui_ok := keys[v3ModifiedUserInteraction] // MUI + ms, ms_ok := keys[v3ModifiedScope] // MS + mc, mc_ok := keys[v3ModifiedConfidentiality] // MC + mi, mi_ok := keys[v3ModifiedIntegrity] // MI + ma, ma_ok := keys[v3ModifiedAvailability] // MA + cr, cr_ok := keys[v3ConfidentialityRequirement] // CR + ir, ir_ok := keys[v3IntegrityRequirement] // IR + ar, ar_ok := keys[v3AvailabilityRequirement] // AR + + return (mav_ok && mav != v3MAVNotDefined) || + (mac_ok && mac != v3MACNotDefined) || + (mpr_ok && mpr != v3MPRNotDefined) || + (mui_ok && mui != v3MUINotDefined) || + (ms_ok && ms != v3MSNotDefined) || + (mc_ok && mc != v3MCNotDefined) || + (mi_ok && mi != v3MINotDefined) || + (ma_ok && ma != v3MANotDefined) || + (cr_ok && cr != v3CRNotDefined) || + (ir_ok && ir != v3IRNotDefined) || + (ar_ok && ar != v3ARNotDefined); +} + +// roundup implemention (from CVSS v3.1 spec, appendix A) +func roundup(val float64) float64 { + return math.Ceil(10.0 * val) / 10.0 +} + +var v3MetricCoefs = map[v3Metric]float64 { + v3AVNetwork: 0.85, // AV:N + v3AVAdjacentNetwork: 0.62, // AV:A + v3AVLocal: 0.55, // AV:L + v3AVPhysical: 0.2, // AV:P + + v3ACLow: 0.77, // AC:L + v3ACHigh: 0.44, // AC:H + + v3PRNone: 0.85, // PR:N + v3PRLow: 0.62, // PR:L + v3PRHigh: 0.27, // PR:H + + v3UINone: 0.85, // UI:N + v3UIRequired: 0.62, // UI:R + + v3CHigh: 0.56, // C:H + v3CLow: 0.22, // C:L + v3CNone: 0.0, // C:N + + v3IHigh: 0.56, // I:H + v3ILow: 0.22, // I:L + v3INone: 0.0, // I:N + + v3AHigh: 0.56, // A:H + v3ALow: 0.22, // A:L + v3ANone: 0.0, // A:N + + v3ENotDefined: 1.0, // E:X + v3EHigh: 1.0, // E:H + v3EFunctional: 0.97, // E:F + v3EProofOfConcept: 0.94, // E:P + v3EUnproven: 0.91, // E:U + + v3RLNotDefined: 1.0, // RL:X + v3RLUnavailable: 1.0, // RL:U + v3RLWorkaround: 0.97, // RL:W + v3RLTemporaryFix: 0.96, // RL:T + v3RLOfficialFix: 0.95, // RL:O + + v3RCNotDefined: 1.0, // RC:X + v3RCConfirmed: 1.0, // RC:C + v3RCReasonable: 0.96, // RC:R + v3RCUnknown: 0.92, // RC:U + + v3CRNotDefined: 1.0, // CR:X + v3CRHigh: 1.5, // CR:H + v3CRMedium: 1.0, // CR:M + v3CRLow: 0.5, // CR:L + + v3IRNotDefined: 1.0, // IR:X + v3IRHigh: 1.5, // IR:H + v3IRMedium: 1.0, // IR:M + v3IRLow: 0.5, // IR:L + + v3ARNotDefined: 1.0, // AR:X + v3ARHigh: 1.5, // AR:H + v3ARMedium: 1.0, // AR:M + v3ARLow: 0.5, // AR:L + + v3MAVNotDefined: 1.0, // MAV:X + v3MAVNetwork: 0.85, // MAV:N + v3MAVAdjacentNetwork: 0.62, // MAV:A + v3MAVLocal: 0.55, // MAV:L + v3MAVPhysical: 0.2, // MAV:P + + v3MACNotDefined: 1.0, // MAC:X + v3MACLow: 0.77, // MAC:L + v3MACHigh: 0.44, // MAC:H + + v3MPRNotDefined: 1.0, // MPR:X + v3MPRNone: 0.85, // MPR:N + v3MPRLow: 0.62, // MPR:L + v3MPRHigh: 0.27, // MPR:H + + v3MUINotDefined: 1.0, // MUI:X + v3MUINone: 0.85, // MUI:N + v3MUIRequired: 0.62, // MUI:R + + v3MCNotDefined: 1.0, // MC:X + v3MCHigh: 0.56, // MC:H + v3MCLow: 0.22, // MC:L + v3MCNone: 0.0, // MC:N + + v3MINotDefined: 1.0, // MI:X + v3MIHigh: 0.56, // MI:H + v3MILow: 0.22, // MI:L + v3MINone: 0.0, // MI:N + + v3MANotDefined: 1.0, // MA:X + v3MAHigh: 0.56, // MA:H + v3MALow: 0.22, // MA:L + v3MANone: 0.0, // MA:N +} + +// map of modified metrics to not defined value and base key +var v3ModMetricDefaults = map[v3Key]struct { + notDefined v3Metric + baseKey v3Key +} { + v3ModifiedAttackVector: { v3MAVNotDefined, v3AttackVector }, + v3ModifiedAttackComplexity: { v3MACNotDefined, v3AttackComplexity }, + v3ModifiedUserInteraction: { v3MUINotDefined, v3UserInteraction }, + v3ModifiedConfidentiality: { v3MCNotDefined, v3Confidentiality }, + v3ModifiedIntegrity: { v3MINotDefined, v3Integrity }, + v3ModifiedAvailability: { v3MANotDefined, v3Availability }, +} + +// Get modified metric coefficient, or fall back to base coefficient if +// modified metric is not defined. +func getV3ModCoef(keys map[Key]v3Metric, key v3Key) float64 { + d := v3ModMetricDefaults[key] + if v, _ := keys[key]; v != d.notDefined { + // return modified metric coefficient + return v3MetricCoefs[v] + } else { + // return base coefficient + return v3MetricCoefs[keys[d.baseKey]] + } +} + +var v3PrivReqCoefs = map[v3Metric]map[bool]float64 { + v3PRNone: map[bool]float64 { false: 0.85, true: 0.85 }, + v3PRLow: map[bool]float64 { false: 0.62, true: 0.68 }, + v3PRHigh: map[bool]float64 { false: 0.27, true: 0.50 }, + v3MPRNone: map[bool]float64 { false: 0.85, true: 0.85 }, + v3MPRLow: map[bool]float64 { false: 0.62, true: 0.68 }, + v3MPRHigh: map[bool]float64 { false: 0.27, true: 0.50 }, +} -- cgit v1.2.3