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 }