go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/llx/score.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package llx
     5  
     6  import (
     7  	"errors"
     8  	"math"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  const (
    14  	scoreTypeMondoo = iota
    15  	scoreTypeCVSSv3
    16  )
    17  
    18  func scoreVector(num int32) ([]byte, error) {
    19  	if num > 100 || num < 0 {
    20  		return nil, errors.New("Not a valid score (" + strconv.FormatInt(int64(num), 10) + ")")
    21  	}
    22  
    23  	return []byte{scoreTypeMondoo, byte(num & 0xff)}, nil
    24  }
    25  
    26  func scoreString(vector string) ([]byte, error) {
    27  	switch {
    28  	case strings.HasPrefix(vector, "CVSS:3.0/"):
    29  		return cvssv3vector(vector[8:]), nil
    30  	case strings.HasPrefix(vector, "CVSS:3.1/"):
    31  		return cvssv3vector(vector[8:]), nil
    32  	default:
    33  		return nil, errors.New("Cannot parse this CVSS vector into a Mondoo score")
    34  	}
    35  }
    36  
    37  // ScoreString turns a given score data into a printable string
    38  func ScoreString(b []byte) string {
    39  	switch b[0] {
    40  	case scoreTypeMondoo:
    41  		s := strconv.Itoa(int(b[1]))
    42  		return s
    43  	case scoreTypeCVSSv3:
    44  		num := cvssv3score(b)
    45  		s := strconv.FormatFloat(num, 'f', 2, 64)
    46  		return s + " (CVSSv3)"
    47  	default:
    48  		return "<unknown-score-type>"
    49  	}
    50  }
    51  
    52  func scoreValue(vector []byte) (int, error) {
    53  	switch vector[0] {
    54  	case scoreTypeMondoo:
    55  		return int(vector[1]), nil
    56  	case scoreTypeCVSSv3:
    57  		num := cvssv3score(vector)
    58  		return int(100 - (num * 10)), nil
    59  	default:
    60  		return 0, errors.New("unknown score value")
    61  	}
    62  }
    63  
    64  var (
    65  	// all metrics are in order from highest to lowest; for example:
    66  	// AV = [ Network, Adjacent, Local, Physical ]
    67  	cvssv3multipliersAV     = []float64{0.85, 0.62, 0.55, 0.2}
    68  	cvssv3multipliersAC     = []float64{0.77, 0.44}
    69  	cvssv3multipliersPR     = []float64{0.85, 0.68, 0.5, 0.85, 0.62, 0.27} // 3 x changed, 3 x unchanged
    70  	cvssv3multipliersUI     = []float64{0.85, 0.62}
    71  	cvssv3multipliersCIA    = []float64{0.56, 0.22, 0}
    72  	cvssv3multipliersE      = []float64{1, 1, 0.97, 0.94, 0.91}
    73  	cvssv3multipliersRL     = []float64{1, 1, 0.97, 0.96, 0.95}
    74  	cvssv3multipliersRC     = []float64{1, 1, 0.96, 0.92}
    75  	cvssv3multipliersCRIRAR = []float64{1, 1.5, 1, 0.5}
    76  )
    77  
    78  func cvssv3vector(s string) []byte {
    79  	res := make([]byte, 9)
    80  	res[0] = scoreTypeCVSSv3
    81  
    82  	// CVSS:3.1 /AV:_/AC:_/PR:_/UI:_/S:_/C:_/I:_/A:_
    83  	//          0123456789_123456789_123456789_12345
    84  
    85  	// AV:
    86  	switch s[4] {
    87  	case 'N', 'n':
    88  		res[1] = 0
    89  	case 'A', 'a':
    90  		res[1] = 1
    91  	case 'L', 'l':
    92  		res[1] = 2
    93  	case 'P', 'p':
    94  		res[1] = 3
    95  	}
    96  
    97  	// AC:
    98  	switch s[9] {
    99  	case 'L', 'l':
   100  		res[2] = 0
   101  	case 'H', 'h':
   102  		res[2] = 1
   103  	}
   104  
   105  	// PR:
   106  	switch s[23] {
   107  	case 'C':
   108  		res[5] = 1
   109  		switch s[14] {
   110  		case 'N', 'n':
   111  			res[3] = 0
   112  		case 'L', 'l':
   113  			res[3] = 1
   114  		case 'H', 'h':
   115  			res[3] = 2
   116  		}
   117  	case 'U':
   118  		res[4] = 0
   119  		switch s[14] {
   120  		case 'N', 'n':
   121  			res[3] = 3
   122  		case 'L', 'l':
   123  			res[3] = 4
   124  		case 'H', 'h':
   125  			res[3] = 5
   126  		}
   127  	}
   128  
   129  	// UI:
   130  	switch s[19] {
   131  	case 'N', 'n':
   132  		res[4] = 0
   133  	case 'R', 'r':
   134  		res[4] = 1
   135  	}
   136  
   137  	// C / I / A:
   138  	for i := 0; i <= 2; i++ {
   139  		switch s[27+i*4] {
   140  		case 'H', 'h':
   141  			res[6+i] = 0
   142  		case 'L', 'l':
   143  			res[6+i] = 1
   144  		case 'N', 'n':
   145  			res[6+i] = 2
   146  		}
   147  	}
   148  
   149  	return res
   150  }
   151  
   152  // s: AV AC PR S UI C I A
   153  func cvssv3score(s []byte) float64 {
   154  	// Calculation:
   155  	// https://www.first.org/cvss/specification-document
   156  	// Chapter 7. CVSS v3.1 Equations
   157  
   158  	c := cvssv3multipliersCIA[s[6]]
   159  	i := cvssv3multipliersCIA[s[7]]
   160  	a := cvssv3multipliersCIA[s[8]]
   161  	iss := 1 - ((1 - c) * (1 - i) * (1 - a))
   162  
   163  	var impact float64
   164  	changed := s[5]
   165  	if changed == 0 {
   166  		impact = 6.42 * iss
   167  	} else {
   168  		impact = 7.52*(iss-0.029) - 3.25*math.Pow(iss-0.02, 15)
   169  	}
   170  
   171  	exploitability := 8.22 * cvssv3multipliersAV[s[1]] * cvssv3multipliersAC[s[2]] * cvssv3multipliersPR[s[3]] * cvssv3multipliersUI[s[4]]
   172  
   173  	if iss <= 0 {
   174  		return 0
   175  	}
   176  
   177  	var base float64
   178  	if changed == 0 {
   179  		base = impact + exploitability
   180  	} else {
   181  		base = 1.08 * (impact + exploitability)
   182  	}
   183  
   184  	return math.Ceil(math.Min(base, 10)*10.0) / 10.0
   185  }