cvsscalc31.js
1 /* Copyright (c) 2019, FIRST.ORG, INC. 2 * All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 5 * following conditions are met: 6 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 7 * disclaimer. 8 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 9 * following disclaimer in the documentation and/or other materials provided with the distribution. 10 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote 11 * products derived from this software without specific prior written permission. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 16 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 17 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 18 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 19 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 */ 21 22 /* This JavaScript contains two main functions. Both take CVSS metric values and calculate CVSS scores for Base, 23 * Temporal and Environmental metric groups, their associated severity ratings, and an overall Vector String. 24 * 25 * Use CVSS31.calculateCVSSFromMetrics if you wish to pass metric values as individual parameters. 26 * Use CVSS31.calculateCVSSFromVector if you wish to pass metric values as a single Vector String. 27 * 28 * Changelog 29 * 30 * 2019-06-01 Darius Wiles Updates for CVSS version 3.1: 31 * 32 * 1) The CVSS31.roundUp1 function now performs rounding using integer arithmetic to 33 * eliminate problems caused by tiny errors introduced during JavaScript math 34 * operations. Thanks to Stanislav Kontar of Red Hat for suggesting and testing 35 * various implementations. 36 * 37 * 2) Environmental formulas changed to prevent the Environmental Score decreasing when 38 * the value of an Environmental metric is raised. The problem affected a small 39 * percentage of CVSS v3.0 metrics. The change is to the modifiedImpact 40 * formula, but only affects scores where the Modified Scope is Changed (or the 41 * Scope is Changed if Modified Scope is Not Defined). 42 * 43 * 3) The JavaScript object containing everything in this file has been renamed from 44 * "CVSS" to "CVSS31" to allow both objects to be included without causing a 45 * naming conflict. 46 * 47 * 4) Variable names and code order have changed to more closely reflect the formulas 48 * in the CVSS v3.1 Specification Document. 49 * 50 * 5) A successful call to calculateCVSSFromMetrics now returns sub-formula values. 51 * 52 * Note that some sets of metrics will produce different scores between CVSS v3.0 and 53 * v3.1 as a result of changes 1 and 2. See the explanation of changes between these 54 * two standards in the CVSS v3.1 User Guide for more details. 55 * 56 * 2018-02-15 Darius Wiles Added a missing pair of parentheses in the Environmental score, specifically 57 * in the code setting envScore in the main clause (not the else clause). It was changed 58 * from "min (...), 10" to "min ((...), 10)". This correction does not alter any final 59 * Environmental scores. 60 * 61 * 2015-08-04 Darius Wiles Added CVSS.generateXMLFromMetrics and CVSS.generateXMLFromVector functions to return 62 * XML string representations of: a set of metric values; or a Vector String respectively. 63 * Moved all constants and functions to an object named "CVSS" to 64 * reduce the chance of conflicts in global variables when this file is combined with 65 * other JavaScript code. This will break all existing code that uses this file until 66 * the string "CVSS." is prepended to all references. The "Exploitability" metric has been 67 * renamed "Exploit Code Maturity" in the specification, so the same change has been made 68 * in the code in this file. 69 * 70 * 2015-04-24 Darius Wiles Environmental formula modified to eliminate undesirable behavior caused by subtle 71 * differences in rounding between Temporal and Environmental formulas that often 72 * caused the latter to be 0.1 lower than than the former when all Environmental 73 * metrics are "Not defined". Also added a RoundUp1 function to simplify formulas. 74 * 75 * 2015-04-09 Darius Wiles Added calculateCVSSFromVector function, license information, cleaned up code and improved 76 * comments. 77 * 78 * 2014-12-12 Darius Wiles Initial release for CVSS 3.0 Preview 2. 79 */ 80 81 // Constants used in the formula. They are not declared as "const" to avoid problems in older browsers. 82 83 var CVSS31 = {}; 84 85 CVSS31.CVSSVersionIdentifier = "CVSS:3.1"; 86 CVSS31.exploitabilityCoefficient = 8.22; 87 CVSS31.scopeCoefficient = 1.08; 88 89 // A regular expression to validate that a CVSS 3.1 vector string is well formed. It checks metrics and metric 90 // values. It does not check that a metric is specified more than once and it does not check that all base 91 // metrics are present. These checks need to be performed separately. 92 93 CVSS31.vectorStringRegex_31 = /^CVSS:3\.1\/((AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XUNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])\/)*(AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XUNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$/; 94 95 96 // Associative arrays mapping each metric value to the constant defined in the CVSS scoring formula in the CVSS v3.1 97 // specification. 98 99 CVSS31.Weight = { 100 AV: { N: 0.85, A: 0.62, L: 0.55, P: 0.2}, 101 AC: { H: 0.44, L: 0.77}, 102 PR: { U: {N: 0.85, L: 0.62, H: 0.27}, // These values are used if Scope is Unchanged 103 C: {N: 0.85, L: 0.68, H: 0.5}}, // These values are used if Scope is Changed 104 UI: { N: 0.85, R: 0.62}, 105 S: { U: 6.42, C: 7.52}, // Note: not defined as constants in specification 106 CIA: { N: 0, L: 0.22, H: 0.56}, // C, I and A have the same weights 107 108 E: { X: 1, U: 0.91, P: 0.94, F: 0.97, H: 1}, 109 RL: { X: 1, O: 0.95, T: 0.96, W: 0.97, U: 1}, 110 RC: { X: 1, U: 0.92, R: 0.96, C: 1}, 111 112 CIAR: { X: 1, L: 0.5, M: 1, H: 1.5} // CR, IR and AR have the same weights 113 }; 114 115 116 // Severity rating bands, as defined in the CVSS v3.1 specification. 117 118 CVSS31.severityRatings = [ { name: "None", bottom: 0.0, top: 0.0}, 119 { name: "Low", bottom: 0.1, top: 3.9}, 120 { name: "Medium", bottom: 4.0, top: 6.9}, 121 { name: "High", bottom: 7.0, top: 8.9}, 122 { name: "Critical", bottom: 9.0, top: 10.0} ]; 123 124 125 126 127 /* ** CVSS31.calculateCVSSFromMetrics ** 128 * 129 * Takes Base, Temporal and Environmental metric values as individual parameters. Their values are in the short format 130 * defined in the CVSS v3.1 standard definition of the Vector String. For example, the AttackComplexity parameter 131 * should be either "H" or "L". 132 * 133 * Returns Base, Temporal and Environmental scores, severity ratings, and an overall Vector String. All Base metrics 134 * are required to generate this output. All Temporal and Environmental metric values are optional. Any that are not 135 * passed default to "X" ("Not Defined"). 136 * 137 * The output is an object which always has a property named "success". 138 * 139 * If no errors are encountered, success is Boolean "true", and the following other properties are defined containing 140 * scores, severities and a vector string: 141 * baseMetricScore, baseSeverity, 142 * temporalMetricScore, temporalSeverity, 143 * environmentalMetricScore, environmentalSeverity, 144 * vectorString 145 * 146 * The following properties are also defined, and contain sub-formula values: 147 * baseISS, baseImpact, baseExploitability, 148 * environmentalMISS, environmentalModifiedImpact, environmentalModifiedExploitability 149 * 150 * 151 * If errors are encountered, success is Boolean "false", and the following other properties are defined: 152 * errorType - a string indicating the error. Either: 153 * "MissingBaseMetric", if at least one Base metric has not been defined; or 154 * "UnknownMetricValue", if at least one metric value is invalid. 155 * errorMetrics - an array of strings representing the metrics at fault. The strings are abbreviated versions of the 156 * metrics, as defined in the CVSS v3.1 standard definition of the Vector String. 157 */ 158 CVSS31.calculateCVSSFromMetrics = function ( 159 AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability, 160 ExploitCodeMaturity, RemediationLevel, ReportConfidence, 161 ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement, 162 ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope, 163 ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability) { 164 165 // If input validation fails, this array is populated with strings indicating which metrics failed validation. 166 var badMetrics = []; 167 168 // ENSURE ALL BASE METRICS ARE DEFINED 169 // 170 // We need values for all Base Score metrics to calculate scores. 171 // If any Base Score parameters are undefined, create an array of missing metrics and return it with an error. 172 173 if (typeof AttackVector === "undefined" || AttackVector === "") { badMetrics.push("AV"); } 174 if (typeof AttackComplexity === "undefined" || AttackComplexity === "") { badMetrics.push("AC"); } 175 if (typeof PrivilegesRequired === "undefined" || PrivilegesRequired === "") { badMetrics.push("PR"); } 176 if (typeof UserInteraction === "undefined" || UserInteraction === "") { badMetrics.push("UI"); } 177 if (typeof Scope === "undefined" || Scope === "") { badMetrics.push("S"); } 178 if (typeof Confidentiality === "undefined" || Confidentiality === "") { badMetrics.push("C"); } 179 if (typeof Integrity === "undefined" || Integrity === "") { badMetrics.push("I"); } 180 if (typeof Availability === "undefined" || Availability === "") { badMetrics.push("A"); } 181 182 if (badMetrics.length > 0) { 183 return { success: false, errorType: "MissingBaseMetric", errorMetrics: badMetrics }; 184 } 185 186 187 // STORE THE METRIC VALUES THAT WERE PASSED AS PARAMETERS 188 // 189 // Temporal and Environmental metrics are optional, so set them to "X" ("Not Defined") if no value was passed. 190 191 var AV = AttackVector; 192 var AC = AttackComplexity; 193 var PR = PrivilegesRequired; 194 var UI = UserInteraction; 195 var S = Scope; 196 var C = Confidentiality; 197 var I = Integrity; 198 var A = Availability; 199 200 var E = ExploitCodeMaturity || "X"; 201 var RL = RemediationLevel || "X"; 202 var RC = ReportConfidence || "X"; 203 204 var CR = ConfidentialityRequirement || "X"; 205 var IR = IntegrityRequirement || "X"; 206 var AR = AvailabilityRequirement || "X"; 207 var MAV = ModifiedAttackVector || "X"; 208 var MAC = ModifiedAttackComplexity || "X"; 209 var MPR = ModifiedPrivilegesRequired || "X"; 210 var MUI = ModifiedUserInteraction || "X"; 211 var MS = ModifiedScope || "X"; 212 var MC = ModifiedConfidentiality || "X"; 213 var MI = ModifiedIntegrity || "X"; 214 var MA = ModifiedAvailability || "X"; 215 216 217 // CHECK VALIDITY OF METRIC VALUES 218 // 219 // Use the Weight object to ensure that, for every metric, the metric value passed is valid. 220 // If any invalid values are found, create an array of their metrics and return it with an error. 221 // 222 // The Privileges Required (PR) weight depends on Scope, but when checking the validity of PR we must not assume 223 // that the given value for Scope is valid. We therefore always look at the weights for Unchanged Scope when 224 // performing this check. The same applies for validation of Modified Privileges Required (MPR). 225 // 226 // The Weights object does not contain "X" ("Not Defined") values for Environmental metrics because we replace them 227 // with their Base metric equivalents later in the function. For example, an MAV of "X" will be replaced with the 228 // value given for AV. We therefore need to explicitly allow a value of "X" for Environmental metrics. 229 230 if (!CVSS31.Weight.AV.hasOwnProperty(AV)) { badMetrics.push("AV"); } 231 if (!CVSS31.Weight.AC.hasOwnProperty(AC)) { badMetrics.push("AC"); } 232 if (!CVSS31.Weight.PR.U.hasOwnProperty(PR)) { badMetrics.push("PR"); } 233 if (!CVSS31.Weight.UI.hasOwnProperty(UI)) { badMetrics.push("UI"); } 234 if (!CVSS31.Weight.S.hasOwnProperty(S)) { badMetrics.push("S"); } 235 if (!CVSS31.Weight.CIA.hasOwnProperty(C)) { badMetrics.push("C"); } 236 if (!CVSS31.Weight.CIA.hasOwnProperty(I)) { badMetrics.push("I"); } 237 if (!CVSS31.Weight.CIA.hasOwnProperty(A)) { badMetrics.push("A"); } 238 239 if (!CVSS31.Weight.E.hasOwnProperty(E)) { badMetrics.push("E"); } 240 if (!CVSS31.Weight.RL.hasOwnProperty(RL)) { badMetrics.push("RL"); } 241 if (!CVSS31.Weight.RC.hasOwnProperty(RC)) { badMetrics.push("RC"); } 242 243 if (!(CR === "X" || CVSS31.Weight.CIAR.hasOwnProperty(CR))) { badMetrics.push("CR"); } 244 if (!(IR === "X" || CVSS31.Weight.CIAR.hasOwnProperty(IR))) { badMetrics.push("IR"); } 245 if (!(AR === "X" || CVSS31.Weight.CIAR.hasOwnProperty(AR))) { badMetrics.push("AR"); } 246 if (!(MAV === "X" || CVSS31.Weight.AV.hasOwnProperty(MAV))) { badMetrics.push("MAV"); } 247 if (!(MAC === "X" || CVSS31.Weight.AC.hasOwnProperty(MAC))) { badMetrics.push("MAC"); } 248 if (!(MPR === "X" || CVSS31.Weight.PR.U.hasOwnProperty(MPR))) { badMetrics.push("MPR"); } 249 if (!(MUI === "X" || CVSS31.Weight.UI.hasOwnProperty(MUI))) { badMetrics.push("MUI"); } 250 if (!(MS === "X" || CVSS31.Weight.S.hasOwnProperty(MS))) { badMetrics.push("MS"); } 251 if (!(MC === "X" || CVSS31.Weight.CIA.hasOwnProperty(MC))) { badMetrics.push("MC"); } 252 if (!(MI === "X" || CVSS31.Weight.CIA.hasOwnProperty(MI))) { badMetrics.push("MI"); } 253 if (!(MA === "X" || CVSS31.Weight.CIA.hasOwnProperty(MA))) { badMetrics.push("MA"); } 254 255 if (badMetrics.length > 0) { 256 return { success: false, errorType: "UnknownMetricValue", errorMetrics: badMetrics }; 257 } 258 259 260 261 // GATHER WEIGHTS FOR ALL METRICS 262 263 var metricWeightAV = CVSS31.Weight.AV [AV]; 264 var metricWeightAC = CVSS31.Weight.AC [AC]; 265 var metricWeightPR = CVSS31.Weight.PR [S][PR]; // PR depends on the value of Scope (S). 266 var metricWeightUI = CVSS31.Weight.UI [UI]; 267 var metricWeightS = CVSS31.Weight.S [S]; 268 var metricWeightC = CVSS31.Weight.CIA [C]; 269 var metricWeightI = CVSS31.Weight.CIA [I]; 270 var metricWeightA = CVSS31.Weight.CIA [A]; 271 272 var metricWeightE = CVSS31.Weight.E [E]; 273 var metricWeightRL = CVSS31.Weight.RL [RL]; 274 var metricWeightRC = CVSS31.Weight.RC [RC]; 275 276 // For metrics that are modified versions of Base Score metrics, e.g. Modified Attack Vector, use the value of 277 // the Base Score metric if the modified version value is "X" ("Not Defined"). 278 var metricWeightCR = CVSS31.Weight.CIAR [CR]; 279 var metricWeightIR = CVSS31.Weight.CIAR [IR]; 280 var metricWeightAR = CVSS31.Weight.CIAR [AR]; 281 var metricWeightMAV = CVSS31.Weight.AV [MAV !== "X" ? MAV : AV]; 282 var metricWeightMAC = CVSS31.Weight.AC [MAC !== "X" ? MAC : AC]; 283 var metricWeightMPR = CVSS31.Weight.PR [MS !== "X" ? MS : S] [MPR !== "X" ? MPR : PR]; // Depends on MS. 284 var metricWeightMUI = CVSS31.Weight.UI [MUI !== "X" ? MUI : UI]; 285 var metricWeightMS = CVSS31.Weight.S [MS !== "X" ? MS : S]; 286 var metricWeightMC = CVSS31.Weight.CIA [MC !== "X" ? MC : C]; 287 var metricWeightMI = CVSS31.Weight.CIA [MI !== "X" ? MI : I]; 288 var metricWeightMA = CVSS31.Weight.CIA [MA !== "X" ? MA : A]; 289 290 291 292 // CALCULATE THE CVSS BASE SCORE 293 294 var iss; /* Impact Sub-Score */ 295 var impact; 296 var exploitability; 297 var baseScore; 298 299 iss = (1 - ((1 - metricWeightC) * (1 - metricWeightI) * (1 - metricWeightA))); 300 301 if (S === 'U') { 302 impact = metricWeightS * iss; 303 } else { 304 impact = metricWeightS * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15); 305 } 306 307 exploitability = CVSS31.exploitabilityCoefficient * metricWeightAV * metricWeightAC * metricWeightPR * metricWeightUI; 308 309 if (impact <= 0) { 310 baseScore = 0; 311 } else { 312 if (S === 'U') { 313 baseScore = CVSS31.roundUp1(Math.min((exploitability + impact), 10)); 314 } else { 315 baseScore = CVSS31.roundUp1(Math.min(CVSS31.scopeCoefficient * (exploitability + impact), 10)); 316 } 317 } 318 319 320 // CALCULATE THE CVSS TEMPORAL SCORE 321 322 var temporalScore = CVSS31.roundUp1(baseScore * metricWeightE * metricWeightRL * metricWeightRC); 323 324 325 // CALCULATE THE CVSS ENVIRONMENTAL SCORE 326 // 327 // - modifiedExploitability recalculates the Base Score Exploitability sub-score using any modified values from the 328 // Environmental metrics group in place of the values specified in the Base Score, if any have been defined. 329 // - modifiedImpact recalculates the Base Score Impact sub-score using any modified values from the 330 // Environmental metrics group in place of the values specified in the Base Score, and any additional weightings 331 // given in the Environmental metrics group. 332 333 var miss; /* Modified Impact Sub-Score */ 334 var modifiedImpact; 335 var envScore; 336 var modifiedExploitability; 337 338 miss = Math.min (1 - 339 ( (1 - metricWeightMC * metricWeightCR) * 340 (1 - metricWeightMI * metricWeightIR) * 341 (1 - metricWeightMA * metricWeightAR)), 0.915); 342 343 if (MS === "U" || 344 (MS === "X" && S === "U")) { 345 modifiedImpact = metricWeightMS * miss; 346 } else { 347 modifiedImpact = metricWeightMS * (miss - 0.029) - 3.25 * Math.pow(miss * 0.9731 - 0.02, 13); 348 } 349 350 modifiedExploitability = CVSS31.exploitabilityCoefficient * metricWeightMAV * metricWeightMAC * metricWeightMPR * metricWeightMUI; 351 352 if (modifiedImpact <= 0) { 353 envScore = 0; 354 } else if (MS === "U" || (MS === "X" && S === "U")) { 355 envScore = CVSS31.roundUp1(CVSS31.roundUp1(Math.min((modifiedImpact + modifiedExploitability), 10)) * 356 metricWeightE * metricWeightRL * metricWeightRC); 357 } else { 358 envScore = CVSS31.roundUp1(CVSS31.roundUp1(Math.min(CVSS31.scopeCoefficient * (modifiedImpact + modifiedExploitability), 10)) * 359 metricWeightE * metricWeightRL * metricWeightRC); 360 } 361 362 363 // CONSTRUCT THE VECTOR STRING 364 365 var vectorString = 366 CVSS31.CVSSVersionIdentifier + 367 "/AV:" + AV + 368 "/AC:" + AC + 369 "/PR:" + PR + 370 "/UI:" + UI + 371 "/S:" + S + 372 "/C:" + C + 373 "/I:" + I + 374 "/A:" + A; 375 376 if (E !== "X") {vectorString = vectorString + "/E:" + E;} 377 if (RL !== "X") {vectorString = vectorString + "/RL:" + RL;} 378 if (RC !== "X") {vectorString = vectorString + "/RC:" + RC;} 379 380 if (CR !== "X") {vectorString = vectorString + "/CR:" + CR;} 381 if (IR !== "X") {vectorString = vectorString + "/IR:" + IR;} 382 if (AR !== "X") {vectorString = vectorString + "/AR:" + AR;} 383 if (MAV !== "X") {vectorString = vectorString + "/MAV:" + MAV;} 384 if (MAC !== "X") {vectorString = vectorString + "/MAC:" + MAC;} 385 if (MPR !== "X") {vectorString = vectorString + "/MPR:" + MPR;} 386 if (MUI !== "X") {vectorString = vectorString + "/MUI:" + MUI;} 387 if (MS !== "X") {vectorString = vectorString + "/MS:" + MS;} 388 if (MC !== "X") {vectorString = vectorString + "/MC:" + MC;} 389 if (MI !== "X") {vectorString = vectorString + "/MI:" + MI;} 390 if (MA !== "X") {vectorString = vectorString + "/MA:" + MA;} 391 392 393 // Return an object containing the scores for all three metric groups, and an overall vector string. 394 // Sub-formula values are also included. 395 396 return { 397 success: true, 398 399 baseMetricScore: baseScore.toFixed(1), 400 baseSeverity: CVSS31.severityRating( baseScore.toFixed(1) ), 401 baseISS: iss, 402 baseImpact: impact, 403 baseExploitability: exploitability, 404 405 temporalMetricScore: temporalScore.toFixed(1), 406 temporalSeverity: CVSS31.severityRating( temporalScore.toFixed(1) ), 407 408 environmentalMetricScore: envScore.toFixed(1), 409 environmentalSeverity: CVSS31.severityRating( envScore.toFixed(1) ), 410 environmentalMISS: miss, 411 environmentalModifiedImpact: modifiedImpact, 412 environmentalModifiedExploitability: modifiedExploitability, 413 414 vectorString: vectorString 415 }; 416 }; 417 418 419 420 421 /* ** CVSS31.calculateCVSSFromVector ** 422 * 423 * Takes Base, Temporal and Environmental metric values as a single string in the Vector String format defined 424 * in the CVSS v3.1 standard definition of the Vector String. 425 * 426 * Returns Base, Temporal and Environmental scores, severity ratings, and an overall Vector String. All Base metrics 427 * are required to generate this output. All Temporal and Environmental metric values are optional. Any that are not 428 * passed default to "X" ("Not Defined"). 429 * 430 * See the comment for the CVSS31.calculateCVSSFromMetrics function for details on the function output. In addition to 431 * the error conditions listed for that function, this function can also return: 432 * "MalformedVectorString", if the Vector String passed does not conform to the format in the standard; or 433 * "MultipleDefinitionsOfMetric", if the Vector String is well formed but defines the same metric (or metrics), 434 * more than once. 435 */ 436 CVSS31.calculateCVSSFromVector = function ( vectorString ) { 437 438 var metricValues = { 439 AV: undefined, AC: undefined, PR: undefined, UI: undefined, S: undefined, 440 C: undefined, I: undefined, A: undefined, 441 E: undefined, RL: undefined, RC: undefined, 442 CR: undefined, IR: undefined, AR: undefined, 443 MAV: undefined, MAC: undefined, MPR: undefined, MUI: undefined, MS: undefined, 444 MC: undefined, MI: undefined, MA: undefined 445 }; 446 447 // If input validation fails, this array is populated with strings indicating which metrics failed validation. 448 var badMetrics = []; 449 450 if (!CVSS31.vectorStringRegex_31.test(vectorString)) { 451 return { success: false, errorType: "MalformedVectorString" }; 452 } 453 454 var metricNameValue = vectorString.substring(CVSS31.CVSSVersionIdentifier.length).split("/"); 455 456 for (var i in metricNameValue) { 457 if (metricNameValue.hasOwnProperty(i)) { 458 459 var singleMetric = metricNameValue[i].split(":"); 460 461 if (typeof metricValues[singleMetric[0]] === "undefined") { 462 metricValues[singleMetric[0]] = singleMetric[1]; 463 } else { 464 badMetrics.push(singleMetric[0]); 465 } 466 } 467 } 468 469 if (badMetrics.length > 0) { 470 return { success: false, errorType: "MultipleDefinitionsOfMetric", errorMetrics: badMetrics }; 471 } 472 473 return CVSS31.calculateCVSSFromMetrics ( 474 metricValues.AV, metricValues.AC, metricValues.PR, metricValues.UI, metricValues.S, 475 metricValues.C, metricValues.I, metricValues.A, 476 metricValues.E, metricValues.RL, metricValues.RC, 477 metricValues.CR, metricValues.IR, metricValues.AR, 478 metricValues.MAV, metricValues.MAC, metricValues.MPR, metricValues.MUI, metricValues.MS, 479 metricValues.MC, metricValues.MI, metricValues.MA); 480 }; 481 482 483 484 485 /* ** CVSS31.roundUp1 ** 486 * 487 * Rounds up its parameter to 1 decimal place and returns the result. 488 * 489 * Standard JavaScript errors thrown when arithmetic operations are performed on non-numbers will be returned if the 490 * given input is not a number. 491 * 492 * Implementation note: Tiny representation errors in floating point numbers makes rounding complex. For example, 493 * consider calculating Math.ceil((1-0.58)*100) by hand. It can be simplified to Math.ceil(0.42*100), then 494 * Math.ceil(42), and finally 42. Most JavaScript implementations give 43. The problem is that, on many systems, 495 * 1-0.58 = 0.42000000000000004, and the tiny error is enough to push ceil up to the next integer. The implementation 496 * below avoids such problems by performing the rounding using integers. The input is first multiplied by 100,000 497 * and rounded to the nearest integer to consider 6 decimal places of accuracy, so 0.000001 results in 0.0, but 498 * 0.000009 results in 0.1. 499 * 500 * A more elegant solution may be possible, but the following gives answers consistent with results from an arbitrary 501 * precision library. 502 */ 503 CVSS31.roundUp1 = function Roundup (input) { 504 var int_input = Math.round(input * 100000); 505 506 if (int_input % 10000 === 0) { 507 return int_input / 100000; 508 } else { 509 return (Math.floor(int_input / 10000) + 1) / 10; 510 } 511 }; 512 513 514 515 /* ** CVSS31.severityRating ** 516 * 517 * Given a CVSS score, returns the name of the severity rating as defined in the CVSS standard. 518 * The input needs to be a number between 0.0 to 10.0, to one decimal place of precision. 519 * 520 * The following error values may be returned instead of a severity rating name: 521 * NaN (JavaScript "Not a Number") - if the input is not a number. 522 * undefined - if the input is a number that is not within the range of any defined severity rating. 523 */ 524 CVSS31.severityRating = function (score) { 525 var severityRatingLength = CVSS31.severityRatings.length; 526 527 var validatedScore = Number(score); 528 529 if (isNaN(validatedScore)) { 530 return validatedScore; 531 } 532 533 for (var i = 0; i < severityRatingLength; i++) { 534 if (score >= CVSS31.severityRatings[i].bottom && score <= CVSS31.severityRatings[i].top) { 535 return CVSS31.severityRatings[i].name; 536 } 537 } 538 539 return undefined; 540 }; 541 542 543 544 /////////////////////////////////////////////////////////////////////////// 545 // DATA AND FUNCTIONS FOR CREATING AN XML REPRESENTATION OF A CVSS SCORE // 546 /////////////////////////////////////////////////////////////////////////// 547 548 // A mapping between abbreviated metric values and the string used in the XML representation. 549 // For example, a Remediation Level (RL) abbreviated metric value of "W" maps to "WORKAROUND". 550 // For brevity, every Base metric shares its definition with its equivalent Environmental metric. This is possible 551 // because the metric values are same between these groups, except that the latter have an additional metric value 552 // of "NOT_DEFINED". 553 554 CVSS31.XML_MetricNames = { 555 E: { X: "NOT_DEFINED", U: "UNPROVEN", P: "PROOF_OF_CONCEPT", F: "FUNCTIONAL", H: "HIGH"}, 556 RL: { X: "NOT_DEFINED", O: "OFFICIAL_FIX", T: "TEMPORARY_FIX", W: "WORKAROUND", U: "UNAVAILABLE"}, 557 RC: { X: "NOT_DEFINED", U: "UNKNOWN", R: "REASONABLE", C: "CONFIRMED"}, 558 559 CIAR: { X: "NOT_DEFINED", L: "LOW", M: "MEDIUM", H: "HIGH"}, // CR, IR and AR use the same values 560 MAV: { N: "NETWORK", A: "ADJACENT_NETWORK", L: "LOCAL", P: "PHYSICAL", X: "NOT_DEFINED" }, 561 MAC: { H: "HIGH", L: "LOW", X: "NOT_DEFINED" }, 562 MPR: { N: "NONE", L: "LOW", H: "HIGH", X: "NOT_DEFINED" }, 563 MUI: { N: "NONE", R: "REQUIRED", X: "NOT_DEFINED" }, 564 MS: { U: "UNCHANGED", C: "CHANGED", X: "NOT_DEFINED" }, 565 MCIA: { N: "NONE", L: "LOW", H: "HIGH", X: "NOT_DEFINED" } // C, I and A use the same values 566 }; 567 568 569 570 /* ** CVSS31.generateXMLFromMetrics ** 571 * 572 * Takes Base, Temporal and Environmental metric values as individual parameters. Their values are in the short format 573 * defined in the CVSS v3.1 standard definition of the Vector String. For example, the AttackComplexity parameter 574 * should be either "H" or "L". 575 * 576 * Returns a single string containing the metric values in XML form. All Base metrics are required to generate this 577 * output. All Temporal and Environmental metric values are optional. Any that are not passed will be represented in 578 * the XML as NOT_DEFINED. The function returns a string for simplicity. It is arguably better to return the XML as 579 * a DOM object, but at the time of writing this leads to complexity due to older browsers using different JavaScript 580 * interfaces to do this. Also for simplicity, all Temporal and Environmental metrics are included in the string, 581 * even though those with a value of "Not Defined" do not need to be included. 582 * 583 * The output of this function is an object which always has a property named "success". 584 * 585 * If no errors are encountered, success is Boolean "true", and the "xmlString" property contains the XML string 586 * representation. 587 * 588 * If errors are encountered, success is Boolean "false", and other properties are defined as per the 589 * CVSS31.calculateCVSSFromMetrics function. Refer to the comment for that function for more details. 590 */ 591 CVSS31.generateXMLFromMetrics = function ( 592 AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability, 593 ExploitCodeMaturity, RemediationLevel, ReportConfidence, 594 ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement, 595 ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope, 596 ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability) { 597 598 // A string containing the XML we wish to output, with placeholders for the CVSS metrics we will substitute for 599 // their values, based on the inputs passed to this function. 600 var xmlTemplate = 601 '<?xml version="1.0" encoding="UTF-8"?>\n' + 602 '<cvssv3.1 xmlns="https://www.first.org/cvss/cvss-v3.1.xsd"\n' + 603 ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + 604 ' xsi:schemaLocation="https://www.first.org/cvss/cvss-v3.1.xsd https://www.first.org/cvss/cvss-v3.1.xsd"\n' + 605 ' >\n' + 606 '\n' + 607 ' <base_metrics>\n' + 608 ' <attack-vector>__AttackVector__</attack-vector>\n' + 609 ' <attack-complexity>__AttackComplexity__</attack-complexity>\n' + 610 ' <privileges-required>__PrivilegesRequired__</privileges-required>\n' + 611 ' <user-interaction>__UserInteraction__</user-interaction>\n' + 612 ' <scope>__Scope__</scope>\n' + 613 ' <confidentiality-impact>__Confidentiality__</confidentiality-impact>\n' + 614 ' <integrity-impact>__Integrity__</integrity-impact>\n' + 615 ' <availability-impact>__Availability__</availability-impact>\n' + 616 ' <base-score>__BaseScore__</base-score>\n' + 617 ' <base-severity>__BaseSeverityRating__</base-severity>\n' + 618 ' </base_metrics>\n' + 619 '\n' + 620 ' <temporal_metrics>\n' + 621 ' <exploit-code-maturity>__ExploitCodeMaturity__</exploit-code-maturity>\n' + 622 ' <remediation-level>__RemediationLevel__</remediation-level>\n' + 623 ' <report-confidence>__ReportConfidence__</report-confidence>\n' + 624 ' <temporal-score>__TemporalScore__</temporal-score>\n' + 625 ' <temporal-severity>__TemporalSeverityRating__</temporal-severity>\n' + 626 ' </temporal_metrics>\n' + 627 '\n' + 628 ' <environmental_metrics>\n' + 629 ' <confidentiality-requirement>__ConfidentialityRequirement__</confidentiality-requirement>\n' + 630 ' <integrity-requirement>__IntegrityRequirement__</integrity-requirement>\n' + 631 ' <availability-requirement>__AvailabilityRequirement__</availability-requirement>\n' + 632 ' <modified-attack-vector>__ModifiedAttackVector__</modified-attack-vector>\n' + 633 ' <modified-attack-complexity>__ModifiedAttackComplexity__</modified-attack-complexity>\n' + 634 ' <modified-privileges-required>__ModifiedPrivilegesRequired__</modified-privileges-required>\n' + 635 ' <modified-user-interaction>__ModifiedUserInteraction__</modified-user-interaction>\n' + 636 ' <modified-scope>__ModifiedScope__</modified-scope>\n' + 637 ' <modified-confidentiality-impact>__ModifiedConfidentiality__</modified-confidentiality-impact>\n' + 638 ' <modified-integrity-impact>__ModifiedIntegrity__</modified-integrity-impact>\n' + 639 ' <modified-availability-impact>__ModifiedAvailability__</modified-availability-impact>\n' + 640 ' <environmental-score>__EnvironmentalScore__</environmental-score>\n' + 641 ' <environmental-severity>__EnvironmentalSeverityRating__</environmental-severity>\n' + 642 ' </environmental_metrics>\n' + 643 '\n' + 644 '</cvssv3.1>\n'; 645 646 647 // Call CVSS31.calculateCVSSFromMetrics to validate all the parameters and generate scores and severity ratings. 648 // If that function returns an error, immediately return it to the caller of this function. 649 var result = CVSS31.calculateCVSSFromMetrics ( 650 AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability, 651 ExploitCodeMaturity, RemediationLevel, ReportConfidence, 652 ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement, 653 ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope, 654 ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability); 655 656 if (result.success !== true) { 657 return result; 658 } 659 660 var xmlOutput = xmlTemplate; 661 xmlOutput = xmlOutput.replace ("__AttackVector__", CVSS31.XML_MetricNames["MAV"][AttackVector]); 662 xmlOutput = xmlOutput.replace ("__AttackComplexity__", CVSS31.XML_MetricNames["MAC"][AttackComplexity]); 663 xmlOutput = xmlOutput.replace ("__PrivilegesRequired__", CVSS31.XML_MetricNames["MPR"][PrivilegesRequired]); 664 xmlOutput = xmlOutput.replace ("__UserInteraction__", CVSS31.XML_MetricNames["MUI"][UserInteraction]); 665 xmlOutput = xmlOutput.replace ("__Scope__", CVSS31.XML_MetricNames["MS"][Scope]); 666 xmlOutput = xmlOutput.replace ("__Confidentiality__", CVSS31.XML_MetricNames["MCIA"][Confidentiality]); 667 xmlOutput = xmlOutput.replace ("__Integrity__", CVSS31.XML_MetricNames["MCIA"][Integrity]); 668 xmlOutput = xmlOutput.replace ("__Availability__", CVSS31.XML_MetricNames["MCIA"][Availability]); 669 xmlOutput = xmlOutput.replace ("__BaseScore__", result.baseMetricScore); 670 xmlOutput = xmlOutput.replace ("__BaseSeverityRating__", result.baseSeverity); 671 672 xmlOutput = xmlOutput.replace ("__ExploitCodeMaturity__", CVSS31.XML_MetricNames["E"][ExploitCodeMaturity || "X"]); 673 xmlOutput = xmlOutput.replace ("__RemediationLevel__", CVSS31.XML_MetricNames["RL"][RemediationLevel || "X"]); 674 xmlOutput = xmlOutput.replace ("__ReportConfidence__", CVSS31.XML_MetricNames["RC"][ReportConfidence || "X"]); 675 xmlOutput = xmlOutput.replace ("__TemporalScore__", result.temporalMetricScore); 676 xmlOutput = xmlOutput.replace ("__TemporalSeverityRating__", result.temporalSeverity); 677 678 xmlOutput = xmlOutput.replace ("__ConfidentialityRequirement__", CVSS31.XML_MetricNames["CIAR"][ConfidentialityRequirement || "X"]); 679 xmlOutput = xmlOutput.replace ("__IntegrityRequirement__", CVSS31.XML_MetricNames["CIAR"][IntegrityRequirement || "X"]); 680 xmlOutput = xmlOutput.replace ("__AvailabilityRequirement__", CVSS31.XML_MetricNames["CIAR"][AvailabilityRequirement || "X"]); 681 xmlOutput = xmlOutput.replace ("__ModifiedAttackVector__", CVSS31.XML_MetricNames["MAV"][ModifiedAttackVector || "X"]); 682 xmlOutput = xmlOutput.replace ("__ModifiedAttackComplexity__", CVSS31.XML_MetricNames["MAC"][ModifiedAttackComplexity || "X"]); 683 xmlOutput = xmlOutput.replace ("__ModifiedPrivilegesRequired__", CVSS31.XML_MetricNames["MPR"][ModifiedPrivilegesRequired || "X"]); 684 xmlOutput = xmlOutput.replace ("__ModifiedUserInteraction__", CVSS31.XML_MetricNames["MUI"][ModifiedUserInteraction || "X"]); 685 xmlOutput = xmlOutput.replace ("__ModifiedScope__", CVSS31.XML_MetricNames["MS"][ModifiedScope || "X"]); 686 xmlOutput = xmlOutput.replace ("__ModifiedConfidentiality__", CVSS31.XML_MetricNames["MCIA"][ModifiedConfidentiality || "X"]); 687 xmlOutput = xmlOutput.replace ("__ModifiedIntegrity__", CVSS31.XML_MetricNames["MCIA"][ModifiedIntegrity || "X"]); 688 xmlOutput = xmlOutput.replace ("__ModifiedAvailability__", CVSS31.XML_MetricNames["MCIA"][ModifiedAvailability || "X"]); 689 xmlOutput = xmlOutput.replace ("__EnvironmentalScore__", result.environmentalMetricScore); 690 xmlOutput = xmlOutput.replace ("__EnvironmentalSeverityRating__", result.environmentalSeverity); 691 692 return { success: true, xmlString: xmlOutput }; 693 }; 694 695 696 697 /* ** CVSS31.generateXMLFromVector ** 698 * 699 * Takes Base, Temporal and Environmental metric values as a single string in the Vector String format defined 700 * in the CVSS v3.1 standard definition of the Vector String. 701 * 702 * Returns an XML string representation of this input. See the comment for CVSS31.generateXMLFromMetrics for more 703 * detail on inputs, return values and errors. In addition to the error conditions listed for that function, this 704 * function can also return: 705 * "MalformedVectorString", if the Vector String passed is does not conform to the format in the standard; or 706 * "MultipleDefinitionsOfMetric", if the Vector String is well formed but defines the same metric (or metrics), 707 * more than once. 708 */ 709 CVSS31.generateXMLFromVector = function ( vectorString ) { 710 711 var metricValues = { 712 AV: undefined, AC: undefined, PR: undefined, UI: undefined, S: undefined, 713 C: undefined, I: undefined, A: undefined, 714 E: undefined, RL: undefined, RC: undefined, 715 CR: undefined, IR: undefined, AR: undefined, 716 MAV: undefined, MAC: undefined, MPR: undefined, MUI: undefined, MS: undefined, 717 MC: undefined, MI: undefined, MA: undefined 718 }; 719 720 // If input validation fails, this array is populated with strings indicating which metrics failed validation. 721 var badMetrics = []; 722 723 if (!CVSS31.vectorStringRegex_31.test(vectorString)) { 724 return { success: false, errorType: "MalformedVectorString" }; 725 } 726 727 var metricNameValue = vectorString.substring(CVSS31.CVSSVersionIdentifier.length).split("/"); 728 729 for (var i in metricNameValue) { 730 if (metricNameValue.hasOwnProperty(i)) { 731 732 var singleMetric = metricNameValue[i].split(":"); 733 734 if (typeof metricValues[singleMetric[0]] === "undefined") { 735 metricValues[singleMetric[0]] = singleMetric[1]; 736 } else { 737 badMetrics.push(singleMetric[0]); 738 } 739 } 740 } 741 742 if (badMetrics.length > 0) { 743 return { success: false, errorType: "MultipleDefinitionsOfMetric", errorMetrics: badMetrics }; 744 } 745 746 return CVSS31.generateXMLFromMetrics ( 747 metricValues.AV, metricValues.AC, metricValues.PR, metricValues.UI, metricValues.S, 748 metricValues.C, metricValues.I, metricValues.A, 749 metricValues.E, metricValues.RL, metricValues.RC, 750 metricValues.CR, metricValues.IR, metricValues.AR, 751 metricValues.MAV, metricValues.MAC, metricValues.MPR, metricValues.MUI, metricValues.MS, 752 metricValues.MC, metricValues.MI, metricValues.MA); 753 };