/ src / cvsscalc31.js
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  };