cvss-suite 4.1.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGES.md → CHANGELOG.md} +36 -0
  3. data/LICENSE.md +2 -1
  4. data/README.md +80 -99
  5. data/docs/upgrading-to-5.md +117 -0
  6. data/docs/usage.md +234 -0
  7. data/lib/cvss_suite/cvss.rb +20 -3
  8. data/lib/cvss_suite/cvss2/cvss2.rb +3 -0
  9. data/lib/cvss_suite/cvss2/cvss2_base.rb +15 -4
  10. data/lib/cvss_suite/cvss2/cvss2_environmental.rb +3 -1
  11. data/lib/cvss_suite/cvss2/cvss2_temporal.rb +2 -0
  12. data/lib/cvss_suite/cvss3/cvss3.rb +4 -0
  13. data/lib/cvss_suite/cvss3/cvss3_base.rb +11 -1
  14. data/lib/cvss_suite/cvss3/cvss3_environmental.rb +43 -60
  15. data/lib/cvss_suite/cvss3/cvss3_temporal.rb +2 -0
  16. data/lib/cvss_suite/cvss31/cvss31.rb +4 -0
  17. data/lib/cvss_suite/cvss31/cvss31_base.rb +15 -1
  18. data/lib/cvss_suite/cvss31/cvss31_environmental.rb +46 -60
  19. data/lib/cvss_suite/cvss31/cvss31_temporal.rb +2 -0
  20. data/lib/cvss_suite/cvss40/cvss40.rb +10 -0
  21. data/lib/cvss_suite/cvss40/cvss40_all_up.rb +3 -1
  22. data/lib/cvss_suite/cvss40/cvss40_base.rb +8 -0
  23. data/lib/cvss_suite/cvss40/cvss40_calc_helper.rb +12 -8
  24. data/lib/cvss_suite/cvss40/cvss40_constants_levels.rb +2 -0
  25. data/lib/cvss_suite/cvss40/cvss40_constants_macro_vector_lookup.rb +2 -0
  26. data/lib/cvss_suite/cvss40/cvss40_constants_max_composed.rb +2 -0
  27. data/lib/cvss_suite/cvss40/cvss40_constants_max_severity.rb +2 -0
  28. data/lib/cvss_suite/cvss40/cvss40_environmental.rb +2 -0
  29. data/lib/cvss_suite/cvss40/cvss40_environmental_security.rb +2 -0
  30. data/lib/cvss_suite/cvss40/cvss40_supplemental.rb +2 -0
  31. data/lib/cvss_suite/cvss40/cvss40_threat.rb +2 -0
  32. data/lib/cvss_suite/cvss_31_and_before.rb +5 -3
  33. data/lib/cvss_suite/cvss_40_and_later.rb +3 -1
  34. data/lib/cvss_suite/cvss_metric.rb +4 -2
  35. data/lib/cvss_suite/cvss_property.rb +8 -6
  36. data/lib/cvss_suite/errors.rb +75 -9
  37. data/lib/cvss_suite/helpers/cvss31_helper.rb +6 -0
  38. data/lib/cvss_suite/helpers/cvss3_helper.rb +4 -0
  39. data/lib/cvss_suite/invalid_cvss.rb +26 -2
  40. data/lib/cvss_suite/version.rb +3 -1
  41. data/lib/cvss_suite.rb +86 -13
  42. metadata +12 -110
  43. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -21
  44. data/.github/ISSUE_TEMPLATE/custom.md +0 -7
  45. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  46. data/.github/workflows/rspec.yml +0 -23
  47. data/.github/workflows/rubocop.yml +0 -21
  48. data/.gitignore +0 -14
  49. data/.rspec +0 -3
  50. data/.rubocop.yml +0 -69
  51. data/.rubocop_todo.yml +0 -59
  52. data/CNAME +0 -1
  53. data/CODE_OF_CONDUCT.md +0 -79
  54. data/Gemfile +0 -9
  55. data/PULL_REQUEST_TEMPLATE.md +0 -24
  56. data/_config.yml +0 -1
  57. data/bin/console +0 -14
  58. data/bin/setup +0 -7
  59. data/cvss_suite.gemspec +0 -48
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -12,13 +14,17 @@ module CvssSuite
12
14
  attr_reader :base
13
15
 
14
16
  ##
15
- # Creates a new CVSS vector by a +vector+.
17
+ # Creates a new CVSS vector by a +vector+. CvssSuite.new also passes the
18
+ # +original+ string it was handed, because +vector+ reaches here with the
19
+ # CVSS:x.x/ prefix or the CVSS 2 parentheses already stripped, and an error
20
+ # has to name the vector the caller actually wrote.
16
21
  #
17
22
  # Raises an exception if it is called on Cvss class.
18
- def initialize(vector)
23
+ def initialize(vector, original = vector)
19
24
  raise CvssSuite::Errors::InvalidParentClass, 'Do not instantiate this class!' if instance_of? Cvss
20
25
 
21
26
  @vector = vector
27
+ @original = original
22
28
  @properties = []
23
29
  extract_metrics
24
30
  init_metrics
@@ -31,6 +37,9 @@ module CvssSuite
31
37
 
32
38
  score = overall_score
33
39
 
40
+ # The explicit `<= 0.0` branch and the defensive `else` both yield 'None' by
41
+ # design (zero score vs. out-of-range guard); kept distinct for readability.
42
+ # rubocop:disable-next Lint/DuplicateBranch
34
43
  if score <= 0.0
35
44
  'None'
36
45
  elsif (0.1..3.9).cover? score
@@ -65,7 +74,15 @@ module CvssSuite
65
74
  end
66
75
 
67
76
  def check_validity
68
- raise CvssSuite::Errors::InvalidVector, 'Vector is not valid!' unless valid?
77
+ raise CvssSuite::Errors::InvalidVector.for(rejected_vector) unless valid?
78
+ end
79
+
80
+ # What the caller passed, not the normalized form. #vector would be the
81
+ # tempting source, but it is lossy: Cvss2 does not restore the parentheses
82
+ # that prepare_cvss2_vector strips, so a rejected '(AV:N/AC:L)' would be
83
+ # reported as 'AV:N/AC:L', and a paren form it cannot parse at all as ''.
84
+ def rejected_vector
85
+ @original
69
86
  end
70
87
 
71
88
  def required_amount_of_properties
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -55,6 +57,7 @@ module CvssSuite
55
57
  ##
56
58
  # Returns the Environmental Score of the CVSS vector.
57
59
  def environmental_score
60
+ check_validity
58
61
  return temporal_score unless @environmental.valid?
59
62
 
60
63
  (@environmental.score @base, @temporal.score).round(1)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -19,6 +21,9 @@ module CvssSuite
19
21
  # Returns the base score of the CVSS vector. The calculation is based on formula version 2.10 .
20
22
  # See CVSS documentation for further information https://www.first.org/cvss/v2/guide#i3.2.1 .
21
23
  #
24
+ # BaseScore = ((0.6 x Impact) + (0.4 x Exploitability) - 1.5) x f(Impact),
25
+ # where f(Impact) = 0 when Impact is 0, else 1.176.
26
+ #
22
27
  # Takes +Security+ +Requirement+ +Impacts+ for calculating environmental score.
23
28
  def score(sr_cr_score = 1, sr_ir_score = 1, sr_ar_score = 1)
24
29
  impact = calc_impact(sr_cr_score, sr_ir_score, sr_ar_score)
@@ -73,17 +78,23 @@ module CvssSuite
73
78
  { name: 'Complete', abbreviation: 'C', weight: 0.66 }]))
74
79
  end
75
80
 
81
+ # CVSS v2, 3.2.1 -- Impact = 10.41 x (1 - (1-C) x (1-I) x (1-A)).
82
+ # The sr_* args carry the environmental Security Requirements (3.3.1),
83
+ # defaulting to 1 (= no environmental adjustment) for the plain base score.
84
+ # https://www.first.org/cvss/v2/guide#i3.2.1
76
85
  def calc_impact(sr_cr_score = 1, sr_ir_score = 1, sr_ar_score = 1)
77
- confidentiality_score = 1 - @confidentiality_impact.score * sr_cr_score
78
- integrity_score = 1 - @integrity_impact.score * sr_ir_score
79
- availability_score = 1 - @availability_impact.score * sr_ar_score
86
+ confidentiality_score = 1 - (@confidentiality_impact.score * sr_cr_score)
87
+ integrity_score = 1 - (@integrity_impact.score * sr_ir_score)
88
+ availability_score = 1 - (@availability_impact.score * sr_ar_score)
80
89
 
81
- impact = 10.41 * (1 - confidentiality_score * integrity_score * availability_score)
90
+ impact = 10.41 * (1 - (confidentiality_score * integrity_score * availability_score))
82
91
  return impact if sr_cr_score == 1 && sr_ir_score == 1 && sr_ar_score == 1
83
92
 
84
93
  [10, impact].min
85
94
  end
86
95
 
96
+ # CVSS v2, 3.2.1 -- Exploitability = 20 x AccessVector x AccessComplexity x Authentication.
97
+ # https://www.first.org/cvss/v2/guide#i3.2.1
87
98
  def calc_exploitability
88
99
  20 * @access_vector.score * @access_complexity.score * @authentication.score
89
100
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -25,7 +27,7 @@ module CvssSuite
25
27
  @security_requirements_ar.score).round(1)
26
28
 
27
29
  adjusted_temporal = (base_score * temporal_score.to_d).round(1).to_f
28
- (adjusted_temporal + (10 - adjusted_temporal) * @collateral_damage_potential.score) * @target_distribution.score
30
+ (adjusted_temporal + ((10 - adjusted_temporal) * @collateral_damage_potential.score)) * @target_distribution.score
29
31
  end
30
32
 
31
33
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -28,12 +30,14 @@ module CvssSuite
28
30
  ##
29
31
  # Returns the Temporal Score of the CVSS vector.
30
32
  def temporal_score
33
+ check_validity
31
34
  Cvss3Helper.round_up(Cvss3Helper.round_up(@base.score) * @temporal.score)
32
35
  end
33
36
 
34
37
  ##
35
38
  # Returns the Environmental Score of the CVSS vector.
36
39
  def environmental_score
40
+ check_validity
37
41
  return temporal_score unless @environmental.valid?
38
42
 
39
43
  Cvss3Helper.round_up(@environmental.score(@base, @temporal))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -82,6 +84,9 @@ module CvssSuite
82
84
  { name: 'High', abbreviation: 'H', weight: 0.56 }]))
83
85
  end
84
86
 
87
+ # CVSS v3.0 spec, 8.1 Base Metrics Equations -- Exploitability sub-score:
88
+ # 8.22 x AttackVector x AttackComplexity x PrivilegesRequired x UserInteraction
89
+ # https://www.first.org/cvss/v3.0/specification-document
85
90
  def calc_exploitability
86
91
  privilege_score = Cvss3Helper.privileges_required_score @privileges_required, @scope
87
92
 
@@ -89,11 +94,16 @@ module CvssSuite
89
94
  privilege_score * @user_interaction.score
90
95
  end
91
96
 
97
+ # CVSS v3.0 spec, 8.1 Base Metrics Equations -- Impact sub-score:
98
+ # ISCBase = 1 - [(1-C) x (1-I) x (1-A)]
99
+ # Scope Changed: 7.52 x (ISCBase - 0.029) - 3.25 x (ISCBase - 0.02)^15
100
+ # Scope Unchanged: 6.42 x ISCBase
101
+ # https://www.first.org/cvss/v3.0/specification-document
92
102
  def calc_impact
93
103
  isc_base = 1 - ((1 - @confidentiality.score) * (1 - @integrity.score) * (1 - @availability.score))
94
104
 
95
105
  if @scope.selected_value[:name] == 'Changed'
96
- 7.52 * (isc_base - 0.029) - 3.25 * (isc_base - 0.02)**15
106
+ (7.52 * (isc_base - 0.029)) - (3.25 * ((isc_base - 0.02)**15))
97
107
  else
98
108
  6.42 * isc_base
99
109
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -24,17 +26,10 @@ module CvssSuite
24
26
  def score(base, temporal)
25
27
  @base = base
26
28
 
27
- merged_modified_privileges_required = @modified_privileges_required
28
- if @modified_privileges_required.selected_value[:name] == 'Not Defined'
29
- merged_modified_privileges_required = @base.privileges_required
30
- end
31
-
32
- merged_modified_scope = @modified_scope
33
- if @modified_scope.selected_value[:name] == 'Not Defined'
34
- merged_modified_scope = @base.scope
35
- end
36
-
37
- privilege_score = Cvss3Helper.privileges_required_score(merged_modified_privileges_required, merged_modified_scope)
29
+ privilege_score = Cvss3Helper.privileges_required_score(
30
+ merged(@modified_privileges_required, @base.privileges_required),
31
+ merged(@modified_scope, @base.scope)
32
+ )
38
33
 
39
34
  modified_exploitability_sub_score = modified_exploitability_sub(privilege_score)
40
35
 
@@ -47,6 +42,18 @@ module CvssSuite
47
42
 
48
43
  private
49
44
 
45
+ # Returns the modified property, falling back to the base property when the
46
+ # modified metric is "Not Defined".
47
+ def merged(modified, base)
48
+ modified.selected_value[:name] == 'Not Defined' ? base : modified
49
+ end
50
+
51
+ # Whether the effective scope -- the modified scope, or the base scope when
52
+ # the modified scope is "Not Defined" -- is Changed.
53
+ def scope_changed?
54
+ merged(@modified_scope, @base.scope).selected_value[:name] == 'Changed'
55
+ end
56
+
50
57
  def init_properties
51
58
  @properties.push(@confidentiality_requirement =
52
59
  CvssProperty.new(name: 'Confidentiality Requirement', abbreviation: 'CR',
@@ -115,71 +122,47 @@ module CvssSuite
115
122
  { name: 'Not Defined', abbreviation: 'X', weight: 1 }]))
116
123
  end
117
124
 
125
+ # CVSS v3.0 spec, 8.3 Environmental -- ModifiedImpact from the MISS below:
126
+ # Scope Changed: 7.52 x (MISS - 0.029) - 3.25 x (MISS - 0.02)^15
127
+ # Scope Unchanged: 6.42 x MISS
128
+ # https://www.first.org/cvss/v3.0/specification-document
118
129
  def modified_impact_sub(isc_modified)
119
- if @modified_scope.selected_value[:name] == 'Not Defined'
120
- if @base.scope.selected_value[:name] == 'Changed'
121
- return 7.52 * (isc_modified - 0.029) - 3.25 * (isc_modified - 0.02)**15
122
- else
123
- return 6.42 * isc_modified
124
- end
125
- end
126
-
127
- if @modified_scope.selected_value[:name] == 'Changed'
128
- 7.52 * (isc_modified - 0.029) - 3.25 * (isc_modified - 0.02)**15
130
+ if scope_changed?
131
+ (7.52 * (isc_modified - 0.029)) - (3.25 * ((isc_modified - 0.02)**15))
129
132
  else
130
133
  6.42 * isc_modified
131
134
  end
132
135
  end
133
136
 
137
+ # CVSS v3.0 spec, 8.3 Environmental -- Modified Impact Sub-Score (MISS):
138
+ # Minimum(1 - [(1 - MC x CR) x (1 - MI x IR) x (1 - MA x AR)], 0.915)
134
139
  def isc_modified
135
- merged_modified_confidentiality = @modified_confidentiality
136
- if @modified_confidentiality.selected_value[:name] == 'Not Defined'
137
- merged_modified_confidentiality = @base.confidentiality
138
- end
140
+ confidentiality = merged(@modified_confidentiality, @base.confidentiality)
141
+ integrity = merged(@modified_integrity, @base.integrity)
142
+ availability = merged(@modified_availability, @base.availability)
139
143
 
140
- merged_modified_integrity = @modified_integrity
141
- if @modified_integrity.selected_value[:name] == 'Not Defined'
142
- merged_modified_integrity = @base.integrity
143
- end
144
-
145
- merged_modified_availability = @modified_availability
146
- if @modified_availability.selected_value[:name] == 'Not Defined'
147
- merged_modified_availability = @base.availability
148
- end
144
+ confidentiality_score = 1 - (confidentiality.score * @confidentiality_requirement.score)
145
+ integrity_score = 1 - (integrity.score * @integrity_requirement.score)
146
+ availability_score = 1 - (availability.score * @availability_requirement.score)
149
147
 
150
- confidentiality_score = 1 - merged_modified_confidentiality.score * @confidentiality_requirement.score
151
- integrity_score = 1 - merged_modified_integrity.score * @integrity_requirement.score
152
- availability_score = 1 - merged_modified_availability.score * @availability_requirement.score
153
-
154
- [0.915, (1 - confidentiality_score * integrity_score * availability_score)].min
148
+ [0.915, (1 - (confidentiality_score * integrity_score * availability_score))].min
155
149
  end
156
150
 
151
+ # CVSS v3.0 spec, 8.3 Environmental -- ModifiedExploitability:
152
+ # 8.22 x MAV x MAC x MPR x MUI
157
153
  def modified_exploitability_sub(privilege_score)
158
- merged_modified_attack_vector = @modified_attack_vector
159
- if @modified_attack_vector.selected_value[:name] == 'Not Defined'
160
- merged_modified_attack_vector = @base.attack_vector
161
- end
154
+ attack_vector = merged(@modified_attack_vector, @base.attack_vector)
155
+ attack_complexity = merged(@modified_attack_complexity, @base.attack_complexity)
156
+ user_interaction = merged(@modified_user_interaction, @base.user_interaction)
162
157
 
163
- merged_modified_attack_complexity = @modified_attack_complexity
164
- if @modified_attack_complexity.selected_value[:name] == 'Not Defined'
165
- merged_modified_attack_complexity = @base.attack_complexity
166
- end
167
-
168
- merged_modified_user_interaction = @modified_user_interaction
169
- if @modified_user_interaction.selected_value[:name] == 'Not Defined'
170
- merged_modified_user_interaction = @base.user_interaction
171
- end
172
-
173
- 8.22 * merged_modified_attack_vector.score * merged_modified_attack_complexity.score *
174
- privilege_score * merged_modified_user_interaction.score
158
+ 8.22 * attack_vector.score * attack_complexity.score * privilege_score * user_interaction.score
175
159
  end
176
160
 
161
+ # CVSS v3.0 spec, 8.3 Environmental -- ModifiedScore = Roundup(Minimum(
162
+ # [1.08 x] (ModifiedImpact + ModifiedExploitability), 10)) x TemporalScore.
163
+ # The 1.08 factor applies when the effective (modified) scope is Changed.
177
164
  def calculate_score(modified_impact_sub_score, modified_exploitability_sub_score, temporal_score)
178
- if @modified_scope.selected_value[:name] == 'Not Defined'
179
- factor = @base.scope.selected_value[:name] == 'Changed' ? 1.08 : 1.0
180
- else
181
- factor = @modified_scope.selected_value[:name] == 'Changed' ? 1.08 : 1.0
182
- end
165
+ factor = scope_changed? ? 1.08 : 1.0
183
166
 
184
167
  Cvss3Helper.round_up(
185
168
  [factor * (modified_impact_sub_score + modified_exploitability_sub_score), 10].min
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -32,6 +34,7 @@ module CvssSuite
32
34
  # Returns the Temporal Score of the CVSS vector.
33
35
 
34
36
  def temporal_score
37
+ check_validity
35
38
  Cvss31Helper.round_up(Cvss31Helper.round_up(@base.score) * @temporal.score)
36
39
  end
37
40
 
@@ -39,6 +42,7 @@ module CvssSuite
39
42
  # Returns the Environmental Score of the CVSS vector.
40
43
 
41
44
  def environmental_score
45
+ check_validity
42
46
  return temporal_score unless @environmental.valid?
43
47
 
44
48
  Cvss31Helper.round_up(@environmental.score(@base, @temporal))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -83,6 +85,9 @@ module CvssSuite
83
85
  { name: 'High', abbreviation: 'H', weight: 0.56 }]))
84
86
  end
85
87
 
88
+ # CVSS v3.1 spec, 7.1 Base Metrics Equations -- Exploitability sub-score:
89
+ # 8.22 x AttackVector x AttackComplexity x PrivilegesRequired x UserInteraction
90
+ # https://www.first.org/cvss/v3.1/specification-document
86
91
  def calc_exploitability
87
92
  privilege_score = Cvss3Helper.privileges_required_score(@privileges_required, @scope)
88
93
 
@@ -90,11 +95,20 @@ module CvssSuite
90
95
  privilege_score * @user_interaction.score
91
96
  end
92
97
 
98
+ # CVSS v3.1 spec, 7.1 Base Metrics Equations -- Impact sub-score:
99
+ # ISCBase = 1 - [(1-C) x (1-I) x (1-A)]
100
+ # Scope Changed: 7.52 x (ISCBase - 0.029) - 3.25 x (ISCBase - 0.02)^15
101
+ # Scope Unchanged: 6.42 x ISCBase
102
+ # NOTE: v3.1 Base deliberately keeps the exponent 15 from v3.0. Only the
103
+ # Environmental ModifiedImpact (spec 7.3, see cvss31_environmental.rb) changed
104
+ # to `x 0.9731` and exponent 13. The two are intentionally different -- do not
105
+ # "reconcile" them.
106
+ # https://www.first.org/cvss/v3.1/specification-document
93
107
  def calc_impact
94
108
  isc_base = 1 - ((1 - @confidentiality.score) * (1 - @integrity.score) * (1 - @availability.score))
95
109
 
96
110
  if @scope.selected_value[:name] == 'Changed'
97
- 7.52 * (isc_base - 0.029) - 3.25 * (isc_base - 0.02)**15
111
+ (7.52 * (isc_base - 0.029)) - (3.25 * ((isc_base - 0.02)**15))
98
112
  else
99
113
  6.42 * isc_base
100
114
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -24,17 +26,10 @@ module CvssSuite
24
26
  def score(base, temporal)
25
27
  @base = base
26
28
 
27
- merged_modified_privileges_required = @modified_privileges_required
28
- if @modified_privileges_required.selected_value[:name] == 'Not Defined'
29
- merged_modified_privileges_required = @base.privileges_required
30
- end
31
-
32
- merged_modified_scope = @modified_scope
33
- if @modified_scope.selected_value[:name] == 'Not Defined'
34
- merged_modified_scope = @base.scope
35
- end
36
-
37
- privilege_score = Cvss3Helper.privileges_required_score(merged_modified_privileges_required, merged_modified_scope)
29
+ privilege_score = Cvss3Helper.privileges_required_score(
30
+ merged(@modified_privileges_required, @base.privileges_required),
31
+ merged(@modified_scope, @base.scope)
32
+ )
38
33
 
39
34
  modified_exploitability_sub_score = modified_exploitability_sub(privilege_score)
40
35
 
@@ -47,6 +42,18 @@ module CvssSuite
47
42
 
48
43
  private
49
44
 
45
+ # Returns the modified property, falling back to the base property when the
46
+ # modified metric is "Not Defined".
47
+ def merged(modified, base)
48
+ modified.selected_value[:name] == 'Not Defined' ? base : modified
49
+ end
50
+
51
+ # Whether the effective scope -- the modified scope, or the base scope when
52
+ # the modified scope is "Not Defined" -- is Changed.
53
+ def scope_changed?
54
+ merged(@modified_scope, @base.scope).selected_value[:name] == 'Changed'
55
+ end
56
+
50
57
  def init_properties
51
58
  @properties.push(@confidentiality_requirement =
52
59
  CvssProperty.new(name: 'Confidentiality Requirement', abbreviation: 'CR',
@@ -115,71 +122,50 @@ module CvssSuite
115
122
  { name: 'Not Defined', abbreviation: 'X', weight: 1 }]))
116
123
  end
117
124
 
125
+ # CVSS v3.1 spec, 7.3 Environmental -- ModifiedImpact from the MISS below:
126
+ # Scope Changed: 7.52 x (MISS - 0.029) - 3.25 x (MISS x 0.9731 - 0.02)^13
127
+ # Scope Unchanged: 6.42 x MISS
128
+ # NOTE: v3.1 changed this Environmental sub-formula to `x 0.9731` and exponent
129
+ # 13; the v3.1 Base Impact (7.1, see cvss31_base.rb) still uses exponent 15.
130
+ # The difference is intentional per spec.
131
+ # https://www.first.org/cvss/v3.1/specification-document
118
132
  def modified_impact_sub(isc_modified)
119
- if @modified_scope.selected_value[:name] == 'Not Defined'
120
- if @base.scope.selected_value[:name] == 'Changed'
121
- return 7.52 * (isc_modified - 0.029) - 3.25 * (isc_modified * 0.9731 - 0.02)**13
122
- else
123
- return 6.42 * isc_modified
124
- end
125
- end
126
-
127
- if @modified_scope.selected_value[:name] == 'Changed'
128
- 7.52 * (isc_modified - 0.029) - 3.25 * (isc_modified * 0.9731 - 0.02)**13
133
+ if scope_changed?
134
+ (7.52 * (isc_modified - 0.029)) - (3.25 * (((isc_modified * 0.9731) - 0.02)**13))
129
135
  else
130
136
  6.42 * isc_modified
131
137
  end
132
138
  end
133
139
 
140
+ # CVSS v3.1 spec, 7.3 Environmental -- Modified Impact Sub-Score (MISS):
141
+ # Minimum(1 - [(1 - MC x CR) x (1 - MI x IR) x (1 - MA x AR)], 0.915)
134
142
  def isc_modified
135
- merged_modified_confidentiality = @modified_confidentiality
136
- if @modified_confidentiality.selected_value[:name] == 'Not Defined'
137
- merged_modified_confidentiality = @base.confidentiality
138
- end
143
+ confidentiality = merged(@modified_confidentiality, @base.confidentiality)
144
+ integrity = merged(@modified_integrity, @base.integrity)
145
+ availability = merged(@modified_availability, @base.availability)
139
146
 
140
- merged_modified_integrity = @modified_integrity
141
- if @modified_integrity.selected_value[:name] == 'Not Defined'
142
- merged_modified_integrity = @base.integrity
143
- end
144
-
145
- merged_modified_availability = @modified_availability
146
- if @modified_availability.selected_value[:name] == 'Not Defined'
147
- merged_modified_availability = @base.availability
148
- end
147
+ confidentiality_score = 1 - (confidentiality.score * @confidentiality_requirement.score)
148
+ integrity_score = 1 - (integrity.score * @integrity_requirement.score)
149
+ availability_score = 1 - (availability.score * @availability_requirement.score)
149
150
 
150
- confidentiality_score = 1 - merged_modified_confidentiality.score * @confidentiality_requirement.score
151
- integrity_score = 1 - merged_modified_integrity.score * @integrity_requirement.score
152
- availability_score = 1 - merged_modified_availability.score * @availability_requirement.score
153
-
154
- [0.915, (1 - confidentiality_score * integrity_score * availability_score)].min
151
+ [0.915, (1 - (confidentiality_score * integrity_score * availability_score))].min
155
152
  end
156
153
 
154
+ # CVSS v3.1 spec, 7.3 Environmental -- ModifiedExploitability:
155
+ # 8.22 x MAV x MAC x MPR x MUI
157
156
  def modified_exploitability_sub(privilege_score)
158
- merged_modified_attack_vector = @modified_attack_vector
159
- if @modified_attack_vector.selected_value[:name] == 'Not Defined'
160
- merged_modified_attack_vector = @base.attack_vector
161
- end
157
+ attack_vector = merged(@modified_attack_vector, @base.attack_vector)
158
+ attack_complexity = merged(@modified_attack_complexity, @base.attack_complexity)
159
+ user_interaction = merged(@modified_user_interaction, @base.user_interaction)
162
160
 
163
- merged_modified_attack_complexity = @modified_attack_complexity
164
- if @modified_attack_complexity.selected_value[:name] == 'Not Defined'
165
- merged_modified_attack_complexity = @base.attack_complexity
166
- end
167
-
168
- merged_modified_user_interaction = @modified_user_interaction
169
- if @modified_user_interaction.selected_value[:name] == 'Not Defined'
170
- merged_modified_user_interaction = @base.user_interaction
171
- end
172
-
173
- 8.22 * merged_modified_attack_vector.score * merged_modified_attack_complexity.score *
174
- privilege_score * merged_modified_user_interaction.score
161
+ 8.22 * attack_vector.score * attack_complexity.score * privilege_score * user_interaction.score
175
162
  end
176
163
 
164
+ # CVSS v3.1 spec, 7.3 Environmental -- ModifiedScore = Roundup(Minimum(
165
+ # [1.08 x] (ModifiedImpact + ModifiedExploitability), 10)) x TemporalScore.
166
+ # The 1.08 factor applies when the effective (modified) scope is Changed.
177
167
  def calculate_score(modified_impact_sub_score, modified_exploitability_sub_score, temporal_score)
178
- if @modified_scope.selected_value[:name] == 'Not Defined'
179
- factor = @base.scope.selected_value[:name] == 'Changed' ? 1.08 : 1.0
180
- else
181
- factor = @modified_scope.selected_value[:name] == 'Changed' ? 1.08 : 1.0
182
- end
168
+ factor = scope_changed? ? 1.08 : 1.0
183
169
 
184
170
  Cvss31Helper.round_up(
185
171
  [factor * (modified_impact_sub_score + modified_exploitability_sub_score), 10].min
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -28,6 +30,14 @@ module CvssSuite
28
30
  "#{CvssSuite::CVSS_VECTOR_BEGINNINGS.find { |beginning| beginning[:version] == version }[:string]}#{@vector}"
29
31
  end
30
32
 
33
+ ##
34
+ # Returns the base score of the CVSS vector, ignoring threat and
35
+ # environmental metrics -- the base score as published by NVD/GHSA.
36
+ def base_score
37
+ check_validity
38
+ @base.score
39
+ end
40
+
31
41
  private
32
42
 
33
43
  def init_metrics
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -15,7 +17,7 @@ module CvssSuite
15
17
  ##
16
18
  # Returns score of this metric
17
19
  def score
18
- Cvss40CalcHelper.new(@properties.map { |p| [p.abbreviation, p.selected_value[:abbreviation]] }.to_h).score
20
+ Cvss40CalcHelper.new(@properties.to_h { |p| [p.abbreviation, p.selected_value[:abbreviation]] }).score
19
21
  end
20
22
 
21
23
  def initialize(properties, base, threat, environmental, environmental_security, supplemental)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # CVSS-Suite, a Ruby gem to manage the CVSS vector
2
4
  #
3
5
  # This work is licensed under the terms of the MIT license.
@@ -18,6 +20,12 @@ module CvssSuite
18
20
  :vulnerable_system_confidentiality, :vulnerable_system_integrity, :vulnerable_system_availability,
19
21
  :subsequent_system_confidentiality, :subsequent_system_integrity, :subsequent_system_availability
20
22
 
23
+ ##
24
+ # Returns the base score of these metrics, ignoring threat and environmental metrics.
25
+ def score
26
+ Cvss40CalcHelper.new(@properties.to_h { |p| [p.abbreviation, p.selected_value[:abbreviation]] }).score
27
+ end
28
+
21
29
  private
22
30
 
23
31
  def init_properties