aboutsummaryrefslogtreecommitdiff
path: root/cvss/v2vector.go
blob: f4c26e2bcc5cc3c16c28b4d7000ce5f2228d0e97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package cvss

import (
  // "encoding/json"
  "math"
  "regexp"
  "strings"
)

// CVSS 2.0 vector.
type v2Vector []v2Metric

// Convert vector to string.
func (v v2Vector) String() string {
  // convert to slice of metrics
  metrics := []v2Metric(v)

  // build vector
  r := make([]string, len(metrics))
  for i, m := range(metrics) {
    r[i] = m.String()
  }

  // build and return string
  return strings.Join(r, "/")
}

// Return CVSS version.
func (v2Vector) Version() Version {
  return V20
}

// Return metrics in this vector.
func (v v2Vector) Metrics() []Metric {
  // build result
  r := make([]Metric, len(v))
  for i, m := range(v) {
    r[i] = m
  }

  // return result
  return r
}

// Return numerical scores for this vector.
func (v v2Vector) Scores() (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)
}

// Create CVSS 2.0 vector from string.
func newV2Vector(s string) (v2Vector, error) {
  strs := strings.Split(s, "/")
  r := make([]v2Metric, len(strs))

  // walk metric strings
  for i, ms := range(strs) {
    // convert string to vector
    m, err := getV2Metric(ms)
    if err != nil {
      return nil, err
    }

    // add to results
    r[i] = m
  }

  // build and return vector
  return v2Vector(r), nil
}

// // Unmarshal CVSS 2.0 vector from JSON string.
// func (me *v2Vector) UnmarshalJSON(b []byte) error {
//   // decode string, check for error
//   var s string
//   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
// }

var v2MetricRe = "(?:" + strings.Join([]string {
  "(?:AV:[NAL])",
  "(?:AC:[LMH])",
  "(?:Au:[MSN])",
  "(?:C:[NPC])",
  "(?:I:[NPC])",
  "(?:A:[NPC])",
  "(?:E:(?:ND|U|POC|F|H))",
  "(?:RL:(?:OF|TF|W|U|ND))",
  "(?:RC:(?:UC|UR|C|ND))",
  "(?:CDP:(?:N|L|LM|MH|H|ND))",
  "(?:TD:(?:N|L|M|H|ND))",
  "(?:CR:(?:L|M|H|ND))",
  "(?:IR:(?:L|M|H|ND))",
}, "|") + ")"

var v2VecRe = regexp.MustCompile(
  "\\A" + v2MetricRe + "(?:/" + v2MetricRe + ")*\\z",
)

// Is the given string a CVSS v2 vector string?
func isV2VectorString(s string) bool {
  return len(s) > 0 && v2VecRe.MatchString(s)
}