cvss4
Version:
The **Common Vulnerability Scoring System (CVSS)** is a [scoring framework](https://www.first.org/cvss/) that provides numerical scores to assess the severity of software vulnerabilities. This TypeScript-based library offers support for CVSS versions **3.
730 lines (729 loc) • 33.9 kB
JavaScript
import { BaseMetric, EnvironmentalMetric, TemporalMetric, cvssLookup_globalV4, environmentalMetrics, maxComposed, maxSeverityV4, temporalMetrics } from './models';
import { validate, validateVectorV4 } from './validator';
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) ]
export 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)
export 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
export 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
export 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
export 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
export 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;
};
export 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.
export const populateTemporalMetricDefaults = (metricsMap) => {
[...temporalMetrics].forEach((metric) => {
if (!metricsMap.has(metric)) {
metricsMap.set(metric, 'X');
}
});
return metricsMap;
};
export 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])
export 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)
};
};
export 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)
export 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)
};
};
export 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)
export 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
};
};
export 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.
*/
export 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".
*/
export 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.
*/
export 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;
};
export 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;
};
export const getEQMaxes = (lookup, eq) => {
// @ts-ignore
return maxComposed[`eq${eq}`][lookup[eq - 1]];
};
export 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;
};
export 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;
};
export 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 eq6 = 0;
if (m(cvssSelected, "AV") == "N" && m(cvssSelected, "PR") == "N" && m(cvssSelected, "UI") == "N") {
eq1 = 0;
}
else if ((m(cvssSelected, "AV") == "N" || m(cvssSelected, "PR") == "N" || m(cvssSelected, "UI") == "N")
&& !(m(cvssSelected, "AV") == "N" && m(cvssSelected, "PR") == "N" && m(cvssSelected, "UI") == "N")
&& !(m(cvssSelected, "AV") == "P")) {
eq1 = 1;
}
else if (m(cvssSelected, "AV") == "P"
|| !(m(cvssSelected, "AV") == "N" || m(cvssSelected, "PR") == "N" || m(cvssSelected, "UI") == "N")) {
eq1 = 2;
}
// EQ2: 0-(AC:L and AT:N)
// 1-(not(AC:L and AT:N))
if (m(cvssSelected, "AC") == "L" && m(cvssSelected, "AT") == "N") {
eq2 = 0;
}
else if (!(m(cvssSelected, "AC") == "L" && m(cvssSelected, "AT") == "N")) {
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 (m(cvssSelected, "VC") == "H" && m(cvssSelected, "VI") == "H") {
eq3 = 0;
}
else if (!(m(cvssSelected, "VC") == "H" && m(cvssSelected, "VI") == "H")
&& (m(cvssSelected, "VC") == "H" || m(cvssSelected, "VI") == "H" || m(cvssSelected, "VA") == "H")) {
eq3 = 1;
}
else if (!(m(cvssSelected, "VC") == "H" || m(cvssSelected, "VI") == "H" || m(cvssSelected, "VA") == "H")) {
eq3 = 2;
}
// EQ4: 0-(MSI:S or MSA:S)
// 1-not (MSI:S or MSA:S) and (SC:H or SI:H or SA:H)
// 2-not (MSI:S or MSA:S) and not (SC:H or SI:H or SA:H)
if (m(cvssSelected, "MSI") == "S" || m(cvssSelected, "MSA") == "S") {
eq4 = 0;
}
else if (!(m(cvssSelected, "MSI") == "S" || m(cvssSelected, "MSA") == "S") &&
(m(cvssSelected, "SC") == "H" || m(cvssSelected, "SI") == "H" || m(cvssSelected, "SA") == "H")) {
eq4 = 1;
}
else if (!(m(cvssSelected, "MSI") == "S" || m(cvssSelected, "MSA") == "S") &&
!((m(cvssSelected, "SC") == "H" || m(cvssSelected, "SI") == "H" || m(cvssSelected, "SA") == "H"))) {
eq4 = 2;
}
// EQ5: 0-E:A
// 1-E:P
// 2-E:U
if (m(cvssSelected, "E") == "A") {
eq5 = 0;
}
else if (m(cvssSelected, "E") == "P") {
eq5 = 1;
}
else if (m(cvssSelected, "E") == "U") {
eq5 = 2;
}
// EQ6: 0-(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)
// 1-not[(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)]
if ((m(cvssSelected, "CR") == "H" && m(cvssSelected, "VC") == "H")
|| (m(cvssSelected, "IR") == "H" && m(cvssSelected, "VI") == "H")
|| (m(cvssSelected, "AR") == "H" && m(cvssSelected, "VA") == "H")) {
eq6 = 0;
}
else if (!((m(cvssSelected, "CR") == "H" && m(cvssSelected, "VC") == "H")
|| (m(cvssSelected, "IR") == "H" && m(cvssSelected, "VI") == "H")
|| (m(cvssSelected, "AR") == "H" && m(cvssSelected, "VA") == "H"))) {
eq6 = 1;
}
return `${eq1}${eq2}${eq3}${eq4}${eq5}${eq6}`;
};