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