Spaces:
Runtime error
Runtime error
/* 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 = | |
'<?xml version="1.0" encoding="UTF-8"?>\n' + | |
'<cvssv3.1 xmlns="https://www.first.org/cvss/cvss-v3.1.xsd"\n' + | |
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + | |
' xsi:schemaLocation="https://www.first.org/cvss/cvss-v3.1.xsd https://www.first.org/cvss/cvss-v3.1.xsd"\n' + | |
' >\n' + | |
'\n' + | |
' <base_metrics>\n' + | |
' <attack-vector>__AttackVector__</attack-vector>\n' + | |
' <attack-complexity>__AttackComplexity__</attack-complexity>\n' + | |
' <privileges-required>__PrivilegesRequired__</privileges-required>\n' + | |
' <user-interaction>__UserInteraction__</user-interaction>\n' + | |
' <scope>__Scope__</scope>\n' + | |
' <confidentiality-impact>__Confidentiality__</confidentiality-impact>\n' + | |
' <integrity-impact>__Integrity__</integrity-impact>\n' + | |
' <availability-impact>__Availability__</availability-impact>\n' + | |
' <base-score>__BaseScore__</base-score>\n' + | |
' <base-severity>__BaseSeverityRating__</base-severity>\n' + | |
' </base_metrics>\n' + | |
'\n' + | |
' <temporal_metrics>\n' + | |
' <exploit-code-maturity>__ExploitCodeMaturity__</exploit-code-maturity>\n' + | |
' <remediation-level>__RemediationLevel__</remediation-level>\n' + | |
' <report-confidence>__ReportConfidence__</report-confidence>\n' + | |
' <temporal-score>__TemporalScore__</temporal-score>\n' + | |
' <temporal-severity>__TemporalSeverityRating__</temporal-severity>\n' + | |
' </temporal_metrics>\n' + | |
'\n' + | |
' <environmental_metrics>\n' + | |
' <confidentiality-requirement>__ConfidentialityRequirement__</confidentiality-requirement>\n' + | |
' <integrity-requirement>__IntegrityRequirement__</integrity-requirement>\n' + | |
' <availability-requirement>__AvailabilityRequirement__</availability-requirement>\n' + | |
' <modified-attack-vector>__ModifiedAttackVector__</modified-attack-vector>\n' + | |
' <modified-attack-complexity>__ModifiedAttackComplexity__</modified-attack-complexity>\n' + | |
' <modified-privileges-required>__ModifiedPrivilegesRequired__</modified-privileges-required>\n' + | |
' <modified-user-interaction>__ModifiedUserInteraction__</modified-user-interaction>\n' + | |
' <modified-scope>__ModifiedScope__</modified-scope>\n' + | |
' <modified-confidentiality-impact>__ModifiedConfidentiality__</modified-confidentiality-impact>\n' + | |
' <modified-integrity-impact>__ModifiedIntegrity__</modified-integrity-impact>\n' + | |
' <modified-availability-impact>__ModifiedAvailability__</modified-availability-impact>\n' + | |
' <environmental-score>__EnvironmentalScore__</environmental-score>\n' + | |
' <environmental-severity>__EnvironmentalSeverityRating__</environmental-severity>\n' + | |
' </environmental_metrics>\n' + | |
'\n' + | |
'</cvssv3.1>\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; | |