1 /* Copyright (C) 2012-2021 Greenbone Networks GmbH
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /**
21 * @file
22 * @brief CVSS utility functions
23 *
24 * This file contains utility functions for handling CVSS v2 and v3.
25 * get_cvss_score_from_base_metrics calculates the CVSS base score from a CVSS
26 * base vector.
27 *
28 * CVSS v3.1:
29 *
30 * See equations at https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator and
31 * constants at https://www.first.org/cvss/v3.1/specification-document (section
32 * 7.4. Metric Values).
33 *
34 * CVSS v3.0:
35 *
36 * See equations at https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator and
37 * constants at https://www.first.org/cvss/v3.0/specification-document (section
38 * 8.4. Metric Levels).
39 *
40 * CVSS v2:
41 *
42 * The base equation is the foundation of CVSS scoring. The base equation is:
43 * BaseScore6
44 * = round_to_1_decimal(((0.6*Impact)+(0.4*Exploitability)–1.5)*f(Impact))
45 *
46 * Impact
47 * = 10.41*(1-(1-ConfImpact)*(1-IntegImpact)*(1-AvailImpact))
48 *
49 * Exploitability
50 * = 20* AccessVector*AccessComplexity*Authentication
51 *
52 * f(impact)= 0 if Impact=0, 1.176 otherwise
53 * AccessVector = case AccessVector of
54 * requires local access: 0.395
55 * adjacent network accessible: 0.646
56 * network accessible: 1.0
57 * AccessComplexity = case AccessComplexity of
58 * high: 0.35
59 * medium: 0.61
60 * low: 0.71
61 * Authentication = case Authentication of
62 * requires multiple instances of authentication: 0.45
63 * requires single instance of authentication: 0.56
64 * requires no authentication: 0.704
65 * ConfImpact = case ConfidentialityImpact of
66 * none: 0.0
67 * partial: 0.275
68 * complete: 0.660
69 * IntegImpact = case IntegrityImpact of
70 * none: 0.0
71 * partial: 0.275
72 * complete: 0.660
73 * AvailImpact = case AvailabilityImpact of
74 * none: 0.0
75 * partial: 0.275
76 * complete: 0.660
77 */
78
79 #include <glib.h>
80 #include <math.h>
81 #include <string.h>
82
83 #undef G_LOG_DOMAIN
84 /**
85 * @brief GLib log domain.
86 */
87 #define G_LOG_DOMAIN "libgvm base"
88
89 /* Static Headers. */
90
91 static double
92 get_cvss_score_from_base_metrics_v3 (const char *);
93
94 /* CVSS v2. */
95
96 // clang-format off
97 /**
98 * @brief AccessVector (AV) Constants.
99 */
100 #define AV_NETWORK 1.0 /**< Access Vector Network. */
101 #define AV_ADJACENT_NETWORK 0.646 /**< Access Vector Adjacent Network. */
102 #define AV_LOCAL 0.395 /**< Access Vector Local. */
103
104 /**
105 * @brief AccessComplexity (AC) Constants.
106 */
107 #define AC_LOW 0.71 /**< Access Complexity Low. */
108 #define AC_MEDIUM 0.61 /**< Access Complexity Medium. */
109 #define AC_HIGH 0.35 /**< Access Complexity High. */
110
111 /**
112 * @brief Authentication (Au) Constants.
113 */
114 #define Au_MULTIPLE_INSTANCES 0.45 /**< Authentication multiple instances. */
115 #define Au_SINGLE_INSTANCE 0.56 /**< Authentication single instances. */
116 #define Au_NONE 0.704 /**< No Authentication. */
117
118 /**
119 * @brief ConfidentialityImpact (C) Constants.
120 */
121 #define C_NONE 0.0 /**< No Confidentiality Impact. */
122 #define C_PARTIAL 0.275 /**< Partial Confidentiality Impact. */
123 #define C_COMPLETE 0.660 /**< Complete Confidentiality Impact. */
124
125 /**
126 * @brief IntegrityImpact (I) Constants.
127 */
128 #define I_NONE 0.0 /**< No Integrity Impact. */
129 #define I_PARTIAL 0.275 /**< Partial Integrity Impact. */
130 #define I_COMPLETE 0.660 /**< Complete Integrity Impact. */
131
132 /**
133 * @brief AvailabilityImpact (A) Constants.
134 */
135 #define A_NONE 0.0 /**< No Availability Impact. */
136 #define A_PARTIAL 0.275 /**< Partial Availability Impact. */
137 #define A_COMPLETE 0.660 /**< Complete Availability Impact. */
138 // clang-format on
139
140 /**
141 * @brief Base metrics.
142 */
143 enum base_metrics
144 {
145 A, /**< Availability Impact. */
146 I, /**< Integrity Impact. */
147 C, /**< Confidentiality Impact. */
148 Au, /**< Authentication. */
149 AC, /**< Access Complexity. */
150 AV /**< Access Vector. */
151 };
152
153 /**
154 * @brief Describe a CVSS impact element.
155 */
156 struct impact_item
157 {
158 const char *name; /**< Impact element name */
159 double nvalue; /**< Numerical value */
160 };
161
162 /**
163 * @brief Describe a CVSS metrics.
164 */
165 struct cvss
166 {
167 double conf_impact; /**< Confidentiality impact. */
168 double integ_impact; /**< Integrity impact. */
169 double avail_impact; /**< Availability impact. */
170 double access_vector; /**< Access vector. */
171 double access_complexity; /**< Access complexity. */
172 double authentication; /**< Authentication. */
173 };
174
175 static const struct impact_item impact_map[][3] = {
176 [A] =
177 {
178 {"N", A_NONE},
179 {"P", A_PARTIAL},
180 {"C", A_COMPLETE},
181 },
182 [I] =
183 {
184 {"N", I_NONE},
185 {"P", I_PARTIAL},
186 {"C", I_COMPLETE},
187 },
188 [C] =
189 {
190 {"N", C_NONE},
191 {"P", C_PARTIAL},
192 {"C", C_COMPLETE},
193 },
194 [Au] =
195 {
196 {"N", Au_NONE},
197 {"M", Au_MULTIPLE_INSTANCES},
198 {"S", Au_SINGLE_INSTANCE},
199 },
200 [AV] =
201 {
202 {"N", AV_NETWORK},
203 {"A", AV_ADJACENT_NETWORK},
204 {"L", AV_LOCAL},
205 },
206 [AC] =
207 {
208 {"L", AC_LOW},
209 {"M", AC_MEDIUM},
210 {"H", AC_HIGH},
211 },
212 };
213
214 /**
215 * @brief Determine base metric enumeration from a string.
216 *
217 * @param[in] str Base metric in string form, for example "A".
218 * @param[out] res Where to write the desired value.
219 *
220 * @return 0 on success, -1 on error.
221 */
222 static int
toenum(const char * str,enum base_metrics * res)223 toenum (const char *str, enum base_metrics *res)
224 {
225 int rc = 0; /* let's be optimistic */
226
227 if (g_strcmp0 (str, "A") == 0)
228 *res = A;
229 else if (g_strcmp0 (str, "I") == 0)
230 *res = I;
231 else if (g_strcmp0 (str, "C") == 0)
232 *res = C;
233 else if (g_strcmp0 (str, "Au") == 0)
234 *res = Au;
235 else if (g_strcmp0 (str, "AU") == 0)
236 *res = Au;
237 else if (g_strcmp0 (str, "AV") == 0)
238 *res = AV;
239 else if (g_strcmp0 (str, "AC") == 0)
240 *res = AC;
241 else
242 rc = -1;
243
244 return rc;
245 }
246
247 /**
248 * @brief Calculate Impact Sub Score.
249 *
250 * @param[in] cvss Contains the subscores associated
251 * to the metrics.
252 *
253 * @return The resulting subscore.
254 */
255 static double
get_impact_subscore(const struct cvss * cvss)256 get_impact_subscore (const struct cvss *cvss)
257 {
258 return 10.41
259 * (1
260 - (1 - cvss->conf_impact) * (1 - cvss->integ_impact)
261 * (1 - cvss->avail_impact));
262 }
263
264 /**
265 * @brief Calculate Exploitability Sub Score.
266 *
267 * @param[in] cvss Contains the subscores associated
268 * to the metrics.
269 *
270 * @return The resulting subscore.
271 */
272 static double
get_exploitability_subscore(const struct cvss * cvss)273 get_exploitability_subscore (const struct cvss *cvss)
274 {
275 return 20 * cvss->access_vector * cvss->access_complexity
276 * cvss->authentication;
277 }
278
279 /**
280 * @brief Set impact score from string representation.
281 *
282 * @param[in] value The literal value associated to the metric.
283 * @param[in] metric The enumeration constant identifying the metric.
284 * @param[out] cvss The structure to update with the score.
285 *
286 * @return 0 on success, -1 on error.
287 */
288 static inline int
set_impact_from_str(const char * value,enum base_metrics metric,struct cvss * cvss)289 set_impact_from_str (const char *value, enum base_metrics metric,
290 struct cvss *cvss)
291 {
292 int i;
293
294 for (i = 0; i < 3; i++)
295 {
296 const struct impact_item *impact;
297
298 impact = &impact_map[metric][i];
299
300 if (g_strcmp0 (impact->name, value) == 0)
301 {
302 switch (metric)
303 {
304 case A:
305 cvss->avail_impact = impact->nvalue;
306 break;
307
308 case I:
309 cvss->integ_impact = impact->nvalue;
310 break;
311
312 case C:
313 cvss->conf_impact = impact->nvalue;
314 break;
315
316 case Au:
317 cvss->authentication = impact->nvalue;
318 break;
319
320 case AV:
321 cvss->access_vector = impact->nvalue;
322 break;
323
324 case AC:
325 cvss->access_complexity = impact->nvalue;
326 break;
327
328 default:
329 return -1;
330 }
331 return 0;
332 }
333 }
334 return -1;
335 }
336
337 /**
338 * @brief Final CVSS score computation helper.
339 *
340 * @param[in] cvss The CVSS structure that contains the
341 * different metrics and associated scores.
342 *
343 * @return the CVSS score, as a double.
344 */
345 static double
__get_cvss_score(struct cvss * cvss)346 __get_cvss_score (struct cvss *cvss)
347 {
348 double impact = 1.176;
349 double impact_sub;
350 double exploitability_sub;
351
352 impact_sub = get_impact_subscore (cvss);
353 exploitability_sub = get_exploitability_subscore (cvss);
354
355 if (impact_sub < 0.1)
356 impact = 0.0;
357
358 return (((0.6 * impact_sub) + (0.4 * exploitability_sub) - 1.5) * impact)
359 + 0.0;
360 }
361
362 /**
363 * @brief Calculate CVSS Score.
364 *
365 * @param cvss_str Base vector string from which to compute score.
366 *
367 * @return The resulting score. -1 upon error during parsing.
368 */
369 double
get_cvss_score_from_base_metrics(const char * cvss_str)370 get_cvss_score_from_base_metrics (const char *cvss_str)
371 {
372 struct cvss cvss;
373 char *token, *base_str, *base_metrics;
374
375 if (cvss_str == NULL)
376 return -1.0;
377
378 if (g_str_has_prefix (cvss_str, "CVSS:3.1/")
379 || g_str_has_prefix (cvss_str, "CVSS:3.0/"))
380 return get_cvss_score_from_base_metrics_v3 (cvss_str
381 + strlen ("CVSS:3.X/"));
382
383 memset (&cvss, 0x00, sizeof (struct cvss));
384
385 base_str = base_metrics = g_strdup_printf ("%s/", cvss_str);
386
387 while ((token = strchr (base_metrics, '/')) != NULL)
388 {
389 char *token2 = strtok (base_metrics, ":");
390 char *metric_name = token2;
391 char *metric_value;
392 enum base_metrics mval;
393 int rc;
394
395 *token++ = '\0';
396
397 if (metric_name == NULL)
398 goto ret_err;
399
400 metric_value = strtok (NULL, ":");
401
402 if (metric_value == NULL)
403 goto ret_err;
404
405 rc = toenum (metric_name, &mval);
406 if (rc)
407 goto ret_err;
408
409 if (set_impact_from_str (metric_value, mval, &cvss))
410 goto ret_err;
411
412 base_metrics = token;
413 }
414
415 g_free (base_str);
416 return __get_cvss_score (&cvss);
417
418 ret_err:
419 g_free (base_str);
420 return (double) -1;
421 }
422
423 /* CVSS v3. */
424
425 /**
426 * @brief Round final score as in spec.
427 *
428 * @param cvss CVSS score.
429 *
430 * @return Rounded score.
431 */
432 static double
roundup(double cvss)433 roundup (double cvss)
434 {
435 int trim;
436
437 /* "Roundup returns the smallest number, specified to 1 decimal place,
438 * that is equal to or higher than its input. For example, Roundup (4.02)
439 * returns 4.1; and Roundup (4.00) returns 4.0." */
440
441 /* 3.020000000 => 3.1 */
442 /* 3.000000001 => 3.0 */
443 /* 5.299996 => 5.3 */
444 /* 5.500320 => 5.6 */
445
446 trim = round (cvss * 100000);
447 if ((trim % 10000) == 0)
448 return ((double) trim) / 100000;
449 return (floor (trim / 10000) + 1) / 10.0;
450 }
451
452 /**
453 * @brief Get impact.
454 *
455 * @param value Metric value.
456 *
457 * @return Impact.
458 */
459 static double
v3_impact(const char * value)460 v3_impact (const char *value)
461 {
462 if (strcasecmp (value, "N") == 0)
463 return 0.0;
464 if (strcasecmp (value, "L") == 0)
465 return 0.22;
466 if (strcasecmp (value, "H") == 0)
467 return 0.56;
468 return -1.0;
469 }
470
471 /**
472 * @brief Calculate CVSS Score.
473 *
474 * @param cvss_str Vector from which to compute score, without prefix.
475 *
476 * @return CVSS score, or -1 on error.
477 */
478 static double
get_cvss_score_from_base_metrics_v3(const char * cvss_str)479 get_cvss_score_from_base_metrics_v3 (const char *cvss_str)
480 {
481 gchar **split, **point;
482 int scope_changed;
483 double impact_conf, impact_integ, impact_avail;
484 double vector, complexity, privilege, user;
485 double isc_base, impact, exploitability, base;
486
487 /* https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator
488 * https://www.first.org/cvss/v3.1/specification-document
489 * https://www.first.org/cvss/v3.0/specification-document */
490
491 scope_changed = -1;
492 impact_conf = -1.0;
493 impact_integ = -1.0;
494 impact_avail = -1.0;
495 vector = -1.0;
496 complexity = -1.0;
497 privilege = -1.0;
498 user = -1.0;
499
500 /* AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N */
501
502 split = g_strsplit (cvss_str, "/", 0);
503 point = split;
504 while (*point)
505 {
506 /* Scope. */
507 if (strncasecmp ("S:", *point, 2) == 0)
508 {
509 if (strcasecmp (*point + 2, "U") == 0)
510 scope_changed = 0;
511 else if (strcasecmp (*point + 2, "C") == 0)
512 scope_changed = 1;
513 }
514
515 /* Confidentiality. */
516 if (strncasecmp ("C:", *point, 2) == 0)
517 impact_conf = v3_impact (*point + 2);
518
519 /* Integrity. */
520 if (strncasecmp ("I:", *point, 2) == 0)
521 impact_integ = v3_impact (*point + 2);
522
523 /* Availability. */
524 if (strncasecmp ("A:", *point, 2) == 0)
525 impact_avail = v3_impact (*point + 2);
526
527 /* Attack Vector. */
528 if (strncasecmp ("AV:", *point, 3) == 0)
529 {
530 if (strcasecmp (*point + 3, "N") == 0)
531 vector = 0.85;
532 else if (strcasecmp (*point + 3, "A") == 0)
533 vector = 0.62;
534 else if (strcasecmp (*point + 3, "L") == 0)
535 vector = 0.55;
536 else if (strcasecmp (*point + 3, "P") == 0)
537 vector = 0.2;
538 }
539
540 /* Attack Complexity. */
541 if (strncasecmp ("AC:", *point, 3) == 0)
542 {
543 if (strcasecmp (*point + 3, "L") == 0)
544 complexity = 0.77;
545 else if (strcasecmp (*point + 3, "H") == 0)
546 complexity = 0.44;
547 }
548
549 /* Privileges Required. */
550 if (strncasecmp ("PR:", *point, 3) == 0)
551 {
552 if (strcasecmp (*point + 3, "N") == 0)
553 privilege = 0.85;
554 else if (strcasecmp (*point + 3, "L") == 0)
555 privilege = 0.62;
556 else if (strcasecmp (*point + 3, "H") == 0)
557 privilege = 0.27;
558 else
559 privilege = -1.0;
560 }
561
562 /* User Interaction. */
563 if (strncasecmp ("UI:", *point, 3) == 0)
564 {
565 if (strcasecmp (*point + 3, "N") == 0)
566 user = 0.85;
567 else if (strcasecmp (*point + 3, "R") == 0)
568 user = 0.62;
569 }
570
571 point++;
572 }
573
574 g_strfreev (split);
575
576 /* All of the base metrics are required. */
577
578 if (scope_changed == -1 || impact_conf == -1.0 || impact_integ == -1.0
579 || impact_avail == -1.0 || vector == -1.0 || complexity == -1.0
580 || privilege == -1.0 || user == -1.0)
581 return -1.0;
582
583 /* Privileges Required has a special case for S:C. */
584
585 if (scope_changed && privilege == 0.62)
586 privilege = 0.68;
587 else if (scope_changed && privilege == 0.27)
588 privilege = 0.5;
589
590 /* Impact. */
591
592 isc_base = 1 - ((1 - impact_conf) * (1 - impact_integ) * (1 - impact_avail));
593
594 if (scope_changed)
595 impact = 7.52 * (isc_base - 0.029) - 3.25 * pow ((isc_base - 0.02), 15);
596 else
597 impact = 6.42 * isc_base;
598
599 if (impact <= 0)
600 return 0.0;
601
602 /* Exploitability. */
603
604 exploitability = 8.22 * vector * complexity * privilege * user;
605
606 /* Final. */
607
608 if (scope_changed)
609 base = 1.08 * (impact + exploitability);
610 else
611 base = impact + exploitability;
612
613 if (base > 10.0)
614 return 10.0;
615
616 return roundup (base);
617 }
618