diff --git a/lib/cvss.ts b/lib/cvss.ts index be70e49..7acc1c1 100644 --- a/lib/cvss.ts +++ b/lib/cvss.ts @@ -50,8 +50,6 @@ export function CVSS(cvss: string | CvssVectorObject) { * @returns {string} returns one of the five possible ratings */ function getTemporalRating() { - if (!(util.getVersion(vector) === "3.0" || util.getVersion(vector) === "3.1")) - throw "This function is not supported for this cvss version"; return util.getRating(getTemporalScore()); } @@ -64,8 +62,6 @@ export function CVSS(cvss: string | CvssVectorObject) { * @returns {string} returns one of the five possible ratings */ function getEnvironmentalRating() { - if (!(util.getVersion(vector) === "3.0" || util.getVersion(vector) === "3.1")) - throw "This function is not supported for this cvss version"; return util.getRating(getEnvironmentalScore()); } @@ -104,8 +100,6 @@ export function CVSS(cvss: string | CvssVectorObject) { * @returns {number} Temporal Score */ function getTemporalScore() { - if (!(util.getVersion(vector) === "3.0" || util.getVersion(vector) === "3.1")) - throw "This function is not supported for this cvss version"; return score.getTemporalScore(vector); } @@ -116,8 +110,6 @@ export function CVSS(cvss: string | CvssVectorObject) { * @returns {number} Environmental Score */ function getEnvironmentalScore() { - if (!(util.getVersion(vector) === "3.0" || util.getVersion(vector) === "3.1")) - throw "This function is not supported for this cvss version"; return score.getEnvironmentalScore(vector); } @@ -155,8 +147,6 @@ export function CVSS(cvss: string | CvssVectorObject) { * @returns {number} Impact sub score */ function getImpactSubScore() { - if (!(util.getVersion(vector) === "3.0" || util.getVersion(vector) === "3.1")) - throw "This function is not supported for this cvss version"; return score.getImpactSubScore(vector); } @@ -170,8 +160,6 @@ export function CVSS(cvss: string | CvssVectorObject) { * @returns {number} Exploitability sub score */ function getExploitabilitySubScore() { - if (!(util.getVersion(vector) === "3.0" || util.getVersion(vector) === "3.1")) - throw "This function is not supported for this cvss version"; return score.getExploitabilitySubScore(vector); } diff --git a/lib/cvss_3_0.ts b/lib/cvss_3_0.ts index 1a5d7fe..6446d7b 100644 --- a/lib/cvss_3_0.ts +++ b/lib/cvss_3_0.ts @@ -6,6 +6,7 @@ export const definitions: CvssVersionDefinition = { { name: "Attack Vector", abbr: "AV", + mandatory: true, metrics: [ { name: "Network", abbr: "N", numerical: 0.85 }, { name: "Adjacent", abbr: "A", numerical: 0.62 }, @@ -16,6 +17,7 @@ export const definitions: CvssVersionDefinition = { { name: "Attack Complexity", abbr: "AC", + mandatory: true, metrics: [ { name: "Low", abbr: "L", numerical: 0.77 }, { name: "High", abbr: "H", numerical: 0.44 } @@ -24,6 +26,7 @@ export const definitions: CvssVersionDefinition = { { name: "Privileges Required", abbr: "PR", + mandatory: true, metrics: [ { name: "None", abbr: "N", numerical: { changed: 0.85, unchanged: 0.85 } }, { name: "Low", abbr: "L", numerical: { changed: 0.68, unchanged: 0.62 } }, @@ -33,6 +36,7 @@ export const definitions: CvssVersionDefinition = { { name: "User Interaction", abbr: "UI", + mandatory: true, metrics: [ { name: "None", abbr: "N", numerical: 0.85 }, { name: "Required", abbr: "R", numerical: 0.62 } @@ -41,6 +45,7 @@ export const definitions: CvssVersionDefinition = { { name: "Scope", abbr: "S", + mandatory: true, metrics: [ { name: "Unchanged", abbr: "U" }, { name: "Changed", abbr: "C" } @@ -49,6 +54,7 @@ export const definitions: CvssVersionDefinition = { { name: "Confidentiality", abbr: "C", + mandatory: true, metrics: [ { name: "None", abbr: "N", numerical: 0 }, { name: "Low", abbr: "L", numerical: 0.22 }, @@ -58,6 +64,7 @@ export const definitions: CvssVersionDefinition = { { name: "Integrity", abbr: "I", + mandatory: true, metrics: [ { name: "None", abbr: "N", numerical: 0 }, { name: "Low", abbr: "L", numerical: 0.22 }, @@ -67,6 +74,7 @@ export const definitions: CvssVersionDefinition = { { name: "Availability", abbr: "A", + mandatory: true, metrics: [ { name: "None", abbr: "N", numerical: 0 }, { name: "Low", abbr: "L", numerical: 0.22 }, @@ -76,6 +84,7 @@ export const definitions: CvssVersionDefinition = { { name: "Exploit Code Maturity", abbr: "E", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "High", abbr: "H", numerical: 1 }, @@ -87,6 +96,7 @@ export const definitions: CvssVersionDefinition = { { name: "Remediation Level", abbr: "RL", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "Unavailable", abbr: "U", numerical: 1 }, @@ -98,6 +108,7 @@ export const definitions: CvssVersionDefinition = { { name: "Report Confidence", abbr: "RC", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "Confirmed", abbr: "C", numerical: 1 }, @@ -108,6 +119,7 @@ export const definitions: CvssVersionDefinition = { { name: "Confidentiality Req.", abbr: "CR", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "High", abbr: "H", numerical: 1.5 }, @@ -118,6 +130,7 @@ export const definitions: CvssVersionDefinition = { { name: "Integrity Req.", abbr: "IR", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "High", abbr: "H", numerical: 1.5 }, @@ -128,6 +141,7 @@ export const definitions: CvssVersionDefinition = { { name: "Availability Req.", abbr: "AR", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "High", abbr: "H", numerical: 1.5 }, @@ -138,6 +152,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Attack Vector", abbr: "MAV", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "Network", abbr: "N", numerical: 0.85 }, @@ -149,6 +164,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Attack Complexity", abbr: "MAC", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "Low", abbr: "L", numerical: 0.77 }, @@ -158,6 +174,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Privileges Required", abbr: "MPR", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: { changed: 1, unchanged: 1 } }, { name: "None", abbr: "N", numerical: { changed: 0.85, unchanged: 0.85 } }, @@ -168,6 +185,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified User Interaction", abbr: "MUI", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "None", abbr: "N", numerical: 0.85 }, @@ -177,6 +195,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Scope", abbr: "MS", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X" }, { name: "Unchanged", abbr: "U" }, @@ -186,6 +205,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Confidentiality", abbr: "MC", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "None", abbr: "N", numerical: 0 }, @@ -196,6 +216,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Integrity", abbr: "MI", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "None", abbr: "N", numerical: 0 }, @@ -206,6 +227,7 @@ export const definitions: CvssVersionDefinition = { { name: "Modified Availability", abbr: "MA", + mandatory: false, metrics: [ { name: "Not Defined", abbr: "X", numerical: 1 }, { name: "None", abbr: "N", numerical: 0 }, diff --git a/lib/cvss_4_0.ts b/lib/cvss_4_0.ts index 5dbb4df..4137526 100644 --- a/lib/cvss_4_0.ts +++ b/lib/cvss_4_0.ts @@ -1,8 +1,9 @@ -export const definitions = { +import { CvssVersionDefinition, CvssLookup, MaxComposed, MaxSeverity } from "./types"; + +export const definitions: CvssVersionDefinition = { version: "4.0", definitions: [ { - metricGroup: "Base", name: "Attack Vector", abbr: "AV", mandatory: true, @@ -14,7 +15,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Attack Complexity", abbr: "AC", mandatory: true, @@ -24,7 +24,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Attack Requirements", abbr: "AT", mandatory: true, @@ -34,7 +33,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Privileges Required", abbr: "PR", mandatory: true, @@ -45,7 +43,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "User Interaction", abbr: "UI", mandatory: true, @@ -56,7 +53,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Vulnerable System Confidentiality Impact", abbr: "VC", mandatory: true, @@ -67,7 +63,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Vulnerable System Integrity Impact", abbr: "VI", mandatory: true, @@ -78,7 +73,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Vulnerable System Availability Impact", abbr: "VA", mandatory: true, @@ -89,7 +83,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Subsequent System Confidentiality Impact", abbr: "SC", mandatory: true, @@ -100,7 +93,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Subsequent System Integrity Impact", abbr: "SI", mandatory: true, @@ -112,7 +104,6 @@ export const definitions = { ] }, { - metricGroup: "Base", name: "Subsequent System Availability Impact", abbr: "SA", mandatory: true, @@ -124,7 +115,6 @@ export const definitions = { ] }, { - metricGroup: "Threat", name: "Exploit Maturity", abbr: "E", mandatory: false, @@ -136,7 +126,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Confidentiality Requirement", abbr: "CR", mandatory: false, @@ -148,7 +137,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Integrity Requirement", abbr: "IR", mandatory: false, @@ -160,7 +148,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Availability Requirement", abbr: "AR", mandatory: false, @@ -172,7 +159,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Attack Vector", abbr: "MAV", mandatory: false, @@ -185,7 +171,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Attack Complexity", abbr: "MAC", mandatory: false, @@ -196,7 +181,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Attack Requirements ", abbr: "MAT", mandatory: false, @@ -207,7 +191,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Privileges Required", abbr: "MPR", mandatory: false, @@ -219,7 +202,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified User Interaction", abbr: "MUI", mandatory: false, @@ -231,7 +213,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Vulnerable System Confidentiality", abbr: "MVC", mandatory: false, @@ -243,7 +224,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Vulnerable System Integrity", abbr: "MVI", mandatory: false, @@ -255,7 +235,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Vulnerable System Availability", abbr: "MVA", mandatory: false, @@ -267,7 +246,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Subsequent System Confidentiality", abbr: "MSC", mandatory: false, @@ -279,7 +257,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Subsequent System Integrity", abbr: "MSI", mandatory: false, @@ -292,7 +269,6 @@ export const definitions = { ] }, { - metricGroup: "Enviromental", name: "Modified Subsequent System Availability", abbr: "MSA", mandatory: false, @@ -305,7 +281,6 @@ export const definitions = { ] }, { - metricGroup: "Supplemental", name: "Safety", abbr: "S", mandatory: false, @@ -316,9 +291,8 @@ export const definitions = { ] }, { - metricGroup: "Supplemental", name: "Automatable", - abbr: "Au", + abbr: "AU", mandatory: false, metrics: [ { name: "Not Defined", abbr: "X" }, @@ -327,7 +301,6 @@ export const definitions = { ] }, { - metricGroup: "Supplemental", name: "Recovery", abbr: "R", mandatory: false, @@ -339,7 +312,6 @@ export const definitions = { ] }, { - metricGroup: "Supplemental", name: "Value Density", abbr: "V", mandatory: false, @@ -350,7 +322,6 @@ export const definitions = { ] }, { - metricGroup: "Supplemental", name: "Vulnerability Response Effort", abbr: "RE", mandatory: false, @@ -362,7 +333,6 @@ export const definitions = { ] }, { - metricGroup: "Supplemental", name: "Provider Urgency", abbr: "U", mandatory: false, @@ -377,7 +347,7 @@ export const definitions = { ] }; -export const cvssLookup_global = { +export const cvssLookup_global: CvssLookup = { "000000": 10, "000001": 9.9, "000010": 9.8, @@ -650,7 +620,7 @@ export const cvssLookup_global = { "212221": 0.1 }; -export const maxComposed = { +export const maxComposed: MaxComposed = { // EQ1 eq1: { 0: ["AV:N/PR:N/UI:N/"], @@ -694,7 +664,7 @@ export const maxComposed = { } }; -export const maxSeverity = { +export const maxSeverity: MaxSeverity = { eq1: { 0: 1, 1: 4, diff --git a/lib/score_4_0.ts b/lib/score_4_0.ts index 4013739..6f21610 100644 --- a/lib/score_4_0.ts +++ b/lib/score_4_0.ts @@ -1,14 +1,19 @@ import { CvssVectorObject, Metric, MetricUnion } from "./types"; -import { definitions, cvssLookup_global, maxComposed, maxSeverity } from "./cvss_4_0"; +import { cvssLookup_global, maxComposed, maxSeverity } from "./cvss_4_0"; import { util } from "./util"; -const parseMetric = function ( - value: T, - abbr: string, - vectorObject: CvssVectorObject -) { +/** + * Finds the vector's value for a specific metric and checks for additional scoring rules. + * + * @param {string} abbr Abbreviation of the vector metric + * @param {CvssVectorObject} vectorObject Vector object of interested + * + * @returns {MetricUnion} Calculated Score + */ +const parseMetric = function (abbr: string, vectorObject: CvssVectorObject) { const definition = util.findMetric(abbr, vectorObject.CVSS); + let value = util.findMetricValue(abbr, vectorObject); if (vectorObject.CVSS === "4.0") { // If E=X it will default to the worst case i.e. E=A @@ -27,8 +32,7 @@ const parseMetric = function ( if (abbr == "AR" && vectorObject["AR"] == "X") { return definition.metrics.find((metric) => metric.abbr === "H") as T; } - // All other environmental metrics just overwrite base score values, - // so if they’re not defined just use the base score value. + // All other environmental metrics just overwrite base score values if they are defined, when not defined use base score if (vectorObject["M" + abbr] !== undefined && vectorObject["M" + abbr] !== "X") { const modifiedDefinition = util.findMetric("M" + abbr, vectorObject.CVSS); value = definition.metrics.find( @@ -39,10 +43,64 @@ const parseMetric = function ( return value; }; +const eq3eq6CalculateLowerMacroVector = function (eqLevels) { + if (eqLevels.eq3 === "1" && eqLevels.eq6 === "1") { + return cvssLookup_global[ + `${eqLevels.eq1}${eqLevels.eq2}${parseInt(eqLevels.eq3) + 1}${eqLevels.eq4}${eqLevels.eq5}${ + eqLevels.eq6 + }` + ]; + } + if (eqLevels.eq3 === "1" && eqLevels.eq6 === "0") { + return cvssLookup_global[ + `${eqLevels.eq1}${eqLevels.eq2}${eqLevels.eq3}${eqLevels.eq4}${eqLevels.eq5}${ + parseInt(eqLevels.eq6) + 1 + }` + ]; + } + if (eqLevels.eq3 === "0" && eqLevels.eq6 === "1") { + return cvssLookup_global[ + `${eqLevels.eq1}${eqLevels.eq2}${parseInt(eqLevels.eq3) + 1}${eqLevels.eq4}${eqLevels.eq5}${ + eqLevels.eq6 + }` + ]; + } + if (eqLevels.eq3 === "0" && eqLevels.eq6 === "0") { + const eq3eq6NextLowerLeftMarcoVector = + cvssLookup_global[ + `${eqLevels.eq1}${eqLevels.eq2}${eqLevels.eq3}${eqLevels.eq4}${eqLevels.eq5}${ + parseInt(eqLevels.eq6) + 1 + }` + ]; + const eq3eq6NextLowerRightMarcoVector = + cvssLookup_global[ + `${eqLevels.eq1}${eqLevels.eq2}${parseInt(eqLevels.eq3) + 1}${eqLevels.eq4}${eqLevels.eq5}${ + eqLevels.eq6 + }` + ]; + return eq3eq6NextLowerLeftMarcoVector > eq3eq6NextLowerRightMarcoVector + ? eq3eq6NextLowerLeftMarcoVector + : eq3eq6NextLowerRightMarcoVector; + } + + return cvssLookup_global[ + `${eqLevels.eq1}${eqLevels.eq2}${parseInt(eqLevels.eq3) + 1}${eqLevels.eq4}${eqLevels.eq5}${ + parseInt(eqLevels.eq6) + 1 + }` + ]; +}; + +/** + * Parses the vector to a number score + * + * @param {string} vector The vector string + * + * @returns {number} Calculated Score + */ function getScore(vector: string) { const vectorObj = util.getVectorObject(vector); - const metrics: { [key: string]: Metric } = { + const metrics = { AV: {} as Metric, // EQ1 PR: {} as Metric, // EQ1 UI: {} as Metric, // EQ1 @@ -63,15 +121,18 @@ function getScore(vector: string) { }; for (let [key] of Object.entries(metrics)) { - metrics[key] = parseMetric( - util.findMetricValue(key, vectorObj), - key, - vectorObj - ); + metrics[key] = parseMetric(key, vectorObj); } // calculate EQ levels - const eqLevels = { eq1: "0", eq2: "0", eq3: "0", eq4: "0", eq5: "0", eq6: "0" }; + const eqLevels = { + eq1: "0", + eq2: "0", + eq3: "0", + eq4: "0", + eq5: "0", + eq6: "0" + }; // EQ1 // 0 AV:N and PR:N and UI:N @@ -79,13 +140,13 @@ function getScore(vector: string) { // 2 AV:P or not(AV:N or PR:N or UI:N) if (metrics.AV.abbr === "N" && metrics.PR.abbr === "N" && metrics.UI.abbr === "N") eqLevels.eq1 = "0"; - else if ( + if ( (metrics.AV.abbr === "N" || metrics.PR.abbr === "N" || metrics.UI.abbr === "N") && !(metrics.AV.abbr === "N" && metrics.PR.abbr === "N" && metrics.UI.abbr === "N") && !(metrics.AV.abbr === "P") ) eqLevels.eq1 = "1"; - else if ( + if ( metrics.AV.abbr === "P" || !(metrics.AV.abbr === "N" || metrics.PR.abbr === "N" || metrics.UI.abbr === "N") ) @@ -95,19 +156,19 @@ function getScore(vector: string) { // 0 AC:L and AT:N // 1 not (AC:L and AT:N) if (metrics.AC.abbr === "L" && metrics.AT.abbr === "N") eqLevels.eq2 = "0"; - else if (!(metrics.AC.abbr === "L" && metrics.AT.abbr === "N")) eqLevels.eq2 = "1"; + if (!(metrics.AC.abbr === "L" && metrics.AT.abbr === "N")) eqLevels.eq2 = "1"; // EQ3 // 0 VC:H and VI:H // 1 not (VC:H and VI:H) and (VC:H or VI:H or VA:H) // 2 not (VC:H or VI:H or VA:H) if (metrics.VC.abbr === "H" && metrics.VI.abbr === "H") eqLevels.eq3 = "0"; - else if ( + if ( !(metrics.VC.abbr === "H" && metrics.VI.abbr === "H") && (metrics.VC.abbr === "H" || metrics.VI.abbr === "H" || metrics.VA.abbr === "H") ) eqLevels.eq3 = "1"; - else if (!(metrics.VC.abbr === "H" || metrics.VI.abbr === "H" || metrics.VA.abbr === "H")) + if (!(metrics.VC.abbr === "H" || metrics.VI.abbr === "H" || metrics.VA.abbr === "H")) eqLevels.eq3 = "2"; // EQ4 @@ -117,12 +178,12 @@ function getScore(vector: string) { // If MSI=X or MSA=X they will default to the corresponding value of SI and SA according to the rules of Modified Base Metrics in section 4.2 (See Table 15). // So if there are no modified base metrics, the highest value that EQ4 can reach is 1. if (metrics.MSI.abbr === "S" || metrics.MSA.abbr === "S") eqLevels.eq4 = "0"; - else if ( + if ( !(metrics.MSI.abbr === "S" || metrics.MSA.abbr === "S") && (metrics.SC.abbr === "H" || metrics.SI.abbr === "H" || metrics.SA.abbr === "H") ) eqLevels.eq4 = "1"; - else if ( + if ( !(metrics.MSI.abbr === "S" || metrics.MSA.abbr === "S") && !(metrics.SC.abbr === "H" || metrics.SI.abbr === "H" || metrics.SA.abbr === "H") ) @@ -134,8 +195,8 @@ function getScore(vector: string) { // 2 E:U // If E=X it will default to the worst case (i.e., E=A). if (metrics.E.abbr === "A") eqLevels.eq5 = "0"; - else if (metrics.E.abbr === "P") eqLevels.eq5 = "1"; - else if (metrics.E.abbr === "U") eqLevels.eq5 = "2"; + if (metrics.E.abbr === "P") eqLevels.eq5 = "1"; + if (metrics.E.abbr === "U") eqLevels.eq5 = "2"; // EQ6 // 0 (CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H) @@ -147,7 +208,7 @@ function getScore(vector: string) { ((metrics.AR.abbr === "H" || metrics.AR.abbr === "X") && metrics.VA.abbr === "H") ) eqLevels.eq6 = "0"; - else if ( + if ( !((metrics.CR.abbr === "H" || metrics.CR.abbr === "X") && metrics.VC.abbr === "H") && !((metrics.IR.abbr === "H" || metrics.IR.abbr === "X") && metrics.VI.abbr === "H") && !((metrics.AR.abbr === "H" || metrics.AR.abbr === "X") && metrics.VA.abbr === "H") @@ -161,132 +222,33 @@ function getScore(vector: string) { // 1.1 The maximal scoring difference is determined as the difference between the current MacroVector and the lower MacroVector // 1.1.1 there is no lower MacroVector the available distance is set to NaN and then ignored in the further calculations // The scores of each MacroVector can be found in the cvssLookup table - const eq1NextLowerMarcoVectorScore = cvssLookup_global[ - "".concat( - "" + (parseInt(eqLevels.eq1) + 1), - eqLevels.eq2, - eqLevels.eq3, - eqLevels.eq4, - eqLevels.eq5, + `${parseInt(eqLevels.eq1) + 1}${eqLevels.eq2}${eqLevels.eq3}${eqLevels.eq4}${eqLevels.eq5}${ eqLevels.eq6 - ) + }` ]; const eq2NextLowerMarcoVectorScore = cvssLookup_global[ - "".concat( - eqLevels.eq1, - "" + (parseInt(eqLevels.eq2) + 1), - eqLevels.eq3, - eqLevels.eq4, - eqLevels.eq5, + `${eqLevels.eq1}${parseInt(eqLevels.eq2) + 1}${eqLevels.eq3}${eqLevels.eq4}${eqLevels.eq5}${ eqLevels.eq6 - ) + }` ]; const eq4NextLowerMarcoVectorScore = cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - eqLevels.eq3, - "" + (parseInt(eqLevels.eq4) + 1), - eqLevels.eq5, + `${eqLevels.eq1}${eqLevels.eq2}${eqLevels.eq3}${parseInt(eqLevels.eq4) + 1}${eqLevels.eq5}${ eqLevels.eq6 - ) + }` ]; const eq5NextLowerMarcoVectorScore = cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - eqLevels.eq3, - eqLevels.eq4, - "" + (parseInt(eqLevels.eq5) + 1), + `${eqLevels.eq1}${eqLevels.eq2}${eqLevels.eq3}${eqLevels.eq4}${parseInt(eqLevels.eq5) + 1}${ eqLevels.eq6 - ) + }` ]; // EQ3 and EQ6 are joint see Table 30, an if case represents an change in level constraint f.e 11 -> 21 - let eq3eq6NextLowerMarcoVector = 0; - let eq3eq6NextLowerLeftMarcoVector = 0; - let eq3eq6NextLowerRightMarcoVector = 0; - if (eqLevels.eq3 === "1" && eqLevels.eq6 === "1") { - eq3eq6NextLowerMarcoVector = - cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - "" + (parseInt(eqLevels.eq3) + 1), - eqLevels.eq4, - eqLevels.eq5, - eqLevels.eq6 - ) - ]; - } else if (eqLevels.eq3 === "1" && eqLevels.eq6 === "0") { - eq3eq6NextLowerMarcoVector = - cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - eqLevels.eq3, - eqLevels.eq4, - eqLevels.eq5, - "" + (parseInt(eqLevels.eq6) + 1) - ) - ]; - } else if (eqLevels.eq3 === "0" && eqLevels.eq6 === "1") { - eq3eq6NextLowerMarcoVector = - cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - "" + (parseInt(eqLevels.eq3) + 1), - eqLevels.eq4, - eqLevels.eq5, - eqLevels.eq6 - ) - ]; - } else if (eqLevels.eq3 === "0" && eqLevels.eq6 === "0") { - eq3eq6NextLowerLeftMarcoVector = - cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - eqLevels.eq3, - eqLevels.eq4, - eqLevels.eq5, - "" + (parseInt(eqLevels.eq6) + 1) - ) - ]; - eq3eq6NextLowerRightMarcoVector = - cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - "" + (parseInt(eqLevels.eq3) + 1), - eqLevels.eq4, - eqLevels.eq5, - eqLevels.eq6 - ) - ]; - eq3eq6NextLowerMarcoVector = - eq3eq6NextLowerLeftMarcoVector > eq3eq6NextLowerRightMarcoVector - ? eq3eq6NextLowerLeftMarcoVector - : eq3eq6NextLowerRightMarcoVector; - } // cannot exist path - else - eq3eq6NextLowerMarcoVector = - cvssLookup_global[ - "".concat( - eqLevels.eq1, - eqLevels.eq2, - "" + (parseInt(eqLevels.eq3) + 1), - eqLevels.eq4, - eqLevels.eq5, - "" + (parseInt(eqLevels.eq6) + 1) - ) - ]; + let eq3eq6NextLowerMarcoVector = eq3eq6CalculateLowerMacroVector(eqLevels); // 1.2. The severity distance of the to-be scored vector from a highest severity vector in the same MacroVector is determined const maxima = { @@ -298,7 +260,7 @@ function getScore(vector: string) { }; // combine all vector maximas to create all possible maximums - const possibleMaximumVectorStrings = []; + const possibleMaximumVectorStrings: string[] = []; for (const eq1Max of maxima.eq1) { for (const eq2Max of maxima.eq2) { for (const eq3eq6Max of maxima.eq3eq6) { @@ -338,9 +300,7 @@ function getScore(vector: string) { innerLoop: for (let [key] of Object.entries(severityDistance)) { severityDistance[key] = - metrics[key].numerical - - parseMetric(util.findMetricValue(key, maxVectorObj), key, maxVectorObj) - .numerical; + metrics[key].numerical - parseMetric(key, maxVectorObj).numerical; // if any of the values is negative, a greater max vector can be found if (severityDistance[key] < 0) { @@ -364,7 +324,7 @@ function getScore(vector: string) { break; } - // calculate maximal scoring difference + // calculate maximal scoring difference (msd) const currentMacroVectorValue = cvssLookup_global[macroVector]; const msd = { eq1: currentMacroVectorValue - eq1NextLowerMarcoVectorScore, @@ -442,6 +402,56 @@ function getScore(vector: string) { return parseFloat(vectorScore.toFixed(1)); } +/** + * Error function for unsupport function + * + * @param {string} vector The vector string + * + * @returns Error + */ +function getTemporalScore(vector: string) { + throw new Error("This function is not supported for this cvss version"); + return 0; +} + +/** + * Error function for unsupport function + * + * @param {string} vector The vector string + * + * @returns Error + */ +function getEnvironmentalScore(vector: string) { + throw new Error("This function is not supported for this cvss version"); + return 0; +} + +/** + * Error function for unsupport function + * + * @param {string} vector The vector string + * + * @returns Error + */ +function getImpactSubScore(vector: string) { + throw new Error("This function is not supported for this cvss version"); +} + +/** + * Error function for unsupport function + * + * @param {string} vector The vector string + * + * @returns Error + */ +function getExploitabilitySubScore(vector: string) { + throw new Error("This function is not supported for this cvss version"); +} + export const score = { - getScore + getScore, + getTemporalScore, + getEnvironmentalScore, + getImpactSubScore, + getExploitabilitySubScore }; diff --git a/lib/testFile.ts b/lib/testFile.ts deleted file mode 100644 index d517a96..0000000 --- a/lib/testFile.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { score } from "./score_4_0"; -import { CVSS } from "./cvss"; -// TODO: delete this file - -console.log( - CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N").getVectorObject() -); -const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); -vector.getTemporalScore(); diff --git a/lib/types.ts b/lib/types.ts index 99f196b..3d21e7a 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -7,20 +7,20 @@ export type CvssVectorObject = { C: "N" | "L" | "H"; I: "N" | "L" | "H"; A: "N" | "L" | "H"; - E: "X" | "H" | "F" | "P" | "U"; - RL: "X" | "U" | "W" | "T" | "O"; - RC: "X" | "C" | "R" | "U"; - CR: "X" | "H" | "M" | "L"; - IR: "X" | "H" | "M" | "L"; - AR: "X" | "H" | "M" | "L"; - MAV: "X" | "N" | "A" | "L" | "P"; - MAC: "X" | "L" | "H"; - MPR: "X" | "N" | "L" | "H"; - MUI: "X" | "N" | "R"; - MS: "X" | "U" | "C"; - MC: "X" | "N" | "L" | "H"; - MI: "X" | "N" | "L" | "H"; - MA: "X" | "N" | "L" | "H"; + E?: "X" | "H" | "F" | "P" | "U"; + RL?: "X" | "U" | "W" | "T" | "O"; + RC?: "X" | "C" | "R" | "U"; + CR?: "X" | "H" | "M" | "L"; + IR?: "X" | "H" | "M" | "L"; + AR?: "X" | "H" | "M" | "L"; + MAV?: "X" | "N" | "A" | "L" | "P"; + MAC?: "X" | "L" | "H"; + MPR?: "X" | "N" | "L" | "H"; + MUI?: "X" | "N" | "R"; + MS?: "X" | "U" | "C"; + MC?: "X" | "N" | "L" | "H"; + MI?: "X" | "N" | "L" | "H"; + MA?: "X" | "N" | "L" | "H"; CVSS: string; }; @@ -56,6 +56,7 @@ export type MetricUnion = Metric | MetricScope | MetricPrivilegesRequired; export type Definition = { name: string; abbr: string; + mandatory: boolean; metrics: MetricUnion[]; }; @@ -63,3 +64,10 @@ export type CvssVersionDefinition = { version: string; definitions: Definition[]; }; +export type CvssLookup = { [key: string]: number }; + +export type MaxComposed = { + [key: string]: { [key: number]: { [key: string]: string[] } | string[] }; +}; + +export type MaxSeverity = { [key: string]: { [key: number]: { [key: number]: number } | number } }; diff --git a/lib/util.ts b/lib/util.ts index 7a9a775..a0bdd83 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -21,7 +21,7 @@ const findMetric = function (abbr: string, cvssVersion: string) { * @param {string} abbr Abbreviation of the vector metric * @param {CvssVectorObject} vectorObject Vector object of interested * - * @returns {metric | undefined} The metric matching to the given abbriviation or undefined if no match is found + * @returns {MetricUnion} The metric matching to the given abbriviation or undefined if no match is found */ const findMetricValue = function ( abbr: string, @@ -29,35 +29,6 @@ const findMetricValue = function ( ) { const definition = findMetric(abbr, vectorObject.CVSS); let value = definition.metrics.find((metric) => metric.abbr === vectorObject[definition.abbr]); - - // important for cvss 4.0 scoring - if (vectorObject.CVSS === "4.0") { - // If E=X it will default to the worst case i.e. E=A - if (abbr == "E" && vectorObject["E"] == "X") { - return definition.metrics.find((metric) => metric.abbr === "A") as T; - } - // If CR=X, IR=X or AR=X they will default to the worst case i.e. CR=H, IR=H and AR=H - if (abbr == "CR" && vectorObject["CR"] == "X") { - return definition.metrics.find((metric) => metric.abbr === "H") as T; - } - // IR:X is the same as IR:H - if (abbr == "IR" && vectorObject["IR"] == "X") { - return definition.metrics.find((metric) => metric.abbr === "H") as T; - } - // AR:X is the same as AR:H - if (abbr == "AR" && vectorObject["AR"] == "X") { - return definition.metrics.find((metric) => metric.abbr === "H") as T; - } - // All other environmental metrics just overwrite base score values, - // so if they’re not defined just use the base score value. - if (vectorObject["M" + abbr] !== undefined && vectorObject["M" + abbr] !== "X") { - const modifiedDefinition = findMetric("M" + abbr, vectorObject.CVSS); - value = definition.metrics.find( - (metric) => metric.abbr === vectorObject[modifiedDefinition.abbr] - ); - } - } - return value as T; }; @@ -223,8 +194,6 @@ const isVectorValid = function (vector: string) { const totalExpressionVector = new RegExp("^CVSS:(3.(0|1)|4.0)(/" + expression + ")+$"); - console.log(totalExpressionVector); - //Checks if the vector is in valid format if (!totalExpressionVector.test(vector)) { return false; @@ -248,43 +217,29 @@ const isVectorValid = function (vector: string) { ); }); - console.log(allExpressions); - for (const regex of allExpressions) { if ((vector.match(regex) || []).length > 1) { return false; } } - const mandatoryParamsVersion3_0 = [ - /\/AV:[NALP]/g, - /\/AC:[LH]/g, - /\/PR:[NLH]/g, - /\/UI:[NR]/g, - /\/S:[UC]/g, - /\/C:[NLH]/g, - /\/I:[NLH]/g, - /\/A:[NLH]/g - ]; - - const mandatoryParamsVersion4_0 = [ - /\/AV:[NALP]/g, - /\/AC:[LH]/g, - /\/AT:[NP]/g, - /\/PR:[NLH]/g, - /\/UI:[NPA]/g, - /\/VC:[NLH]/g, - /\/VI:[NLH]/g, - /\/VA:[NLH]/g, - /\/SC:[NLH]/g, - /\/SI:[NLHS]/g, - /\/SA:[NLHS]/g - ]; + /** + * Scans the definitions file and returns the array of mandatory registered abbreviation + * with its possible values. + */ + const mandatoryExpressions = definitions.definitions + .filter((definition) => definition.mandatory) + .map((currentValue) => { + return new RegExp( + `/${currentValue.abbr}:[${currentValue.metrics.reduce((accumulator2, currentValue2) => { + return accumulator2 + currentValue2.abbr; + }, "")}]`, + "g" + ); + }); //Checks whether all mandatory parameters are present in the vector - for (const regex of getVersion(vector) === "4.0" - ? mandatoryParamsVersion4_0 - : mandatoryParamsVersion3_0) { + for (const regex of mandatoryExpressions) { if ((vector.match(regex) || []).length < 1) { return false; } diff --git a/test/cvss_3_0.spec.js b/test/cvss_3_0.spec.ts similarity index 100% rename from test/cvss_3_0.spec.js rename to test/cvss_3_0.spec.ts diff --git a/test/cvss_4_0.spec.js b/test/cvss_4_0.spec.ts similarity index 73% rename from test/cvss_4_0.spec.js rename to test/cvss_4_0.spec.ts index 99314fe..48d1655 100644 --- a/test/cvss_4_0.spec.js +++ b/test/cvss_4_0.spec.ts @@ -1,6 +1,5 @@ import { CVSS } from "../lib/cvss"; -// examples taken from: https://www.first.org/cvss/v4.0/examples const examples = [ { score: 7.3, vector: "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N" }, { score: 7.7, vector: "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N" }, @@ -43,7 +42,7 @@ const examples = [ { score: 8.5, vector: "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/S:P" }, { score: 9.4, - vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/S:P/AU:Y/V:C/RE:L" // this breaks + vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/S:P/AU:Y/V:C/RE:L" }, { score: 7.0, @@ -71,47 +70,56 @@ const examples = [ describe("Score Tests", () => { it("Should return the score", () => { // These are just some example test cases. - - const vector = CVSS( - "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/S:P/AU:Y/V:C/RE:L" - ); - console.log(vector.getVersion()); - expect(vector.getScore()).toBe(9.4); - - // for (const ex of examples) { - // try { - // const vector = CVSS(ex.vector); - // console.log(vector.getVersion()); - // expect(vector.getScore()).toBe(ex.score); - // } catch { - // console.log(ex.vector, ex.score); - // } - // } + for (const example of examples) { + const vector = CVSS(example.vector); + expect(vector.getScore()).toBe(example.score); + } }); }); -// describe("Version Tests", () => { -// it("Should throw error when calling getVersion", () => { -// const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); -// expect(vector.getVersion()).toBe("4.0"); -// }); -// }); +describe("Version Tests", () => { + it("Should return the correct version when calling getVersion", () => { + const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); + expect(vector.getVersion()).toBe("4.0"); + }); +}); describe("Temporal Tests", () => { it("Should throw error when calling getTemporalScore", () => { const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); - vector.getTemporalScore(); - expect(function () { - parser.parse(raw); - }).toThrow("This function is not supported for this cvss version"); + const fn = () => { + vector.getTemporalScore(); + }; + expect(fn).toThrow("This function is not supported for this cvss version"); + }); +}); + +describe("Environmental score tests", () => { + it("Should throw error when calling getEnvironmentalScore", () => { + const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); + const fn = () => { + vector.getEnvironmentalScore(); + }; + expect(fn).toThrow("This function is not supported for this cvss version"); + }); +}); + +describe("ImpactSub score tests", () => { + it("Should throw error when calling getImpactSubScore", () => { + const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); + const fn = () => { + vector.getImpactSubScore(); + }; + expect(fn).toThrow("This function is not supported for this cvss version"); }); }); -// describe("Environmental score tests", () => { -// it("Should throw error when calling getEnvironmentalScore", () => { -// const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); -// expect(vector.getEnvironmentalScore()).toThrow( -// "This function is not supported for this cvss version" -// ); -// }); -// }); +describe("ExploitabilitySub score tests", () => { + it("Should throw error when calling getExploitabilitySubScore", () => { + const vector = CVSS("CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"); + const fn = () => { + vector.getExploitabilitySubScore(); + }; + expect(fn).toThrow("This function is not supported for this cvss version"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 33c6e04..0f953e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "emitDeclarationOnly": true, "outDir": "dist", "resolveJsonModule": true, - "lib": ["es6"], + "lib": ["es6", "es2017.object"], "target": "es5" } }