1use std::fmt::{Display, Formatter};
9use std::str::FromStr;
10
11use serde::{Deserialize, Serialize};
12
13use crate::error::{CVSSError, Result};
14use crate::metric::Metric;
15use crate::severity::SeverityType;
16use crate::v4::attack_complexity::AttackComplexityType;
17use crate::v4::attack_requirements::AttackRequirementsType;
18use crate::v4::attack_vector::AttackVectorType;
19use crate::v4::constant::{
20 get_eq1245_max_composed, get_eq1245_max_severity, get_eq36_max_composed, get_eq36_max_severity,
21 lookup,
22};
23use crate::v4::environmental::{
24 AvailabilityRequirements, ConfidentialityRequirements, Environmental, IntegrityRequirements,
25};
26use crate::v4::exploit_maturity::ExploitMaturity;
27use crate::v4::privileges_required::PrivilegesRequiredType;
28use crate::v4::subsequent_impact_metrics::{
29 SubsequentAvailabilityImpactType, SubsequentConfidentialityImpactType, SubsequentImpact,
30 SubsequentIntegrityImpactType,
31};
32use crate::v4::user_interaction::UserInteractionType;
33use crate::v4::vulnerable_impact_metrics::{
34 VulnerableAvailabilityImpactType, VulnerableConfidentialityImpactType, VulnerableImpact,
35 VulnerableIntegrityImpactType,
36};
37use crate::version::Version;
38
39pub mod attack_complexity;
40pub mod attack_requirements;
41pub mod attack_vector;
42mod constant;
43pub mod environmental;
44pub mod exploit_maturity;
45pub mod privileges_required;
46pub mod subsequent_impact_metrics;
47pub mod user_interaction;
48pub mod vulnerable_impact_metrics;
49
50#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
55#[serde(rename_all = "camelCase")]
56pub struct ExploitAbility {
57 pub attack_vector: AttackVectorType,
59 pub attack_complexity: AttackComplexityType,
61 pub attack_requirements: AttackRequirementsType,
63 pub privileges_required: PrivilegesRequiredType,
65 pub user_interaction: UserInteractionType,
67}
68
69impl ExploitAbility {
70 pub fn score(&self) -> f32 {
72 self.attack_vector.score()
73 + self.attack_complexity.score()
74 + self.user_interaction.score()
75 + self.privileges_required.score()
76 }
77 fn eq1(&self) -> Option<u32> {
81 if self.attack_vector.is_network()
82 && self.privileges_required.is_none()
83 && self.user_interaction.is_none()
84 {
85 return Some(0);
87 } else if (self.attack_vector.is_network()
88 || self.privileges_required.is_none()
89 || self.user_interaction.is_none())
90 && !(self.attack_vector.is_network()
91 && self.privileges_required.is_none()
92 && self.user_interaction.is_none())
93 && !(self.attack_vector.is_physical())
94 {
95 return Some(1);
97 } else if self.attack_vector.is_physical()
98 || !(self.attack_vector.is_network()
99 || self.privileges_required.is_none()
100 || self.user_interaction.is_none())
101 {
102 return Some(2);
104 }
105 None
106 }
107 fn eq2(&self) -> Option<u32> {
110 if self.attack_complexity.is_low() && self.attack_requirements.is_none() {
111 return Some(0);
112 } else if !(self.attack_complexity.is_low() && self.attack_requirements.is_none()) {
113 return Some(1);
114 }
115 None
116 }
117}
118
119#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
129#[serde(rename_all = "camelCase")]
130pub struct CVSS {
131 pub version: Version,
133 pub vector_string: String,
135 #[serde(flatten)]
136 pub exploit_ability: ExploitAbility,
137 #[serde(flatten)]
138 pub vulnerable_impact: VulnerableImpact,
139 #[serde(flatten)]
140 pub subsequent_impact: SubsequentImpact,
141 pub exploit: ExploitMaturity,
142 #[serde(flatten)]
143 pub environmental: Environmental,
144 pub base_score: f32,
146 pub base_severity: SeverityType,
148}
149
150impl CVSS {
151 pub fn vector_string(vectors: &str) -> Result<Self> {
154 let exploit_ability = ExploitAbility {
155 attack_vector: AttackVectorType::from_str(vectors)?,
156 attack_complexity: AttackComplexityType::from_str(vectors)?,
157 attack_requirements: AttackRequirementsType::from_str(vectors)?,
158 privileges_required: PrivilegesRequiredType::from_str(vectors)?,
159 user_interaction: UserInteractionType::from_str(vectors)?,
160 };
161 let vulnerable_impact = VulnerableImpact {
162 confidentiality_impact: VulnerableConfidentialityImpactType::from_str(vectors)?,
163 integrity_impact: VulnerableIntegrityImpactType::from_str(vectors)?,
164 availability_impact: VulnerableAvailabilityImpactType::from_str(vectors)?,
165 };
166 let subsequent_impact = SubsequentImpact {
167 confidentiality_impact: SubsequentConfidentialityImpactType::from_str(vectors)?,
168 integrity_impact: SubsequentIntegrityImpactType::from_str(vectors)?,
169 availability_impact: SubsequentAvailabilityImpactType::from_str(vectors)?,
170 };
171 let exploit = ExploitMaturity::from_str(vectors).unwrap_or_default();
172 let environmental = Environmental {
173 confidentiality_requirements: ConfidentialityRequirements::from_str(vectors)
174 .unwrap_or_default(),
175 integrity_requirements: IntegrityRequirements::from_str(vectors).unwrap_or_default(),
176 availability_requirements: AvailabilityRequirements::from_str(vectors).unwrap_or_default(),
177 };
178 Ok(CVSS {
179 version: Version::V4_0,
180 vector_string: format!("{}{}", Version::V4_0, vectors),
181 exploit_ability,
182 vulnerable_impact,
183 subsequent_impact,
184 exploit,
185 environmental,
186 base_score: 0.0,
187 base_severity: SeverityType::None,
188 })
189 }
190}
191
192impl Display for CVSS {
193 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
194 write!(
195 f,
196 "CVSS:{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}",
197 self.version,
198 self.exploit_ability.attack_vector,
199 self.exploit_ability.attack_complexity,
200 self.exploit_ability.attack_requirements,
201 self.exploit_ability.privileges_required,
202 self.exploit_ability.user_interaction,
203 self.vulnerable_impact.confidentiality_impact,
204 self.vulnerable_impact.integrity_impact,
205 self.vulnerable_impact.availability_impact,
206 self.subsequent_impact.confidentiality_impact,
207 self.subsequent_impact.integrity_impact,
208 self.subsequent_impact.availability_impact,
209 self.environmental.confidentiality_requirements,
210 self.environmental.integrity_requirements,
211 self.environmental.availability_requirements,
212 self.exploit
213 )
214 }
215}
216impl FromStr for CVSS {
217 type Err = CVSSError;
218 fn from_str(vector_string: &str) -> Result<Self> {
219 let (version, vectors) = match vector_string.split_once('/') {
220 None => {
221 return Err(CVSSError::InvalidPrefix {
222 value: vector_string.to_string(),
223 })
224 }
225 Some((v, vector)) => {
226 let version = Version::from_str(v).unwrap_or_default();
227 (version, vector)
228 }
229 };
230 if matches!(version, Version::None) {
231 return Err(CVSSError::InvalidCVSSVersion {
232 value: version.to_string(),
233 expected: "4.0".to_string(),
234 });
235 }
236 let mut cvss = CVSS::vector_string(vectors)?;
237 cvss.base_score = cvss.base_score();
238 cvss.base_severity = SeverityType::from(cvss.base_score);
239 cvss.vector_string = cvss.to_string();
240 Ok(cvss)
241 }
242}
243
244impl CVSS {
245 fn base_score(&self) -> f32 {
246 if self.subsequent_impact.all_none() && self.vulnerable_impact.all_none() {
247 return 0.0;
248 }
249 let (eq1, eq2, eq3, eq4, eq5, eq6) = self.macro_vector();
250 let mv = format!("{}{}{}{}{}{}", eq1, eq2, eq3, eq4, eq5, eq6);
251 let score = lookup(&eq1, &eq2, &eq3, &eq4, &eq5, &eq6).unwrap_or(0.0);
252 let mut lower = 0;
253 let score_eq1_next_lower = if eq1 < 2 {
254 lower += 1;
255 lookup(&(eq1 + 1), &eq2, &eq3, &eq4, &eq5, &eq6)
256 } else {
257 None
258 };
259 let score_eq2_next_lower = if eq2 < 1 {
260 lower += 1;
261 lookup(&eq1, &(eq2 + 1), &eq3, &eq4, &eq5, &eq6)
262 } else {
263 None
264 };
265 let score_eq4_next_lower = if eq4 < 2 {
266 lower += 1;
267 lookup(&eq1, &eq2, &eq3, &(eq4 + 1), &eq5, &eq6)
268 } else {
269 None
270 };
271 let score_eq5_next_lower = if eq5 < 2 {
272 lower += 1;
273 lookup(&eq1, &eq2, &eq3, &eq4, &(eq5 + 1), &eq6)
274 } else {
275 None
276 };
277 let score_eq3eq6_next_lower = if (eq3 == 0 || eq3 == 1) && eq6 == 1 {
278 lower += 1;
279 lookup(&eq1, &eq2, &(eq3 + 1), &eq4, &eq5, &eq6)
280 } else if eq3 == 1 && eq6 == 0 {
281 lower += 1;
282 lookup(&eq1, &eq2, &eq3, &eq4, &eq5, &(eq6 + 1))
283 } else if eq3 == 0 && eq6 == 0 {
284 lower += 1;
287 let left = lookup(&eq1, &eq2, &eq3, &eq4, &eq5, &(eq6 + 1)).unwrap_or(0.0);
288 let right = lookup(&eq1, &eq2, &(eq3 + 1), &eq4, &eq5, &eq6).unwrap_or(0.0);
289 let max_score = right.max(left);
290 Some(max_score)
291 } else {
292 None
293 };
294 let max_vectors = self.max_vectors(mv);
296 let (
297 current_severity_distance_eq1,
298 current_severity_distance_eq2,
299 current_severity_distance_eq3eq6,
300 current_severity_distance_eq4,
301 current_severity_distance_eq5,
302 ) = self.severity_distances(max_vectors);
303 let step: f32 = 0.1;
304 let max_severity_eq1 = get_eq1245_max_severity(1, eq1).unwrap_or_default() * step;
306 let max_severity_eq2 = get_eq1245_max_severity(2, eq2).unwrap_or_default() * step;
307 let max_severity_eq3eq6 = get_eq36_max_severity(eq3, eq6).unwrap_or_default() * step;
308 let max_severity_eq4 = get_eq1245_max_severity(4, eq4).unwrap_or_default() * step;
309 let max_severity_eq5 = get_eq1245_max_severity(5, eq5).unwrap_or_default() * step;
310
311 let mut normalized_severity_eq1 = 0.0;
312 let mut normalized_severity_eq2 = 0.0;
313 let mut normalized_severity_eq3eq6 = 0.0;
314 let mut normalized_severity_eq4 = 0.0;
315 let mut normalized_severity_eq5 = 0.0;
316
317 if let Some(score_eq1_next_lower) = score_eq1_next_lower {
318 let available_distance_eq1 = score - score_eq1_next_lower;
319 let percent_to_next_eq1_severity = current_severity_distance_eq1 / max_severity_eq1;
320 normalized_severity_eq1 = available_distance_eq1 * percent_to_next_eq1_severity;
321 }
322 if let Some(score_eq2_next_lower) = score_eq2_next_lower {
323 let available_distance_eq2 = score - score_eq2_next_lower;
324 let percent_to_next_eq2_severity = current_severity_distance_eq2 / max_severity_eq2;
325 normalized_severity_eq2 = available_distance_eq2 * percent_to_next_eq2_severity
326 }
327 if let Some(score_eq3eq6_next_lower) = score_eq3eq6_next_lower {
328 let available_distance_eq3eq6 = score - score_eq3eq6_next_lower;
329 let percent_to_next_eq3eq6_severity = current_severity_distance_eq3eq6 / max_severity_eq3eq6;
330 normalized_severity_eq3eq6 = available_distance_eq3eq6 * percent_to_next_eq3eq6_severity;
331 }
332 if let Some(score_eq4_next_lower) = score_eq4_next_lower {
333 let available_distance_eq4 = score - score_eq4_next_lower;
334 let percent_to_next_eq4_severity = current_severity_distance_eq4 / max_severity_eq4;
335 normalized_severity_eq4 = available_distance_eq4 * percent_to_next_eq4_severity
336 }
337 if let Some(score_eq5_next_lower) = score_eq5_next_lower {
338 let available_distance_eq5 = score - score_eq5_next_lower;
339 let percent_to_next_eq5_severity = current_severity_distance_eq5 / max_severity_eq5;
340 normalized_severity_eq5 = available_distance_eq5 * percent_to_next_eq5_severity
341 }
342 let mut mean_distance = 0.0;
343 if lower != 0 {
344 mean_distance = (normalized_severity_eq1
345 + normalized_severity_eq2
346 + normalized_severity_eq3eq6
347 + normalized_severity_eq4
348 + normalized_severity_eq5)
349 / lower as f32;
350 }
351
352 roundup(score - mean_distance)
353 }
354 fn eq6(&self) -> Option<u32> {
357 if (self.environmental.confidentiality_requirements.is_high()
358 && self.vulnerable_impact.confidentiality_impact.is_high())
359 || (self.environmental.integrity_requirements.is_high()
360 && self.vulnerable_impact.integrity_impact.is_high())
361 || (self.environmental.availability_requirements.is_high()
362 && self.vulnerable_impact.availability_impact.is_high())
363 {
364 return Some(0);
365 } else if !(self.vulnerable_impact.confidentiality_impact.is_high()
366 || (self.environmental.integrity_requirements.is_high()
367 && self.vulnerable_impact.integrity_impact.is_high())
368 || (self.environmental.availability_requirements.is_high()
369 && self.vulnerable_impact.availability_impact.is_high()))
370 {
371 return Some(1);
372 }
373 None
374 }
375 fn max_vectors(&self, macro_vector: String) -> Vec<String> {
376 let mut vectors = vec![];
377 let get_index = |index| {
378 macro_vector
379 .chars()
380 .nth((index - 1) as usize)
381 .unwrap_or_default()
382 .to_digit(10)
383 .unwrap_or(0)
384 };
385 let eq1_maxes = get_eq1245_max_composed(1, get_index(1));
386 let eq2_maxes = get_eq1245_max_composed(2, get_index(2));
387 let eq3_eq6_maxes = get_eq36_max_composed(get_index(3), get_index(6));
388 let eq4_maxes = get_eq1245_max_composed(4, get_index(4));
389 let eq5_maxes = get_eq1245_max_composed(5, get_index(5));
390 for eq1_max in eq1_maxes {
392 for eq2_max in eq2_maxes.iter() {
393 for eq3_eq6_max in eq3_eq6_maxes.iter() {
394 for eq4_max in eq4_maxes.iter() {
395 for eq5_max in eq5_maxes.iter() {
396 vectors.push(format!(
397 "{}{}{}{}{}",
398 eq1_max, eq2_max, eq3_eq6_max, eq4_max, eq5_max
399 ));
400 }
401 }
402 }
403 }
404 }
405 vectors
406 }
407 fn severity_distances(&self, vectors: Vec<String>) -> (f32, f32, f32, f32, f32) {
408 let mut severity_distances = vec![];
410 for vector in vectors {
411 let max_vector = CVSS::vector_string(&vector);
412 if let Ok(max_vector) = max_vector {
413 let av = self.exploit_ability.attack_vector.score()
414 - max_vector.exploit_ability.attack_vector.score();
415 let pr = self.exploit_ability.privileges_required.score()
416 - max_vector.exploit_ability.privileges_required.score();
417 let ui = self.exploit_ability.user_interaction.score()
418 - max_vector.exploit_ability.user_interaction.score();
419
420 let ac = self.exploit_ability.attack_complexity.score()
421 - max_vector.exploit_ability.attack_complexity.score();
422 let at = self.exploit_ability.attack_requirements.score()
423 - max_vector.exploit_ability.attack_requirements.score();
424
425 let vc = self.vulnerable_impact.confidentiality_impact.score()
426 - max_vector.vulnerable_impact.confidentiality_impact.score();
427 let vi = self.vulnerable_impact.integrity_impact.score()
428 - max_vector.vulnerable_impact.integrity_impact.score();
429 let va = self.vulnerable_impact.availability_impact.score()
430 - max_vector.vulnerable_impact.availability_impact.score();
431
432 let sc = self.subsequent_impact.confidentiality_impact.score()
433 - max_vector.subsequent_impact.confidentiality_impact.score();
434 let si = self.subsequent_impact.integrity_impact.score()
435 - max_vector.subsequent_impact.integrity_impact.score();
436 let sa = self.subsequent_impact.availability_impact.score()
437 - max_vector.subsequent_impact.availability_impact.score();
438
439 let cr = self.environmental.confidentiality_requirements.score()
440 - max_vector
441 .environmental
442 .confidentiality_requirements
443 .score();
444 let ir = self.environmental.integrity_requirements.score()
445 - max_vector.environmental.integrity_requirements.score();
446 let ar = self.environmental.availability_requirements.score()
447 - max_vector.environmental.availability_requirements.score();
448 let all_severity_distances = vec![av, pr, ui, ac, at, vc, vi, va, sc, si, sa, cr, ir, ar];
449 if all_severity_distances.iter().any(|m| m < &0.0) {
451 continue;
452 }
453 severity_distances = all_severity_distances;
454 break;
455 }
457 }
458 severity_distances.reverse();
460 let (av, pr, ui, ac, at, vc, vi, va, sc, si, sa, cr, ir, ar) = (
461 severity_distances.pop().unwrap_or_default(),
462 severity_distances.pop().unwrap_or_default(),
463 severity_distances.pop().unwrap_or_default(),
464 severity_distances.pop().unwrap_or_default(),
465 severity_distances.pop().unwrap_or_default(),
466 severity_distances.pop().unwrap_or_default(),
467 severity_distances.pop().unwrap_or_default(),
468 severity_distances.pop().unwrap_or_default(),
469 severity_distances.pop().unwrap_or_default(),
470 severity_distances.pop().unwrap_or_default(),
471 severity_distances.pop().unwrap_or_default(),
472 severity_distances.pop().unwrap_or_default(),
473 severity_distances.pop().unwrap_or_default(),
474 severity_distances.pop().unwrap_or_default(),
475 );
476 let current_severity_distance_eq1 = av + pr + ui;
477 let current_severity_distance_eq2 = ac + at;
478 let current_severity_distance_eq3eq6 = vc + vi + va + cr + ir + ar;
479 let current_severity_distance_eq4 = sc + si + sa;
480 let current_severity_distance_eq5 = 0.0;
481 (
482 current_severity_distance_eq1,
483 current_severity_distance_eq2,
484 current_severity_distance_eq3eq6,
485 current_severity_distance_eq4,
486 current_severity_distance_eq5,
487 )
488 }
489 fn macro_vector(&self) -> (u32, u32, u32, u32, u32, u32) {
490 let eq1 = self.exploit_ability.eq1().unwrap_or_default();
491 let eq2 = self.exploit_ability.eq2().unwrap_or_default();
492 let eq3 = self.vulnerable_impact.eq3().unwrap_or_default();
493 let eq4 = self.subsequent_impact.eq4().unwrap_or_default();
494 let eq5 = self.exploit.eq5().unwrap_or_default();
495 let eq6 = self.eq6().unwrap_or_default();
496 (eq1, eq2, eq3, eq4, eq5, eq6)
497 }
498}
499fn roundup(input: f32) -> f32 {
501 let int_input = (input * 100.0) as u32;
502 if int_input % 10 < 5 {
503 (int_input / 10) as f32 / 10.0
504 } else {
505 let score_floor = ((int_input as f32) / 10.0).floor();
506 (score_floor + 1.0) / 10.0
507 }
508}
509#[cfg(test)]
510mod tests {
511 use std::collections::HashMap;
512 use std::str::FromStr;
513
514 use crate::v4::{roundup, CVSS};
515
516 #[test]
517 fn roundup_test() {
518 assert_eq!(roundup(0.12000), 0.1);
519 assert_eq!(roundup(0.15000), 0.2);
520 assert_eq!(roundup(0.94900), 0.9);
521 }
522 #[test]
523 fn cvss_score_test() {
524 let vs_map: HashMap<&'static str, f32> = HashMap::from_iter([
525 (
526 "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
527 10.0,
528 ),
529 (
530 "CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
531 9.4,
532 ),
533 (
534 "CVSS:4.0/AV:A/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
535 9.0,
536 ),
537 (
538 "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
539 8.9,
540 ),
541 (
542 "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
543 7.3,
544 ),
545 (
546 "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
547 6.1,
548 ),
549 (
550 "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:L/VA:L/SC:H/SI:H/SA:H/E:A",
551 2.4,
552 ),
553 (
554 "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:L/VA:N/SC:L/SI:N/SA:H/E:A",
555 2.0,
556 ),
557 (
558 "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:L/VA:N/SC:L/SI:N/SA:H/E:P/CR:H/IR:M/AR:L",
559 0.9,
560 ),
561 ]);
562 for (k, v) in vs_map.iter() {
563 let cvss = CVSS::from_str(k).unwrap();
564 assert_eq!(cvss.base_score, *v)
565 }
566 }
567}