/* Copyright (c) 2019, FIRST.ORG, INC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* This JavaScript contains two main functions. Both take CVSS metric values and calculate CVSS scores for Base, * Temporal and Environmental metric groups, their associated severity ratings, and an overall Vector String. * * Use CVSS31.calculateCVSSFromMetrics if you wish to pass metric values as individual parameters. * Use CVSS31.calculateCVSSFromVector if you wish to pass metric values as a single Vector String. * * Changelog * * 2019-06-01 Darius Wiles Updates for CVSS version 3.1: * * 1) The CVSS31.roundUp1 function now performs rounding using integer arithmetic to * eliminate problems caused by tiny errors introduced during JavaScript math * operations. Thanks to Stanislav Kontar of Red Hat for suggesting and testing * various implementations. * * 2) Environmental formulas changed to prevent the Environmental Score decreasing when * the value of an Environmental metric is raised. The problem affected a small * percentage of CVSS v3.0 metrics. The change is to the modifiedImpact * formula, but only affects scores where the Modified Scope is Changed (or the * Scope is Changed if Modified Scope is Not Defined). * * 3) The JavaScript object containing everything in this file has been renamed from * "CVSS" to "CVSS31" to allow both objects to be included without causing a * naming conflict. * * 4) Variable names and code order have changed to more closely reflect the formulas * in the CVSS v3.1 Specification Document. * * 5) A successful call to calculateCVSSFromMetrics now returns sub-formula values. * * Note that some sets of metrics will produce different scores between CVSS v3.0 and * v3.1 as a result of changes 1 and 2. See the explanation of changes between these * two standards in the CVSS v3.1 User Guide for more details. * * 2018-02-15 Darius Wiles Added a missing pair of parentheses in the Environmental score, specifically * in the code setting envScore in the main clause (not the else clause). It was changed * from "min (...), 10" to "min ((...), 10)". This correction does not alter any final * Environmental scores. * * 2015-08-04 Darius Wiles Added CVSS.generateXMLFromMetrics and CVSS.generateXMLFromVector functions to return * XML string representations of: a set of metric values; or a Vector String respectively. * Moved all constants and functions to an object named "CVSS" to * reduce the chance of conflicts in global variables when this file is combined with * other JavaScript code. This will break all existing code that uses this file until * the string "CVSS." is prepended to all references. The "Exploitability" metric has been * renamed "Exploit Code Maturity" in the specification, so the same change has been made * in the code in this file. * * 2015-04-24 Darius Wiles Environmental formula modified to eliminate undesirable behavior caused by subtle * differences in rounding between Temporal and Environmental formulas that often * caused the latter to be 0.1 lower than than the former when all Environmental * metrics are "Not defined". Also added a RoundUp1 function to simplify formulas. * * 2015-04-09 Darius Wiles Added calculateCVSSFromVector function, license information, cleaned up code and improved * comments. * * 2014-12-12 Darius Wiles Initial release for CVSS 3.0 Preview 2. */ // Constants used in the formula. They are not declared as "const" to avoid problems in older browsers. var CVSS31 = {}; CVSS31.CVSSVersionIdentifier = 'CVSS:3.1'; CVSS31.exploitabilityCoefficient = 8.22; CVSS31.scopeCoefficient = 1.08; // A regular expression to validate that a CVSS 3.1 vector string is well formed. It checks metrics and metric // values. It does not check that a metric is specified more than once and it does not check that all base // metrics are present. These checks need to be performed separately. CVSS31.vectorStringRegex_31 = /^CVSS:3\.[01]\/((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])$/; // Associative arrays mapping each metric value to the constant defined in the CVSS scoring formula in the CVSS v3.1 // specification. CVSS31.Weight = { AV: { N: 0.85, A: 0.62, L: 0.55, P: 0.2 }, AC: { H: 0.44, L: 0.77 }, PR: { U: { N: 0.85, L: 0.62, H: 0.27 }, // These values are used if Scope is Unchanged C: { N: 0.85, L: 0.68, H: 0.5 }, }, // These values are used if Scope is Changed UI: { N: 0.85, R: 0.62 }, S: { U: 6.42, C: 7.52 }, // Note: not defined as constants in specification CIA: { N: 0, L: 0.22, H: 0.56 }, // C, I and A have the same weights E: { X: 1, U: 0.91, P: 0.94, F: 0.97, H: 1 }, RL: { X: 1, O: 0.95, T: 0.96, W: 0.97, U: 1 }, RC: { X: 1, U: 0.92, R: 0.96, C: 1 }, CIAR: { X: 1, L: 0.5, M: 1, H: 1.5 }, // CR, IR and AR have the same weights }; // Severity rating bands, as defined in the CVSS v3.1 specification. CVSS31.severityRatings = [ { name: 'None', bottom: 0.0, top: 0.0 }, { name: 'Low', bottom: 0.1, top: 3.9 }, { name: 'Medium', bottom: 4.0, top: 6.9 }, { name: 'High', bottom: 7.0, top: 8.9 }, { name: 'Critical', bottom: 9.0, top: 10.0 }, ]; /* ** CVSS31.calculateCVSSFromMetrics ** * * Takes Base, Temporal and Environmental metric values as individual parameters. Their values are in the short format * defined in the CVSS v3.1 standard definition of the Vector String. For example, the AttackComplexity parameter * should be either "H" or "L". * * Returns Base, Temporal and Environmental scores, severity ratings, and an overall Vector String. All Base metrics * are required to generate this output. All Temporal and Environmental metric values are optional. Any that are not * passed default to "X" ("Not Defined"). * * The output is an object which always has a property named "success". * * If no errors are encountered, success is Boolean "true", and the following other properties are defined containing * scores, severities and a vector string: * baseMetricScore, baseSeverity, * temporalMetricScore, temporalSeverity, * environmentalMetricScore, environmentalSeverity, * vectorString * * The following properties are also defined, and contain sub-formula values: * baseISS, baseImpact, baseExploitability, * environmentalMISS, environmentalModifiedImpact, environmentalModifiedExploitability * * * If errors are encountered, success is Boolean "false", and the following other properties are defined: * errorType - a string indicating the error. Either: * "MissingBaseMetric", if at least one Base metric has not been defined; or * "UnknownMetricValue", if at least one metric value is invalid. * errorMetrics - an array of strings representing the metrics at fault. The strings are abbreviated versions of the * metrics, as defined in the CVSS v3.1 standard definition of the Vector String. */ CVSS31.calculateCVSSFromMetrics = function ( AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability, ExploitCodeMaturity, RemediationLevel, ReportConfidence, ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement, ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope, ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability, ) { // If input validation fails, this array is populated with strings indicating which metrics failed validation. var badMetrics = []; // ENSURE ALL BASE METRICS ARE DEFINED // // We need values for all Base Score metrics to calculate scores. // If any Base Score parameters are undefined, create an array of missing metrics and return it with an error. if (typeof AttackVector === 'undefined' || AttackVector === '') { badMetrics.push('AV'); } if (typeof AttackComplexity === 'undefined' || AttackComplexity === '') { badMetrics.push('AC'); } if (typeof PrivilegesRequired === 'undefined' || PrivilegesRequired === '') { badMetrics.push('PR'); } if (typeof UserInteraction === 'undefined' || UserInteraction === '') { badMetrics.push('UI'); } if (typeof Scope === 'undefined' || Scope === '') { badMetrics.push('S'); } if (typeof Confidentiality === 'undefined' || Confidentiality === '') { badMetrics.push('C'); } if (typeof Integrity === 'undefined' || Integrity === '') { badMetrics.push('I'); } if (typeof Availability === 'undefined' || Availability === '') { badMetrics.push('A'); } if (badMetrics.length > 0) { return { success: false, errorType: 'MissingBaseMetric', errorMetrics: badMetrics, }; } // STORE THE METRIC VALUES THAT WERE PASSED AS PARAMETERS // // Temporal and Environmental metrics are optional, so set them to "X" ("Not Defined") if no value was passed. var AV = AttackVector; var AC = AttackComplexity; var PR = PrivilegesRequired; var UI = UserInteraction; var S = Scope; var C = Confidentiality; var I = Integrity; var A = Availability; var E = ExploitCodeMaturity || 'X'; var RL = RemediationLevel || 'X'; var RC = ReportConfidence || 'X'; var CR = ConfidentialityRequirement || 'X'; var IR = IntegrityRequirement || 'X'; var AR = AvailabilityRequirement || 'X'; var MAV = ModifiedAttackVector || 'X'; var MAC = ModifiedAttackComplexity || 'X'; var MPR = ModifiedPrivilegesRequired || 'X'; var MUI = ModifiedUserInteraction || 'X'; var MS = ModifiedScope || 'X'; var MC = ModifiedConfidentiality || 'X'; var MI = ModifiedIntegrity || 'X'; var MA = ModifiedAvailability || 'X'; // CHECK VALIDITY OF METRIC VALUES // // Use the Weight object to ensure that, for every metric, the metric value passed is valid. // If any invalid values are found, create an array of their metrics and return it with an error. // // The Privileges Required (PR) weight depends on Scope, but when checking the validity of PR we must not assume // that the given value for Scope is valid. We therefore always look at the weights for Unchanged Scope when // performing this check. The same applies for validation of Modified Privileges Required (MPR). // // The Weights object does not contain "X" ("Not Defined") values for Environmental metrics because we replace them // with their Base metric equivalents later in the function. For example, an MAV of "X" will be replaced with the // value given for AV. We therefore need to explicitly allow a value of "X" for Environmental metrics. if (!CVSS31.Weight.AV.hasOwnProperty(AV)) { badMetrics.push('AV'); } if (!CVSS31.Weight.AC.hasOwnProperty(AC)) { badMetrics.push('AC'); } if (!CVSS31.Weight.PR.U.hasOwnProperty(PR)) { badMetrics.push('PR'); } if (!CVSS31.Weight.UI.hasOwnProperty(UI)) { badMetrics.push('UI'); } if (!CVSS31.Weight.S.hasOwnProperty(S)) { badMetrics.push('S'); } if (!CVSS31.Weight.CIA.hasOwnProperty(C)) { badMetrics.push('C'); } if (!CVSS31.Weight.CIA.hasOwnProperty(I)) { badMetrics.push('I'); } if (!CVSS31.Weight.CIA.hasOwnProperty(A)) { badMetrics.push('A'); } if (!CVSS31.Weight.E.hasOwnProperty(E)) { badMetrics.push('E'); } if (!CVSS31.Weight.RL.hasOwnProperty(RL)) { badMetrics.push('RL'); } if (!CVSS31.Weight.RC.hasOwnProperty(RC)) { badMetrics.push('RC'); } if (!(CR === 'X' || CVSS31.Weight.CIAR.hasOwnProperty(CR))) { badMetrics.push('CR'); } if (!(IR === 'X' || CVSS31.Weight.CIAR.hasOwnProperty(IR))) { badMetrics.push('IR'); } if (!(AR === 'X' || CVSS31.Weight.CIAR.hasOwnProperty(AR))) { badMetrics.push('AR'); } if (!(MAV === 'X' || CVSS31.Weight.AV.hasOwnProperty(MAV))) { badMetrics.push('MAV'); } if (!(MAC === 'X' || CVSS31.Weight.AC.hasOwnProperty(MAC))) { badMetrics.push('MAC'); } if (!(MPR === 'X' || CVSS31.Weight.PR.U.hasOwnProperty(MPR))) { badMetrics.push('MPR'); } if (!(MUI === 'X' || CVSS31.Weight.UI.hasOwnProperty(MUI))) { badMetrics.push('MUI'); } if (!(MS === 'X' || CVSS31.Weight.S.hasOwnProperty(MS))) { badMetrics.push('MS'); } if (!(MC === 'X' || CVSS31.Weight.CIA.hasOwnProperty(MC))) { badMetrics.push('MC'); } if (!(MI === 'X' || CVSS31.Weight.CIA.hasOwnProperty(MI))) { badMetrics.push('MI'); } if (!(MA === 'X' || CVSS31.Weight.CIA.hasOwnProperty(MA))) { badMetrics.push('MA'); } if (badMetrics.length > 0) { return { success: false, errorType: 'UnknownMetricValue', errorMetrics: badMetrics, }; } // GATHER WEIGHTS FOR ALL METRICS var metricWeightAV = CVSS31.Weight.AV[AV]; var metricWeightAC = CVSS31.Weight.AC[AC]; var metricWeightPR = CVSS31.Weight.PR[S][PR]; // PR depends on the value of Scope (S). var metricWeightUI = CVSS31.Weight.UI[UI]; var metricWeightS = CVSS31.Weight.S[S]; var metricWeightC = CVSS31.Weight.CIA[C]; var metricWeightI = CVSS31.Weight.CIA[I]; var metricWeightA = CVSS31.Weight.CIA[A]; var metricWeightE = CVSS31.Weight.E[E]; var metricWeightRL = CVSS31.Weight.RL[RL]; var metricWeightRC = CVSS31.Weight.RC[RC]; // For metrics that are modified versions of Base Score metrics, e.g. Modified Attack Vector, use the value of // the Base Score metric if the modified version value is "X" ("Not Defined"). var metricWeightCR = CVSS31.Weight.CIAR[CR]; var metricWeightIR = CVSS31.Weight.CIAR[IR]; var metricWeightAR = CVSS31.Weight.CIAR[AR]; var metricWeightMAV = CVSS31.Weight.AV[MAV !== 'X' ? MAV : AV]; var metricWeightMAC = CVSS31.Weight.AC[MAC !== 'X' ? MAC : AC]; var metricWeightMPR = CVSS31.Weight.PR[MS !== 'X' ? MS : S][MPR !== 'X' ? MPR : PR]; // Depends on MS. var metricWeightMUI = CVSS31.Weight.UI[MUI !== 'X' ? MUI : UI]; var metricWeightMS = CVSS31.Weight.S[MS !== 'X' ? MS : S]; var metricWeightMC = CVSS31.Weight.CIA[MC !== 'X' ? MC : C]; var metricWeightMI = CVSS31.Weight.CIA[MI !== 'X' ? MI : I]; var metricWeightMA = CVSS31.Weight.CIA[MA !== 'X' ? MA : A]; // CALCULATE THE CVSS BASE SCORE var iss; /* Impact Sub-Score */ var impact; var exploitability; var baseScore; iss = 1 - (1 - metricWeightC) * (1 - metricWeightI) * (1 - metricWeightA); if (S === 'U') { impact = metricWeightS * iss; } else { impact = metricWeightS * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15); } exploitability = CVSS31.exploitabilityCoefficient * metricWeightAV * metricWeightAC * metricWeightPR * metricWeightUI; if (impact <= 0) { baseScore = 0; } else { if (S === 'U') { baseScore = CVSS31.roundUp1(Math.min(exploitability + impact, 10)); } else { baseScore = CVSS31.roundUp1( Math.min(CVSS31.scopeCoefficient * (exploitability + impact), 10), ); } } // CALCULATE THE CVSS TEMPORAL SCORE var temporalScore = CVSS31.roundUp1( baseScore * metricWeightE * metricWeightRL * metricWeightRC, ); // CALCULATE THE CVSS ENVIRONMENTAL SCORE // // - modifiedExploitability recalculates the Base Score Exploitability sub-score using any modified values from the // Environmental metrics group in place of the values specified in the Base Score, if any have been defined. // - modifiedImpact recalculates the Base Score Impact sub-score using any modified values from the // Environmental metrics group in place of the values specified in the Base Score, and any additional weightings // given in the Environmental metrics group. var miss; /* Modified Impact Sub-Score */ var modifiedImpact; var envScore; var modifiedExploitability; miss = Math.min( 1 - (1 - metricWeightMC * metricWeightCR) * (1 - metricWeightMI * metricWeightIR) * (1 - metricWeightMA * metricWeightAR), 0.915, ); if (MS === 'U' || (MS === 'X' && S === 'U')) { modifiedImpact = metricWeightMS * miss; } else { modifiedImpact = metricWeightMS * (miss - 0.029) - 3.25 * Math.pow(miss * 0.9731 - 0.02, 13); } modifiedExploitability = CVSS31.exploitabilityCoefficient * metricWeightMAV * metricWeightMAC * metricWeightMPR * metricWeightMUI; if (modifiedImpact <= 0) { envScore = 0; } else if (MS === 'U' || (MS === 'X' && S === 'U')) { envScore = CVSS31.roundUp1( CVSS31.roundUp1(Math.min(modifiedImpact + modifiedExploitability, 10)) * metricWeightE * metricWeightRL * metricWeightRC, ); } else { envScore = CVSS31.roundUp1( CVSS31.roundUp1( Math.min( CVSS31.scopeCoefficient * (modifiedImpact + modifiedExploitability), 10, ), ) * metricWeightE * metricWeightRL * metricWeightRC, ); } // CONSTRUCT THE VECTOR STRING var vectorString = CVSS31.CVSSVersionIdentifier + '/AV:' + AV + '/AC:' + AC + '/PR:' + PR + '/UI:' + UI + '/S:' + S + '/C:' + C + '/I:' + I + '/A:' + A; if (E !== 'X') { vectorString = vectorString + '/E:' + E; } if (RL !== 'X') { vectorString = vectorString + '/RL:' + RL; } if (RC !== 'X') { vectorString = vectorString + '/RC:' + RC; } if (CR !== 'X') { vectorString = vectorString + '/CR:' + CR; } if (IR !== 'X') { vectorString = vectorString + '/IR:' + IR; } if (AR !== 'X') { vectorString = vectorString + '/AR:' + AR; } if (MAV !== 'X') { vectorString = vectorString + '/MAV:' + MAV; } if (MAC !== 'X') { vectorString = vectorString + '/MAC:' + MAC; } if (MPR !== 'X') { vectorString = vectorString + '/MPR:' + MPR; } if (MUI !== 'X') { vectorString = vectorString + '/MUI:' + MUI; } if (MS !== 'X') { vectorString = vectorString + '/MS:' + MS; } if (MC !== 'X') { vectorString = vectorString + '/MC:' + MC; } if (MI !== 'X') { vectorString = vectorString + '/MI:' + MI; } if (MA !== 'X') { vectorString = vectorString + '/MA:' + MA; } // Return an object containing the scores for all three metric groups, and an overall vector string. // Sub-formula values are also included. return { success: true, baseMetricScore: baseScore.toFixed(1), baseSeverity: CVSS31.severityRating(baseScore.toFixed(1)), baseISS: iss, baseImpact: impact, baseExploitability: exploitability, temporalMetricScore: temporalScore.toFixed(1), temporalSeverity: CVSS31.severityRating(temporalScore.toFixed(1)), environmentalMetricScore: envScore.toFixed(1), environmentalSeverity: CVSS31.severityRating(envScore.toFixed(1)), environmentalMISS: miss, environmentalModifiedImpact: modifiedImpact, environmentalModifiedExploitability: modifiedExploitability, vectorString: vectorString, }; }; /* ** CVSS31.calculateCVSSFromVector ** * * Takes Base, Temporal and Environmental metric values as a single string in the Vector String format defined * in the CVSS v3.1 standard definition of the Vector String. * * Returns Base, Temporal and Environmental scores, severity ratings, and an overall Vector String. All Base metrics * are required to generate this output. All Temporal and Environmental metric values are optional. Any that are not * passed default to "X" ("Not Defined"). * * See the comment for the CVSS31.calculateCVSSFromMetrics function for details on the function output. In addition to * the error conditions listed for that function, this function can also return: * "MalformedVectorString", if the Vector String passed does not conform to the format in the standard; or * "MultipleDefinitionsOfMetric", if the Vector String is well formed but defines the same metric (or metrics), * more than once. */ CVSS31.calculateCVSSFromVector = function (vectorString) { var metricValues = { AV: undefined, AC: undefined, PR: undefined, UI: undefined, S: undefined, C: undefined, I: undefined, A: undefined, E: undefined, RL: undefined, RC: undefined, CR: undefined, IR: undefined, AR: undefined, MAV: undefined, MAC: undefined, MPR: undefined, MUI: undefined, MS: undefined, MC: undefined, MI: undefined, MA: undefined, }; // If input validation fails, this array is populated with strings indicating which metrics failed validation. var badMetrics = []; if (!CVSS31.vectorStringRegex_31.test(vectorString)) { return { success: false, errorType: 'MalformedVectorString' }; } var metricNameValue = vectorString .substring(CVSS31.CVSSVersionIdentifier.length) .split('/'); for (var i in metricNameValue) { if (metricNameValue.hasOwnProperty(i)) { var singleMetric = metricNameValue[i].split(':'); if (typeof metricValues[singleMetric[0]] === 'undefined') { metricValues[singleMetric[0]] = singleMetric[1]; } else { badMetrics.push(singleMetric[0]); } } } if (badMetrics.length > 0) { return { success: false, errorType: 'MultipleDefinitionsOfMetric', errorMetrics: badMetrics, }; } return CVSS31.calculateCVSSFromMetrics( metricValues.AV, metricValues.AC, metricValues.PR, metricValues.UI, metricValues.S, metricValues.C, metricValues.I, metricValues.A, metricValues.E, metricValues.RL, metricValues.RC, metricValues.CR, metricValues.IR, metricValues.AR, metricValues.MAV, metricValues.MAC, metricValues.MPR, metricValues.MUI, metricValues.MS, metricValues.MC, metricValues.MI, metricValues.MA, ); }; /* ** CVSS31.roundUp1 ** * * Rounds up its parameter to 1 decimal place and returns the result. * * Standard JavaScript errors thrown when arithmetic operations are performed on non-numbers will be returned if the * given input is not a number. * * Implementation note: Tiny representation errors in floating point numbers makes rounding complex. For example, * consider calculating Math.ceil((1-0.58)*100) by hand. It can be simplified to Math.ceil(0.42*100), then * Math.ceil(42), and finally 42. Most JavaScript implementations give 43. The problem is that, on many systems, * 1-0.58 = 0.42000000000000004, and the tiny error is enough to push ceil up to the next integer. The implementation * below avoids such problems by performing the rounding using integers. The input is first multiplied by 100,000 * and rounded to the nearest integer to consider 6 decimal places of accuracy, so 0.000001 results in 0.0, but * 0.000009 results in 0.1. * * A more elegant solution may be possible, but the following gives answers consistent with results from an arbitrary * precision library. */ CVSS31.roundUp1 = function Roundup(input) { var int_input = Math.round(input * 100000); if (int_input % 10000 === 0) { return int_input / 100000; } else { return (Math.floor(int_input / 10000) + 1) / 10; } }; /* ** CVSS31.severityRating ** * * Given a CVSS score, returns the name of the severity rating as defined in the CVSS standard. * The input needs to be a number between 0.0 to 10.0, to one decimal place of precision. * * The following error values may be returned instead of a severity rating name: * NaN (JavaScript "Not a Number") - if the input is not a number. * undefined - if the input is a number that is not within the range of any defined severity rating. */ CVSS31.severityRating = function (score) { var severityRatingLength = CVSS31.severityRatings.length; var validatedScore = Number(score); if (isNaN(validatedScore)) { return validatedScore; } for (var i = 0; i < severityRatingLength; i++) { if ( score >= CVSS31.severityRatings[i].bottom && score <= CVSS31.severityRatings[i].top ) { return CVSS31.severityRatings[i].name; } } return undefined; }; /////////////////////////////////////////////////////////////////////////// // DATA AND FUNCTIONS FOR CREATING AN XML REPRESENTATION OF A CVSS SCORE // /////////////////////////////////////////////////////////////////////////// // A mapping between abbreviated metric values and the string used in the XML representation. // For example, a Remediation Level (RL) abbreviated metric value of "W" maps to "WORKAROUND". // For brevity, every Base metric shares its definition with its equivalent Environmental metric. This is possible // because the metric values are same between these groups, except that the latter have an additional metric value // of "NOT_DEFINED". CVSS31.XML_MetricNames = { E: { X: 'NOT_DEFINED', U: 'UNPROVEN', P: 'PROOF_OF_CONCEPT', F: 'FUNCTIONAL', H: 'HIGH', }, RL: { X: 'NOT_DEFINED', O: 'OFFICIAL_FIX', T: 'TEMPORARY_FIX', W: 'WORKAROUND', U: 'UNAVAILABLE', }, RC: { X: 'NOT_DEFINED', U: 'UNKNOWN', R: 'REASONABLE', C: 'CONFIRMED' }, CIAR: { X: 'NOT_DEFINED', L: 'LOW', M: 'MEDIUM', H: 'HIGH' }, // CR, IR and AR use the same values MAV: { N: 'NETWORK', A: 'ADJACENT_NETWORK', L: 'LOCAL', P: 'PHYSICAL', X: 'NOT_DEFINED', }, MAC: { H: 'HIGH', L: 'LOW', X: 'NOT_DEFINED' }, MPR: { N: 'NONE', L: 'LOW', H: 'HIGH', X: 'NOT_DEFINED' }, MUI: { N: 'NONE', R: 'REQUIRED', X: 'NOT_DEFINED' }, MS: { U: 'UNCHANGED', C: 'CHANGED', X: 'NOT_DEFINED' }, MCIA: { N: 'NONE', L: 'LOW', H: 'HIGH', X: 'NOT_DEFINED' }, // C, I and A use the same values }; /* ** CVSS31.generateXMLFromMetrics ** * * Takes Base, Temporal and Environmental metric values as individual parameters. Their values are in the short format * defined in the CVSS v3.1 standard definition of the Vector String. For example, the AttackComplexity parameter * should be either "H" or "L". * * Returns a single string containing the metric values in XML form. All Base metrics are required to generate this * output. All Temporal and Environmental metric values are optional. Any that are not passed will be represented in * the XML as NOT_DEFINED. The function returns a string for simplicity. It is arguably better to return the XML as * a DOM object, but at the time of writing this leads to complexity due to older browsers using different JavaScript * interfaces to do this. Also for simplicity, all Temporal and Environmental metrics are included in the string, * even though those with a value of "Not Defined" do not need to be included. * * The output of this function is an object which always has a property named "success". * * If no errors are encountered, success is Boolean "true", and the "xmlString" property contains the XML string * representation. * * If errors are encountered, success is Boolean "false", and other properties are defined as per the * CVSS31.calculateCVSSFromMetrics function. Refer to the comment for that function for more details. */ CVSS31.generateXMLFromMetrics = function ( AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability, ExploitCodeMaturity, RemediationLevel, ReportConfidence, ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement, ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope, ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability, ) { // A string containing the XML we wish to output, with placeholders for the CVSS metrics we will substitute for // their values, based on the inputs passed to this function. var xmlTemplate = '\n' + '\n' + '\n' + ' \n' + ' __AttackVector__\n' + ' __AttackComplexity__\n' + ' __PrivilegesRequired__\n' + ' __UserInteraction__\n' + ' __Scope__\n' + ' __Confidentiality__\n' + ' __Integrity__\n' + ' __Availability__\n' + ' __BaseScore__\n' + ' __BaseSeverityRating__\n' + ' \n' + '\n' + ' \n' + ' __ExploitCodeMaturity__\n' + ' __RemediationLevel__\n' + ' __ReportConfidence__\n' + ' __TemporalScore__\n' + ' __TemporalSeverityRating__\n' + ' \n' + '\n' + ' \n' + ' __ConfidentialityRequirement__\n' + ' __IntegrityRequirement__\n' + ' __AvailabilityRequirement__\n' + ' __ModifiedAttackVector__\n' + ' __ModifiedAttackComplexity__\n' + ' __ModifiedPrivilegesRequired__\n' + ' __ModifiedUserInteraction__\n' + ' __ModifiedScope__\n' + ' __ModifiedConfidentiality__\n' + ' __ModifiedIntegrity__\n' + ' __ModifiedAvailability__\n' + ' __EnvironmentalScore__\n' + ' __EnvironmentalSeverityRating__\n' + ' \n' + '\n' + '\n'; // Call CVSS31.calculateCVSSFromMetrics to validate all the parameters and generate scores and severity ratings. // If that function returns an error, immediately return it to the caller of this function. var result = CVSS31.calculateCVSSFromMetrics( AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability, ExploitCodeMaturity, RemediationLevel, ReportConfidence, ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement, ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope, ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability, ); if (result.success !== true) { return result; } var xmlOutput = xmlTemplate; xmlOutput = xmlOutput.replace( '__AttackVector__', CVSS31.XML_MetricNames['MAV'][AttackVector], ); xmlOutput = xmlOutput.replace( '__AttackComplexity__', CVSS31.XML_MetricNames['MAC'][AttackComplexity], ); xmlOutput = xmlOutput.replace( '__PrivilegesRequired__', CVSS31.XML_MetricNames['MPR'][PrivilegesRequired], ); xmlOutput = xmlOutput.replace( '__UserInteraction__', CVSS31.XML_MetricNames['MUI'][UserInteraction], ); xmlOutput = xmlOutput.replace( '__Scope__', CVSS31.XML_MetricNames['MS'][Scope], ); xmlOutput = xmlOutput.replace( '__Confidentiality__', CVSS31.XML_MetricNames['MCIA'][Confidentiality], ); xmlOutput = xmlOutput.replace( '__Integrity__', CVSS31.XML_MetricNames['MCIA'][Integrity], ); xmlOutput = xmlOutput.replace( '__Availability__', CVSS31.XML_MetricNames['MCIA'][Availability], ); xmlOutput = xmlOutput.replace('__BaseScore__', result.baseMetricScore); xmlOutput = xmlOutput.replace('__BaseSeverityRating__', result.baseSeverity); xmlOutput = xmlOutput.replace( '__ExploitCodeMaturity__', CVSS31.XML_MetricNames['E'][ExploitCodeMaturity || 'X'], ); xmlOutput = xmlOutput.replace( '__RemediationLevel__', CVSS31.XML_MetricNames['RL'][RemediationLevel || 'X'], ); xmlOutput = xmlOutput.replace( '__ReportConfidence__', CVSS31.XML_MetricNames['RC'][ReportConfidence || 'X'], ); xmlOutput = xmlOutput.replace( '__TemporalScore__', result.temporalMetricScore, ); xmlOutput = xmlOutput.replace( '__TemporalSeverityRating__', result.temporalSeverity, ); xmlOutput = xmlOutput.replace( '__ConfidentialityRequirement__', CVSS31.XML_MetricNames['CIAR'][ConfidentialityRequirement || 'X'], ); xmlOutput = xmlOutput.replace( '__IntegrityRequirement__', CVSS31.XML_MetricNames['CIAR'][IntegrityRequirement || 'X'], ); xmlOutput = xmlOutput.replace( '__AvailabilityRequirement__', CVSS31.XML_MetricNames['CIAR'][AvailabilityRequirement || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedAttackVector__', CVSS31.XML_MetricNames['MAV'][ModifiedAttackVector || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedAttackComplexity__', CVSS31.XML_MetricNames['MAC'][ModifiedAttackComplexity || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedPrivilegesRequired__', CVSS31.XML_MetricNames['MPR'][ModifiedPrivilegesRequired || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedUserInteraction__', CVSS31.XML_MetricNames['MUI'][ModifiedUserInteraction || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedScope__', CVSS31.XML_MetricNames['MS'][ModifiedScope || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedConfidentiality__', CVSS31.XML_MetricNames['MCIA'][ModifiedConfidentiality || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedIntegrity__', CVSS31.XML_MetricNames['MCIA'][ModifiedIntegrity || 'X'], ); xmlOutput = xmlOutput.replace( '__ModifiedAvailability__', CVSS31.XML_MetricNames['MCIA'][ModifiedAvailability || 'X'], ); xmlOutput = xmlOutput.replace( '__EnvironmentalScore__', result.environmentalMetricScore, ); xmlOutput = xmlOutput.replace( '__EnvironmentalSeverityRating__', result.environmentalSeverity, ); return { success: true, xmlString: xmlOutput }; }; /* ** CVSS31.generateXMLFromVector ** * * Takes Base, Temporal and Environmental metric values as a single string in the Vector String format defined * in the CVSS v3.1 standard definition of the Vector String. * * Returns an XML string representation of this input. See the comment for CVSS31.generateXMLFromMetrics for more * detail on inputs, return values and errors. In addition to the error conditions listed for that function, this * function can also return: * "MalformedVectorString", if the Vector String passed is does not conform to the format in the standard; or * "MultipleDefinitionsOfMetric", if the Vector String is well formed but defines the same metric (or metrics), * more than once. */ CVSS31.generateXMLFromVector = function (vectorString) { var metricValues = { AV: undefined, AC: undefined, PR: undefined, UI: undefined, S: undefined, C: undefined, I: undefined, A: undefined, E: undefined, RL: undefined, RC: undefined, CR: undefined, IR: undefined, AR: undefined, MAV: undefined, MAC: undefined, MPR: undefined, MUI: undefined, MS: undefined, MC: undefined, MI: undefined, MA: undefined, }; // If input validation fails, this array is populated with strings indicating which metrics failed validation. var badMetrics = []; if (!CVSS31.vectorStringRegex_31.test(vectorString)) { return { success: false, errorType: 'MalformedVectorString' }; } var metricNameValue = vectorString .substring(CVSS31.CVSSVersionIdentifier.length) .split('/'); for (var i in metricNameValue) { if (metricNameValue.hasOwnProperty(i)) { var singleMetric = metricNameValue[i].split(':'); if (typeof metricValues[singleMetric[0]] === 'undefined') { metricValues[singleMetric[0]] = singleMetric[1]; } else { badMetrics.push(singleMetric[0]); } } } if (badMetrics.length > 0) { return { success: false, errorType: 'MultipleDefinitionsOfMetric', errorMetrics: badMetrics, }; } return CVSS31.generateXMLFromMetrics( metricValues.AV, metricValues.AC, metricValues.PR, metricValues.UI, metricValues.S, metricValues.C, metricValues.I, metricValues.A, metricValues.E, metricValues.RL, metricValues.RC, metricValues.CR, metricValues.IR, metricValues.AR, metricValues.MAV, metricValues.MAC, metricValues.MPR, metricValues.MUI, metricValues.MS, metricValues.MC, metricValues.MI, metricValues.MA, ); }; module.exports = CVSS31;