UNPKG

@rohit_coder/cvss

Version:

The Common Vulnerability Scoring System ([CVSS](https://www.first.org/cvss/)) [base](https://www.first.org/cvss/specification-document#Base-Metrics) [score](https://www.first.org/cvss/specification-document#1-2-Scoring) calculator and validator library wr

1,324 lines (1,320 loc) 54 kB
var BaseMetric; (function (BaseMetric) { BaseMetric["ATTACK_VECTOR"] = "AV"; BaseMetric["ATTACK_COMPLEXITY"] = "AC"; BaseMetric["PRIVILEGES_REQUIRED"] = "PR"; BaseMetric["USER_INTERACTION"] = "UI"; BaseMetric["SCOPE"] = "S"; BaseMetric["CONFIDENTIALITY"] = "C"; BaseMetric["INTEGRITY"] = "I"; BaseMetric["AVAILABILITY"] = "A"; })(BaseMetric || (BaseMetric = {})); var TemporalMetric; (function (TemporalMetric) { TemporalMetric["EXPLOIT_CODE_MATURITY"] = "E"; TemporalMetric["REMEDIATION_LEVEL"] = "RL"; TemporalMetric["REPORT_CONFIDENCE"] = "RC"; })(TemporalMetric || (TemporalMetric = {})); var EnvironmentalMetric; (function (EnvironmentalMetric) { EnvironmentalMetric["CONFIDENTIALITY_REQUIREMENT"] = "CR"; EnvironmentalMetric["INTEGRITY_REQUIREMENT"] = "IR"; EnvironmentalMetric["AVAILABILITY_REQUIREMENT"] = "AR"; EnvironmentalMetric["MODIFIED_ATTACK_VECTOR"] = "MAV"; EnvironmentalMetric["MODIFIED_ATTACK_COMPLEXITY"] = "MAC"; EnvironmentalMetric["MODIFIED_PRIVILEGES_REQUIRED"] = "MPR"; EnvironmentalMetric["MODIFIED_USER_INTERACTION"] = "MUI"; EnvironmentalMetric["MODIFIED_SCOPE"] = "MS"; EnvironmentalMetric["MODIFIED_CONFIDENTIALITY"] = "MC"; EnvironmentalMetric["MODIFIED_INTEGRITY"] = "MI"; EnvironmentalMetric["MODIFIED_AVAILABILITY"] = "MA"; })(EnvironmentalMetric || (EnvironmentalMetric = {})); const baseMetrics = [ BaseMetric.ATTACK_VECTOR, BaseMetric.ATTACK_COMPLEXITY, BaseMetric.PRIVILEGES_REQUIRED, BaseMetric.USER_INTERACTION, BaseMetric.SCOPE, BaseMetric.CONFIDENTIALITY, BaseMetric.INTEGRITY, BaseMetric.AVAILABILITY ]; const temporalMetrics = [ TemporalMetric.EXPLOIT_CODE_MATURITY, TemporalMetric.REMEDIATION_LEVEL, TemporalMetric.REPORT_CONFIDENCE ]; const environmentalMetrics = [ EnvironmentalMetric.AVAILABILITY_REQUIREMENT, EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, EnvironmentalMetric.INTEGRITY_REQUIREMENT, EnvironmentalMetric.MODIFIED_ATTACK_VECTOR, EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY, EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED, EnvironmentalMetric.MODIFIED_USER_INTERACTION, EnvironmentalMetric.MODIFIED_SCOPE, EnvironmentalMetric.MODIFIED_CONFIDENTIALITY, EnvironmentalMetric.MODIFIED_INTEGRITY, EnvironmentalMetric.MODIFIED_AVAILABILITY ]; const baseMetricValues = { [BaseMetric.ATTACK_VECTOR]: ['N', 'A', 'L', 'P'], [BaseMetric.ATTACK_COMPLEXITY]: ['L', 'H'], [BaseMetric.PRIVILEGES_REQUIRED]: ['N', 'L', 'H'], [BaseMetric.USER_INTERACTION]: ['N', 'R'], [BaseMetric.SCOPE]: ['U', 'C'], [BaseMetric.CONFIDENTIALITY]: ['N', 'L', 'H'], [BaseMetric.INTEGRITY]: ['N', 'L', 'H'], [BaseMetric.AVAILABILITY]: ['N', 'L', 'H'], // None, Low, High }; const temporalMetricValues = { [TemporalMetric.EXPLOIT_CODE_MATURITY]: ['X', 'H', 'F', 'P', 'U'], [TemporalMetric.REMEDIATION_LEVEL]: ['X', 'U', 'W', 'T', 'O'], [TemporalMetric.REPORT_CONFIDENCE]: ['X', 'C', 'R', 'U'] }; const environmentalMetricValues = { [EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: ['X', 'H', 'M', 'L'], [EnvironmentalMetric.INTEGRITY_REQUIREMENT]: ['X', 'H', 'M', 'L'], [EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: ['X', 'H', 'M', 'L'], [EnvironmentalMetric.MODIFIED_ATTACK_VECTOR]: ['X', 'N', 'A', 'L', 'P'], [EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY]: ['X', 'L', 'H'], [EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED]: ['X', 'N', 'L', 'H'], [EnvironmentalMetric.MODIFIED_USER_INTERACTION]: ['X', 'N', 'R'], [EnvironmentalMetric.MODIFIED_SCOPE]: ['X', 'U', 'C'], [EnvironmentalMetric.MODIFIED_CONFIDENTIALITY]: ['X', 'N', 'L', 'H'], [EnvironmentalMetric.MODIFIED_INTEGRITY]: ['X', 'N', 'L', 'H'], [EnvironmentalMetric.MODIFIED_AVAILABILITY]: ['X', 'N', 'L', 'H'] }; // Copyright FIRST, Red Hat, and contributors // SPDX-License-Identifier: BSD-2-Clause // CVSS v4.0 metrics ordering and valid values const expectedMetricOrder = { // Base (11 metrics) "AV": ["N", "A", "L", "P"], "AC": ["L", "H"], "AT": ["N", "P"], "PR": ["N", "L", "H"], "UI": ["N", "P", "A"], "VC": ["H", "L", "N"], "VI": ["H", "L", "N"], "VA": ["H", "L", "N"], "SC": ["H", "L", "N"], "SI": ["H", "L", "N"], "SA": ["H", "L", "N"], // Threat (1 metric) "E": ["X", "A", "P", "U"], // Environmental (14 metrics) "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"], "MAT": ["X", "N", "P"], "MPR": ["X", "N", "L", "H"], "MUI": ["X", "N", "P", "A"], "MVC": ["X", "H", "L", "N"], "MVI": ["X", "H", "L", "N"], "MVA": ["X", "H", "L", "N"], "MSC": ["X", "H", "L", "N"], "MSI": ["X", "S", "H", "L", "N"], "MSA": ["X", "S", "H", "L", "N"], // Supplemental (6 metrics) "S": ["X", "N", "P"], "AU": ["X", "N", "Y"], "R": ["X", "A", "U", "I"], "V": ["X", "D", "C"], "RE": ["X", "L", "M", "H"], "U": ["X", "Clear", "Green", "Amber", "Red"], }; // max severity distances in EQs MacroVectors (+1) const maxSeverityV4 = { "eq1": { 0: 1, 1: 4, 2: 5 }, "eq2": { 0: 1, 1: 2 }, "eq3eq6": { 0: { 0: 7, 1: 6 }, 1: { 0: 8, 1: 8 }, 2: { 1: 10 } }, "eq4": { 0: 6, 1: 5, 2: 4 }, "eq5": { 0: 1, 1: 1, 2: 1 }, }; const cvssLookup_globalV4 = { "000000": 10, "000001": 9.9, "000010": 9.8, "000011": 9.5, "000020": 9.5, "000021": 9.2, "000100": 10, "000101": 9.6, "000110": 9.3, "000111": 8.7, "000120": 9.1, "000121": 8.1, "000200": 9.3, "000201": 9, "000210": 8.9, "000211": 8, "000220": 8.1, "000221": 6.8, "001000": 9.8, "001001": 9.5, "001010": 9.5, "001011": 9.2, "001020": 9, "001021": 8.4, "001100": 9.3, "001101": 9.2, "001110": 8.9, "001111": 8.1, "001120": 8.1, "001121": 6.5, "001200": 8.8, "001201": 8, "001210": 7.8, "001211": 7, "001220": 6.9, "001221": 4.8, "002001": 9.2, "002011": 8.2, "002021": 7.2, "002101": 7.9, "002111": 6.9, "002121": 5, "002201": 6.9, "002211": 5.5, "002221": 2.7, "010000": 9.9, "010001": 9.7, "010010": 9.5, "010011": 9.2, "010020": 9.2, "010021": 8.5, "010100": 9.5, "010101": 9.1, "010110": 9, "010111": 8.3, "010120": 8.4, "010121": 7.1, "010200": 9.2, "010201": 8.1, "010210": 8.2, "010211": 7.1, "010220": 7.2, "010221": 5.3, "011000": 9.5, "011001": 9.3, "011010": 9.2, "011011": 8.5, "011020": 8.5, "011021": 7.3, "011100": 9.2, "011101": 8.2, "011110": 8, "011111": 7.2, "011120": 7, "011121": 5.9, "011200": 8.4, "011201": 7, "011210": 7.1, "011211": 5.2, "011220": 5, "011221": 3, "012001": 8.6, "012011": 7.5, "012021": 5.2, "012101": 7.1, "012111": 5.2, "012121": 2.9, "012201": 6.3, "012211": 2.9, "012221": 1.7, "100000": 9.8, "100001": 9.5, "100010": 9.4, "100011": 8.7, "100020": 9.1, "100021": 8.1, "100100": 9.4, "100101": 8.9, "100110": 8.6, "100111": 7.4, "100120": 7.7, "100121": 6.4, "100200": 8.7, "100201": 7.5, "100210": 7.4, "100211": 6.3, "100220": 6.3, "100221": 4.9, "101000": 9.4, "101001": 8.9, "101010": 8.8, "101011": 7.7, "101020": 7.6, "101021": 6.7, "101100": 8.6, "101101": 7.6, "101110": 7.4, "101111": 5.8, "101120": 5.9, "101121": 5, "101200": 7.2, "101201": 5.7, "101210": 5.7, "101211": 5.2, "101220": 5.2, "101221": 2.5, "102001": 8.3, "102011": 7, "102021": 5.4, "102101": 6.5, "102111": 5.8, "102121": 2.6, "102201": 5.3, "102211": 2.1, "102221": 1.3, "110000": 9.5, "110001": 9, "110010": 8.8, "110011": 7.6, "110020": 7.6, "110021": 7, "110100": 9, "110101": 7.7, "110110": 7.5, "110111": 6.2, "110120": 6.1, "110121": 5.3, "110200": 7.7, "110201": 6.6, "110210": 6.8, "110211": 5.9, "110220": 5.2, "110221": 3, "111000": 8.9, "111001": 7.8, "111010": 7.6, "111011": 6.7, "111020": 6.2, "111021": 5.8, "111100": 7.4, "111101": 5.9, "111110": 5.7, "111111": 5.7, "111120": 4.7, "111121": 2.3, "111200": 6.1, "111201": 5.2, "111210": 5.7, "111211": 2.9, "111220": 2.4, "111221": 1.6, "112001": 7.1, "112011": 5.9, "112021": 3, "112101": 5.8, "112111": 2.6, "112121": 1.5, "112201": 2.3, "112211": 1.3, "112221": 0.6, "200000": 9.3, "200001": 8.7, "200010": 8.6, "200011": 7.2, "200020": 7.5, "200021": 5.8, "200100": 8.6, "200101": 7.4, "200110": 7.4, "200111": 6.1, "200120": 5.6, "200121": 3.4, "200200": 7, "200201": 5.4, "200210": 5.2, "200211": 4, "200220": 4, "200221": 2.2, "201000": 8.5, "201001": 7.5, "201010": 7.4, "201011": 5.5, "201020": 6.2, "201021": 5.1, "201100": 7.2, "201101": 5.7, "201110": 5.5, "201111": 4.1, "201120": 4.6, "201121": 1.9, "201200": 5.3, "201201": 3.6, "201210": 3.4, "201211": 1.9, "201220": 1.9, "201221": 0.8, "202001": 6.4, "202011": 5.1, "202021": 2, "202101": 4.7, "202111": 2.1, "202121": 1.1, "202201": 2.4, "202211": 0.9, "202221": 0.4, "210000": 8.8, "210001": 7.5, "210010": 7.3, "210011": 5.3, "210020": 6, "210021": 5, "210100": 7.3, "210101": 5.5, "210110": 5.9, "210111": 4, "210120": 4.1, "210121": 2, "210200": 5.4, "210201": 4.3, "210210": 4.5, "210211": 2.2, "210220": 2, "210221": 1.1, "211000": 7.5, "211001": 5.5, "211010": 5.8, "211011": 4.5, "211020": 4, "211021": 2.1, "211100": 6.1, "211101": 5.1, "211110": 4.8, "211111": 1.8, "211120": 2, "211121": 0.9, "211200": 4.6, "211201": 1.8, "211210": 1.7, "211211": 0.7, "211220": 0.8, "211221": 0.2, "212001": 5.3, "212011": 2.4, "212021": 1.4, "212101": 2.4, "212111": 1.2, "212121": 0.5, "212201": 1, "212211": 0.3, "212221": 0.1, }; const maxComposed = { // EQ1 "eq1": { 0: ["AV:N/PR:N/UI:N/"], 1: ["AV:A/PR:N/UI:N/", "AV:N/PR:L/UI:N/", "AV:N/PR:N/UI:P/"], 2: ["AV:P/PR:N/UI:N/", "AV:A/PR:L/UI:P/"] }, // EQ2 "eq2": { 0: ["AC:L/AT:N/"], 1: ["AC:H/AT:N/", "AC:L/AT:P/"] }, // EQ3+EQ6 "eq3": { 0: { "0": ["VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/"], "1": ["VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/", "VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/"] }, 1: { "0": ["VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/", "VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/"], "1": ["VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/", "VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/", "VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/", "VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/", "VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/"] }, 2: { "1": ["VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/"] }, }, // EQ4 "eq4": { 0: ["SC:H/SI:S/SA:S/"], 1: ["SC:H/SI:H/SA:H/"], 2: ["SC:L/SI:L/SA:L/"] }, // EQ5 "eq5": { 0: ["E:A/"], 1: ["E:P/"], 2: ["E:U/"], }, }; const humanizeBaseMetric = (metric) => { switch (metric) { case BaseMetric.ATTACK_VECTOR: return 'Attack Vector'; case BaseMetric.ATTACK_COMPLEXITY: return 'Attack Complexity'; case BaseMetric.PRIVILEGES_REQUIRED: return 'Privileges Required'; case BaseMetric.USER_INTERACTION: return 'User Interaction'; case BaseMetric.SCOPE: return 'Scope'; case BaseMetric.CONFIDENTIALITY: return 'Confidentiality'; case BaseMetric.INTEGRITY: return 'Integrity'; case BaseMetric.AVAILABILITY: return 'Availability'; default: return 'Unknown'; } }; // eslint-disable-next-line complexity const humanizeBaseMetricValue = (value, metric) => { switch (value) { case 'A': return 'Adjacent'; case 'C': return 'Changed'; case 'H': return 'High'; case 'L': return metric === BaseMetric.ATTACK_VECTOR ? 'Local' : 'Low'; case 'N': return metric === BaseMetric.ATTACK_VECTOR ? 'Network' : 'None'; case 'P': return 'Physical'; case 'R': return 'Required'; case 'U': return 'Unchanged'; default: return 'Unknown'; } }; /** * Stringify a score into a qualitative severity rating string * @param score */ const humanizeScore = (score) => score <= 0 ? 'None' : score <= 3.9 ? 'Low' : score <= 6.9 ? 'Medium' : score <= 8.9 ? 'High' : 'Critical'; const VERSION_REGEX = /^CVSS:(\d(?:\.\d)?)(.*)?$/; const parseVersion = (cvssStr) => { const versionRegexRes = VERSION_REGEX.exec(cvssStr); return versionRegexRes && versionRegexRes[1]; }; const parseVector = (cvssStr) => { const versionRegexRes = VERSION_REGEX.exec(cvssStr); return versionRegexRes && versionRegexRes[2] && versionRegexRes[2].substr(1); }; const parseMetrics = (vectorStr) => (vectorStr ? vectorStr.split('/') : []).map((metric) => { if (!metric) { return { key: '', value: '' }; } const parts = metric.split(':'); return { key: parts[0], value: parts[1] }; }); const parseMetricsAsMap = (cvssStr) => parseMetrics(parseVector(cvssStr) || '').reduce((res, metric) => { if (res.has(metric.key)) { throw new Error(`Duplicated metric: "${metric.key}:${metric.value || ''}"`); } return res.set(metric.key, metric.value); }, new Map()); /** * Validates a CVSS vector string according to expected metrics and mandatory requirements. * @param {string} vectorString - The CVSS vector string to validate. * @returns {{ valid: boolean, error?: string, selectedMetrics?: Record<string, string> }} * Returns an object indicating whether the vector is valid, with an error message if invalid. */ const validateVectorV4 = (vectorString) => { let metrics = vectorString ? vectorString.split("/") : []; const prefix = metrics[0]; if (prefix !== "CVSS:4.0") { return { valid: false, error: "Invalid vector prefix" }; } metrics.shift(); let expectedIndex = 0; const toSelect = {}; const expectedEntries = Object.entries(expectedMetricOrder); const mandatoryMetrics = ['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA']; for (const metric of metrics) { const [key, value] = metric.split(":"); const expectedEntry = expectedEntries.find(entry => entry[0] === key); if (key in toSelect) { return { valid: false, error: `Invalid vector, repeated metric: ${key}` }; } while (expectedIndex < expectedEntries.length && expectedEntries[expectedIndex][0] !== key) { expectedIndex++; } if (expectedIndex >= expectedEntries.length) { return { valid: false, error: `Invalid vector, metric out of sequence: ${key}` }; } if (!expectedEntry) { return { valid: false, error: `Invalid vector, unexpected metric: ${key}` }; } if (!expectedEntry[1].includes(value)) { return { valid: false, error: `Invalid vector, for key ${key}, value ${value} is not in [${expectedEntry[1]}]` }; } toSelect[key] = value; } const missingMandatoryMetrics = mandatoryMetrics.filter(metric => !(metric in toSelect)); if (missingMandatoryMetrics.length > 0) { return { valid: false, error: `Invalid vector, missing mandatory metrics: ${missingMandatoryMetrics.join(', ')}` }; } return { valid: true, selectedMetrics: toSelect }; }; const validateVersion = (versionStr) => { if (!versionStr) { throw new Error('Invalid CVSS string. Example: CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L'); } if (versionStr !== '3.0' && versionStr !== '3.1' && versionStr !== '4.0') { throw new Error(`Unsupported CVSS version: ${versionStr}. Only 3.0 and 3.1 are supported`); } }; const validateVector = (vectorStr) => { if (!vectorStr || vectorStr.includes('//')) { throw new Error('Invalid CVSS string. Example: CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L'); } }; const checkUnknownMetrics = (metricsMap, knownMetrics) => { const allKnownMetrics = knownMetrics || [ ...baseMetrics, ...temporalMetrics, ...environmentalMetrics ]; [...metricsMap.keys()].forEach((userMetric) => { if (!allKnownMetrics.includes(userMetric)) { throw new Error(`Unknown CVSS metric "${userMetric}". Allowed metrics: ${allKnownMetrics.join(', ')}`); } }); }; const checkMandatoryMetrics = (metricsMap, metrics = baseMetrics) => { metrics.forEach((metric) => { if (!metricsMap.has(metric)) { // eslint-disable-next-line max-len throw new Error(`Missing mandatory CVSS metric ${metrics} (${humanizeBaseMetric(metric)})`); } }); }; const checkMetricsValues = (metricsMap, metrics, metricsValues) => { metrics.forEach((metric) => { const userValue = metricsMap.get(metric); if (!userValue) { return; } if (!metricsValues[metric].includes(userValue)) { const allowedValuesHumanized = metricsValues[metric] .map((value) => `${value} (${humanizeBaseMetricValue(value, metric)})`) .join(', '); throw new Error(`Invalid value for CVSS metric ${metric} (${humanizeBaseMetric(metric)})${userValue ? `: ${userValue}` : ''}. Allowed values: ${allowedValuesHumanized}`); } }); }; /** * Validate that the given string is a valid cvss vector * @param cvssStr */ const validate = (cvssStr) => { if (!cvssStr || !cvssStr.startsWith('CVSS:')) { throw new Error('CVSS vector must start with "CVSS:"'); } if (cvssStr.startsWith('CVSS:4')) { validateVectorV4(cvssStr); } const allKnownMetrics = [ ...baseMetrics, ...temporalMetrics, ...environmentalMetrics ]; const allKnownMetricsValues = { ...baseMetricValues, ...temporalMetricValues, ...environmentalMetricValues }; const versionStr = parseVersion(cvssStr); validateVersion(versionStr); const vectorStr = parseVector(cvssStr); validateVector(vectorStr); const metricsMap = parseMetricsAsMap(cvssStr); checkMandatoryMetrics(metricsMap); checkUnknownMetrics(metricsMap, allKnownMetrics); checkMetricsValues(metricsMap, allKnownMetrics, allKnownMetricsValues); const isTemporal = [...metricsMap.keys()].some((metric) => temporalMetrics.includes(metric)); const isEnvironmental = [...metricsMap.keys()].some((metric) => environmentalMetrics.includes(metric)); return { metricsMap, isTemporal, isEnvironmental, versionStr }; }; const detectCvssVersion = (cvssString) => { const versionPrefix = cvssString.split('/')[0]; if (versionPrefix.startsWith('CVSS:4.0')) return '4.0'; if (versionPrefix.startsWith('CVSS:3.')) return '3.1'; throw new Error(`Unsupported CVSS version: ${versionPrefix}`); }; // https://www.first.org/cvss/v3.1/specification-document#7-4-Metric-Values const baseMetricValueScores = { [BaseMetric.ATTACK_VECTOR]: { N: 0.85, A: 0.62, L: 0.55, P: 0.2 }, [BaseMetric.ATTACK_COMPLEXITY]: { L: 0.77, H: 0.44 }, [BaseMetric.PRIVILEGES_REQUIRED]: null, [BaseMetric.USER_INTERACTION]: { N: 0.85, R: 0.62 }, [BaseMetric.SCOPE]: { U: 0, C: 0 }, [BaseMetric.CONFIDENTIALITY]: { N: 0, L: 0.22, H: 0.56 }, [BaseMetric.INTEGRITY]: { N: 0, L: 0.22, H: 0.56 }, [BaseMetric.AVAILABILITY]: { N: 0, L: 0.22, H: 0.56 } }; const temporalMetricValueScores = { [TemporalMetric.EXPLOIT_CODE_MATURITY]: { X: 1, U: 0.91, F: 0.97, P: 0.94, H: 1 }, [TemporalMetric.REMEDIATION_LEVEL]: { X: 1, O: 0.95, T: 0.96, W: 0.97, U: 1 }, [TemporalMetric.REPORT_CONFIDENCE]: { X: 1, U: 0.92, R: 0.96, C: 1 } }; const environmentalMetricValueScores = { [EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 }, [EnvironmentalMetric.INTEGRITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 }, [EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 }, [EnvironmentalMetric.MODIFIED_ATTACK_VECTOR]: baseMetricValueScores[BaseMetric.ATTACK_VECTOR], [EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY]: baseMetricValueScores[BaseMetric.ATTACK_COMPLEXITY], [EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED]: null, [EnvironmentalMetric.MODIFIED_USER_INTERACTION]: baseMetricValueScores[BaseMetric.USER_INTERACTION], [EnvironmentalMetric.MODIFIED_SCOPE]: baseMetricValueScores[BaseMetric.SCOPE], [EnvironmentalMetric.MODIFIED_CONFIDENTIALITY]: baseMetricValueScores[BaseMetric.CONFIDENTIALITY], [EnvironmentalMetric.MODIFIED_INTEGRITY]: baseMetricValueScores[BaseMetric.INTEGRITY], [EnvironmentalMetric.MODIFIED_AVAILABILITY]: baseMetricValueScores[BaseMetric.AVAILABILITY] }; const getPrivilegesRequiredNumericValue = (value, scopeValue) => { if (scopeValue !== 'U' && scopeValue !== 'C' && scopeValue !== 'X') { throw new Error(`Unknown Scope value: ${scopeValue}`); } switch (value) { case 'N': return 0.85; case 'L': return scopeValue === 'U' ? 0.62 : 0.68; case 'H': return scopeValue === 'U' ? 0.27 : 0.5; default: throw new Error(`Unknown PrivilegesRequired value: ${value}`); } }; const getMetricValue = (metric, metricsMap) => { if (!metricsMap.has(metric)) { console.log("metricsMap", metricsMap); throw new Error(`Missing metric: ${metric}`); } return metricsMap.get(metric); }; const getMetricNumericValue = (metric, metricsMap) => { const value = getMetricValue(metric || TemporalMetric || EnvironmentalMetric, metricsMap); if (metric === BaseMetric.PRIVILEGES_REQUIRED) { return getPrivilegesRequiredNumericValue(value, getMetricValue(BaseMetric.SCOPE, metricsMap)); } if (metric === EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED) { return getPrivilegesRequiredNumericValue(value, getMetricValue(EnvironmentalMetric.MODIFIED_SCOPE, metricsMap)); } const score = { ...baseMetricValueScores, ...temporalMetricValueScores, ...environmentalMetricValueScores }[metric]; if (!score) { throw new Error(`Internal error. Missing metric score: ${metric}`); } return score[value]; }; // ISS = 1 - [ (1 - Confidentiality) × (1 - Integrity) × (1 - Availability) ] const calculateIss = (metricsMap) => { const confidentiality = getMetricNumericValue(BaseMetric.CONFIDENTIALITY, metricsMap); const integrity = getMetricNumericValue(BaseMetric.INTEGRITY, metricsMap); const availability = getMetricNumericValue(BaseMetric.AVAILABILITY, metricsMap); return 1 - (1 - confidentiality) * (1 - integrity) * (1 - availability); }; // https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations // MISS = Minimum ( 1 - [ (1 - ConfidentialityRequirement × ModifiedConfidentiality) × (1 - IntegrityRequirement × ModifiedIntegrity) × (1 - AvailabilityRequirement × ModifiedAvailability) ], 0.915) const calculateMiss = (metricsMap) => { const rConfidentiality = getMetricNumericValue(EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, metricsMap); const mConfidentiality = getMetricNumericValue(EnvironmentalMetric.MODIFIED_CONFIDENTIALITY, metricsMap); const rIntegrity = getMetricNumericValue(EnvironmentalMetric.INTEGRITY_REQUIREMENT, metricsMap); const mIntegrity = getMetricNumericValue(EnvironmentalMetric.MODIFIED_INTEGRITY, metricsMap); const rAvailability = getMetricNumericValue(EnvironmentalMetric.AVAILABILITY_REQUIREMENT, metricsMap); const mAvailability = getMetricNumericValue(EnvironmentalMetric.MODIFIED_AVAILABILITY, metricsMap); return Math.min(1 - (1 - rConfidentiality * mConfidentiality) * (1 - rIntegrity * mIntegrity) * (1 - rAvailability * mAvailability), 0.915); }; // https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations // Impact = // If Scope is Unchanged 6.42 × ISS // If Scope is Changed 7.52 × (ISS - 0.029) - 3.25 × (ISS - 0.02)^15 const calculateImpact = (metricsMap, iss) => metricsMap.get(BaseMetric.SCOPE) === 'U' ? 6.42 * iss : 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15); // https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations // ModifiedImpact = // If ModifiedScope is Unchanged 6.42 × MISS // If ModifiedScope is Changed 7.52 × (MISS - 0.029) - 3.25 × (MISS × 0.9731 - 0.02)^13 // ModifiedExploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction // Note : Math.pow is 15 in 3.0 but 13 in 3.1 const calculateModifiedImpact = (metricsMap, miss, versionStr) => metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U' ? 6.42 * miss : 7.52 * (miss - 0.029) - 3.25 * Math.pow(miss * (versionStr === '3.0' ? 1 : 0.9731) - 0.02, versionStr === '3.0' ? 15 : 13); // https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations // Exploitability = 8.22 × AttackVector × AttackComplexity × PrivilegesRequired × UserInteraction const calculateExploitability = (metricsMap) => 8.22 * getMetricNumericValue(BaseMetric.ATTACK_VECTOR, metricsMap) * getMetricNumericValue(BaseMetric.ATTACK_COMPLEXITY, metricsMap) * getMetricNumericValue(BaseMetric.PRIVILEGES_REQUIRED, metricsMap) * getMetricNumericValue(BaseMetric.USER_INTERACTION, metricsMap); // https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations // Exploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction const calculateModifiedExploitability = (metricsMap) => 8.22 * getMetricNumericValue(EnvironmentalMetric.MODIFIED_ATTACK_VECTOR, metricsMap) * getMetricNumericValue(EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY, metricsMap) * getMetricNumericValue(EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED, metricsMap) * getMetricNumericValue(EnvironmentalMetric.MODIFIED_USER_INTERACTION, metricsMap); // https://www.first.org/cvss/v3.1/specification-document#Appendix-A---Floating-Point-Rounding const roundUp = (input) => { const intInput = Math.round(input * 100000); return intInput % 10000 === 0 ? intInput / 100000 : (Math.floor(intInput / 10000) + 1) / 10; }; const modifiedMetricsMap = { MAV: BaseMetric.ATTACK_VECTOR, MAC: BaseMetric.ATTACK_COMPLEXITY, MPR: BaseMetric.PRIVILEGES_REQUIRED, MUI: BaseMetric.USER_INTERACTION, MS: BaseMetric.SCOPE, MC: BaseMetric.CONFIDENTIALITY, MI: BaseMetric.INTEGRITY, MA: BaseMetric.AVAILABILITY }; // When Modified Temporal metric value is 'Not Defined' ('X'), which is the default value, // then Base metric value should be used. const populateTemporalMetricDefaults = (metricsMap) => { [...temporalMetrics].forEach((metric) => { if (!metricsMap.has(metric)) { metricsMap.set(metric, 'X'); } }); return metricsMap; }; const populateEnvironmentalMetricDefaults = (metricsMap) => { [...environmentalMetrics].forEach((metric) => { if (!metricsMap.has(metric)) { metricsMap.set(metric, 'X'); } if (metricsMap.get(metric) === 'X') { metricsMap.set(metric, metricsMap.has(modifiedMetricsMap[metric]) ? metricsMap.get(modifiedMetricsMap[metric]) : 'X'); } }); return metricsMap; }; // https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations // If Impact <= 0 => 0; else // If Scope is Unchanged => Roundup (Minimum [(Impact + Exploitability), 10]) // If Scope is Changed => Roundup (Minimum [1.08 × (Impact + Exploitability), 10]) const calculateBaseResult = (cvssString) => { const { metricsMap } = validate(cvssString); const iss = calculateIss(metricsMap); const impact = calculateImpact(metricsMap, iss); const exploitability = calculateExploitability(metricsMap); const scopeUnchanged = metricsMap.get(BaseMetric.SCOPE) === 'U'; const score = impact <= 0 ? 0 : scopeUnchanged ? roundUp(Math.min(impact + exploitability, 10)) : roundUp(Math.min(1.08 * (impact + exploitability), 10)); return { score, metricsMap, impact: impact <= 0 ? 0 : roundUp(impact), exploitability: impact <= 0 ? 0 : roundUp(exploitability) }; }; const calculateBaseScore = (cvssString) => { const version = detectCvssVersion(cvssString); if (version === '4.0') { return calculateBaseScoreV4(cvssString); } else if (version === '3.1') { const { score } = calculateBaseResult(cvssString); return score; } else { throw new Error(`Unsupported CVSS version: ${version}`); } }; // https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations // If ModifiedImpact <= 0 => 0; else // If ModifiedScope is Unchanged => Roundup (Roundup [Minimum ([ModifiedImpact + ModifiedExploitability], 10)] × ExploitCodeMaturity × RemediationLevel × ReportConfidence) // If ModifiedScope is Changed => Roundup (Roundup [Minimum (1.08 × [ModifiedImpact + ModifiedExploitability], 10)] × ExploitCodeMaturity × RemediationLevel × ReportConfidence) const calculateEnvironmentalResult = (cvssString) => { const validationResult = validate(cvssString); const { versionStr } = validationResult; let { metricsMap } = validationResult; metricsMap = populateTemporalMetricDefaults(metricsMap); metricsMap = populateEnvironmentalMetricDefaults(metricsMap); const miss = calculateMiss(metricsMap); const impact = calculateModifiedImpact(metricsMap, miss, versionStr); const exploitability = calculateModifiedExploitability(metricsMap); const scopeUnchanged = metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U'; const score = impact <= 0 ? 0 : scopeUnchanged ? roundUp(roundUp(Math.min(impact + exploitability, 10)) * getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap) * getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap)) : roundUp(roundUp(Math.min(1.08 * (impact + exploitability), 10)) * getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap) * getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap)); return { score, metricsMap, impact: impact <= 0 ? 0 : roundUp(impact), exploitability: impact <= 0 ? 0 : roundUp(exploitability) }; }; const calculateEnvironmentalScore = (cvssString) => { const { score } = calculateEnvironmentalResult(cvssString); return score; }; // https://www.first.org/cvss/v3.1/specification-document#7-2-Temporal-Metrics-Equations // Roundup (BaseScore × ExploitCodeMaturity × RemediationLevel × ReportConfidence) const calculateTemporalResult = (cvssString) => { let { metricsMap } = validate(cvssString); // populate temp metrics if not provided metricsMap = populateTemporalMetricDefaults(metricsMap); const { score, impact, exploitability } = calculateBaseResult(cvssString); const tempScore = roundUp(score * getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap) * getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap)); return { score: tempScore, metricsMap, impact, exploitability }; }; const calculateTemporalScore = (cvssString) => { const { score } = calculateTemporalResult(cvssString); return score; }; // V4 Logic goes here /// /** * Parses a CVSS vector string and adds default "X" values for unspecified metrics. * @param {string} vector - The CVSS vector string to parse. * @returns {Record<string, string>} - An object representing the parsed and completed metrics. */ const parseVectorV4 = (vector) => { const metrics = vector.split('/'); const cvssSelected = {}; // Remove CVSS:4.0 prefix metrics.shift(); metrics.forEach(metric => { const [key, value] = metric.split(':'); cvssSelected[key] = value; }); if (!("E" in cvssSelected)) { cvssSelected["E"] = "X"; } if (!("CR" in cvssSelected)) { cvssSelected["CR"] = "X"; } if (!("IR" in cvssSelected)) { cvssSelected["IR"] = "X"; } if (!("AR" in cvssSelected)) { cvssSelected["AR"] = "X"; } return cvssSelected; }; /** * Calculates a qualitative severity score based on the numeric CVSS score. * @param {number} score - The numeric CVSS score. * @returns {string} - The qualitative score: "None", "Low", "Medium", "High", or "Critical". */ const calculateQualScore = (score) => { if (score === 0) return "None"; if (score < 4.0) return "Low"; if (score < 7.0) return "Medium"; if (score < 9.0) return "High"; return "Critical"; }; /** * Calculates the base CVSS score for version 4.0 using a vector string. * @param {string} vectorString - The CVSS vector string to calculate the score for. * @returns {number} - The calculated base CVSS score. * @throws {Error} - Throws an error if the vector is invalid. */ const calculateBaseScoreV4 = (vectorString) => { const vvres = validateVectorV4(vectorString); if (!vvres.valid) { throw new Error(vvres.error); } const cvssSelected = parseVectorV4(vectorString); const macrov = macroVector(cvssSelected); let score = cvss_score(cvssSelected, cvssLookup_globalV4, maxSeverityV4, macrov); return score; }; const cvss_score = (cvssSelected, lookup, maxSeverityData, macroVectorResult) => { // The following defines the index of each metric's values. // It is used when looking for the highest vector part of the // combinations produced by the MacroVector respective highest vectors. let AV_levels = { "N": 0.0, "A": 0.1, "L": 0.2, "P": 0.3 }; let PR_levels = { "N": 0.0, "L": 0.1, "H": 0.2 }; let UI_levels = { "N": 0.0, "P": 0.1, "A": 0.2 }; let AC_levels = { 'L': 0.0, 'H': 0.1 }; let AT_levels = { 'N': 0.0, 'P': 0.1 }; let VC_levels = { 'H': 0.0, 'L': 0.1, 'N': 0.2 }; let VI_levels = { 'H': 0.0, 'L': 0.1, 'N': 0.2 }; let VA_levels = { 'H': 0.0, 'L': 0.1, 'N': 0.2 }; let SC_levels = { 'H': 0.1, 'L': 0.2, 'N': 0.3 }; let SI_levels = { 'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3 }; let SA_levels = { 'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3 }; let CR_levels = { 'H': 0.0, 'M': 0.1, 'L': 0.2 }; let IR_levels = { 'H': 0.0, 'M': 0.1, 'L': 0.2 }; let AR_levels = { 'H': 0.0, 'M': 0.1, 'L': 0.2 }; //let E_levels = { 'U': 0.2, 'P': 0.1, 'A': 0 } // Exception for no impact on system (shortcut) if (["VC", "VI", "VA", "SC", "SI", "SA"].every((metric) => m(cvssSelected, metric) == "N")) { return 0.0; } let value = lookup[macroVectorResult]; // 1. For each of the EQs: // a. The maximal scoring difference is determined as the difference // between the current MacroVector and the lower MacroVector. // i. If there is no lower MacroVector the available distance is // set to NaN and then ignored in the further calculations. let eq1 = parseInt(macroVectorResult[0]); let eq2 = parseInt(macroVectorResult[1]); let eq3 = parseInt(macroVectorResult[2]); let eq4 = parseInt(macroVectorResult[3]); let eq5 = parseInt(macroVectorResult[4]); let eq6 = parseInt(macroVectorResult[5]); // compute next lower macro, it can also not exist let eq1_next_lower_macro = `${eq1 + 1}${eq2}${eq3}${eq4}${eq5}${eq6}`; let eq2_next_lower_macro = `${eq1}${eq2 + 1}${eq3}${eq4}${eq5}${eq6}`; let eq3eq6_next_lower_macro = ""; let eq3eq6_next_lower_macro_left = ""; let eq3eq6_next_lower_macro_right = ""; // eq3 and eq6 are related if (eq3 == 1 && eq6 == 1) { // 11 --> 21 eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6 + 1}`; } else if (eq3 == 0 && eq6 == 1) { // 01 --> 11 eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6}`; } else if (eq3 == 1 && eq6 == 0) { // 10 --> 11 eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3}${eq4}${eq5}${eq6 + 1}`; } else if (eq3 == 0 && eq6 == 0) { // 00 --> 01 // 00 --> 10 eq3eq6_next_lower_macro_left = `${eq1}${eq2}${eq3}${eq4}${eq5}${eq6 + 1}`; eq3eq6_next_lower_macro_right = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6}`; } else { // 21 --> 32 (do not exist) eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6 + 1}`; } let eq4_next_lower_macro = `${eq1}${eq2}${eq3}${eq4 + 1}${eq5}${eq6}`; let eq5_next_lower_macro = `${eq1}${eq2}${eq3}${eq4}${eq5 + 1}${eq6}`; // get their score, if the next lower macro score do not exist the result is NaN let score_eq1_next_lower_macro = lookup[eq1_next_lower_macro]; let score_eq2_next_lower_macro = lookup[eq2_next_lower_macro]; let score_eq3eq6_next_lower_macro = NaN; if (eq3 == 0 && eq6 == 0) { // multiple path take the one with higher score let score_eq3eq6_next_lower_macro_left = lookup[eq3eq6_next_lower_macro_left]; let score_eq3eq6_next_lower_macro_right = lookup[eq3eq6_next_lower_macro_right]; score_eq3eq6_next_lower_macro_left = lookup[eq3eq6_next_lower_macro_left]; if (score_eq3eq6_next_lower_macro_left > score_eq3eq6_next_lower_macro_right) { score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_left; } else { score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_right; } } else { score_eq3eq6_next_lower_macro = lookup[eq3eq6_next_lower_macro]; } let score_eq4_next_lower_macro = lookup[eq4_next_lower_macro]; let score_eq5_next_lower_macro = lookup[eq5_next_lower_macro]; // b. The severity distance of the to-be scored vector from a // highest severity vector in the same MacroVector is determined. let eq1_maxes = getEQMaxes(macroVectorResult, 1); let eq2_maxes = getEQMaxes(macroVectorResult, 2); // @ts-ignore let eq3_eq6_maxes = getEQMaxes(macroVectorResult, 3)[macroVectorResult[5]]; let eq4_maxes = getEQMaxes(macroVectorResult, 4); let eq5_maxes = getEQMaxes(macroVectorResult, 5); // compose them // Compose them const max_vectors = []; for (let eq1_max of eq1_maxes) { for (let eq2_max of eq2_maxes) { for (let eq3_eq6_max of eq3_eq6_maxes) { for (let eq4_max of eq4_maxes) { for (let eq5_max of eq5_maxes) { max_vectors.push(eq1_max + eq2_max + eq3_eq6_max + eq4_max + eq5_max); } } } } } //console.log(max_vectors) // Find the max vector to use i.e. one in the combination of all the highests // that is greater or equal (severity distance) than the to-be scored vector. let severity_distance_AV = 0; let severity_distance_PR = 0; let severity_distance_UI = 0; let severity_distance_AC = 0; let severity_distance_AT = 0; let severity_distance_VC = 0; let severity_distance_VI = 0; let severity_distance_VA = 0; let severity_distance_SC = 0; let severity_distance_SI = 0; let severity_distance_SA = 0; let severity_distance_CR = 0; let severity_distance_IR = 0; let severity_distance_AR = 0; for (let i = 0; i < max_vectors.length; i++) { let max_vector = max_vectors[i]; severity_distance_AV = AV_levels[m(cvssSelected, "AV")] - AV_levels[extractValueMetric("AV", max_vector)]; severity_distance_PR = PR_levels[m(cvssSelected, "PR")] - PR_levels[extractValueMetric("PR", max_vector)]; severity_distance_UI = UI_levels[m(cvssSelected, "UI")] - UI_levels[extractValueMetric("UI", max_vector)]; severity_distance_AC = AC_levels[m(cvssSelected, "AC")] - AC_levels[extractValueMetric("AC", max_vector)]; severity_distance_AT = AT_levels[m(cvssSelected, "AT")] - AT_levels[extractValueMetric("AT", max_vector)]; severity_distance_VC = VC_levels[m(cvssSelected, "VC")] - VC_levels[extractValueMetric("VC", max_vector)]; severity_distance_VI = VI_levels[m(cvssSelected, "VI")] - VI_levels[extractValueMetric("VI", max_vector)]; severity_distance_VA = VA_levels[m(cvssSelected, "VA")] - VA_levels[extractValueMetric("VA", max_vector)]; severity_distance_SC = SC_levels[m(cvssSelected, "SC")] - SC_levels[extractValueMetric("SC", max_vector)]; severity_distance_SI = SI_levels[m(cvssSelected, "SI")] - SI_levels[extractValueMetric("SI", max_vector)]; severity_distance_SA = SA_levels[m(cvssSelected, "SA")] - SA_levels[extractValueMetric("SA", max_vector)]; severity_distance_CR = CR_levels[m(cvssSelected, "CR")] - CR_levels[extractValueMetric("CR", max_vector)]; severity_distance_IR = IR_levels[m(cvssSelected, "IR")] - IR_levels[extractValueMetric("IR", max_vector)]; severity_distance_AR = AR_levels[m(cvssSelected, "AR")] - AR_levels[extractValueMetric("AR", max_vector)]; // if any is less than zero this is not the right max if ([severity_distance_AV, severity_distance_PR, severity_distance_UI, severity_distance_AC, severity_distance_AT, severity_distance_VC, severity_distance_VI, severity_distance_VA, severity_distance_SC, severity_distance_SI, severity_distance_SA, severity_distance_CR, severity_distance_IR, severity_distance_AR].some((met) => met < 0)) { continue; } // if multiple maxes exist to reach it it is enough the first one break; } let current_severity_distance_eq1 = severity_distance_AV + severity_distance_PR + severity_distance_UI; let current_severity_distance_eq2 = severity_distance_AC + severity_distance_AT; let current_severity_distance_eq3eq6 = severity_distance_VC + severity_distance_VI + severity_distance_VA + severity_distance_CR + severity_distance_IR + severity_distance_AR; let current_severity_distance_eq4 = severity_distance_SC + severity_distance_SI + severity_distance_SA; // let current_severity_distance_eq5 = 0 let step = 0.1; // if the next lower macro score do not exist the result is Nan // Rename to maximal scoring difference (aka MSD) let available_distance_eq1 = value - score_eq1_next_lower_macro; let available_distance_eq2 = value - score_eq2_next_lower_macro; let available_distance_eq3eq6 = value - score_eq3eq6_next_lower_macro; let available_distance_eq4 = value - score_eq4_next_lower_macro; let available_distance_eq5 = value - score_eq5_next_lower_macro; let percent_to_next_eq1_severity = 0; let percent_to_next_eq2_severity = 0; let percent_to_next_eq3eq6_severity = 0; let percent_to_next_eq4_severity = 0; let percent_to_next_eq5_severity = 0; // some of them do not exist, we will find them by retrieving the score. If score null then do not exist let n_existing_lower = 0; let normalized_severity_eq1 = 0; let normalized_severity_eq2 = 0; let normalized_severity_eq3eq6 = 0; let normalized_severity_eq4 = 0; let normalized_severity_eq5 = 0; // multiply by step because distance is pure let maxSeverity_eq1 = maxSeverityData["eq1"][eq1] * step; let maxSeverity_eq2 = maxSeverityData["eq2"][eq2] * step; let maxSeverity_eq3eq6 = maxSeverityData["eq3eq6"][eq3][eq6] * step; let maxSeverity_eq4 = maxSeverityData["eq4"][eq4] * step; // c. The proportion of the distance is determined by dividing // the severity distance of the to-be-scored vector by the depth // of the MacroVector. // d. The maximal scoring difference is multiplied by the proportion of // distance. if (!isNaN(available_distance_eq1)) { n_existing_lower = n_existing_lower + 1; percent_to_next_eq1_severity = (current_severity_distance_eq1) / maxSeverity_eq1; normalized_severity_eq1 = available_distance_eq1 * percent_to_next_eq1_severity; } if (!isNaN(available_distance_eq2)) { n_existing_lower = n_existing_lower + 1; percent_to_next_eq2_severity = (current_severity_distance_eq2) / maxSeverity_eq2; normalized_severity_eq2 = available_distance_eq2 * percent_to_next_eq2_severity; } if (!isNaN(available_distance_eq3eq6)) { n_existing_lower = n_existing_lower + 1; percent_to_next_eq3eq6_severity = (current_severity_distance_eq3eq6) / maxSeverity_eq3eq6; normalized_severity_eq3eq6 = available_distance_eq3eq6 * percent_to_next_eq3eq6_severity; } if (!isNaN(available_distance_eq4)) { n_existing_lower = n_existing_lower + 1; percent_to_next_eq4_severity = (current_severity_distance_eq4) / maxSeverity_eq4; normalized_severity_eq4 = available_distance_eq4 * percent_to_next_eq4_severity; } if (!isNaN(available_distance_eq5)) { // for eq5 is always 0 the percentage n_existing_lower = n_existing_lower + 1; percent_to_next_eq5_severity = 0; normalized_severity_eq5 = available_distance_eq5 * percent_to_next_eq5_severity; } // 2. The mean of the above computed proportional distances is computed. let mean_distance = 0; if (n_existing_lower == 0) { mean_distance = 0; } else { // sometimes we need to go up but there is nothing there, or down but there is nothing there so it's a change of 0. mean_distance = (normalized_severity_eq1 + normalized_severity_eq2 + normalized_severity_eq3eq6 + normalized_severity_eq4 + normalized_severity_eq5) / n_existing_lower; } // 3. The score of the vector is the score of the MacroVector // (i.e. the score of the highest severity vector) minus the mean // distance so computed. This score is rounded to one decimal place. value -= mean_distance; if (value < 0) { value = 0.0; } if (value > 10) { value = 10.0; } return Math.round(value * 10) / 10; }; const getEQMaxes = (lookup, eq) => { // @ts-ignore return maxComposed[`eq${eq}`][lookup[eq - 1]]; }; const extractValueMetric = (metric, str) => { // indexOf gives first index of the metric, we then need to go over its size let extracted = str.slice(str.indexOf(metric) + metric.length + 1); // remove what follow let metric_val = ""; if (extracted.indexOf('/') > 0) { metric_val = extracted.substring(0, extracted.indexOf('/')); } else { // case where it is the last metric so no ending / metric_val = extracted; } return metric_val; }; const m = (cvssSelected, metric) => { let selected = cvssSelected[metric]; // If E=X it will default to the worst case i.e. E=A if (metric == "E" && selected == "X") { return "A"; } // 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 (metric == "CR" && selected == "X") { return "H"; } // IR:X is the same as IR:H if (metric == "IR" && selected == "X") { return "H"; } // AR:X is the same as AR:H if (metric == "AR" && selected == "X") { return "H"; } // All other environmental metrics just overwrite base score values, // so if they’re not defined just use the base score value. if (Object.keys(cvssSelected).includes("M" + metric)) { let modified_selected = cvssSelected["M" + metric]; if (modified_selected != "X") { return modified_selected; } } return selected; }; const macroVector = (cvssSelected) => { // EQ1: 0-AV:N and PR:N and UI:N // 1-(AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P // 2-AV:P or not(AV:N or PR:N or UI:N) let eq1 = 0; let eq2 = 0; let eq3 = 0; let eq4 = 0; let eq5 = 0; let eq