UNPKG

@neuralegion/cvss

Version:

The Common Vulnerability Scoring System ([CVSS](https://www.first.org/cvss/)) [score](https://www.first.org/cvss/specification-document#1-2-Scoring) calculator and validator library written in [TypeScript](https://www.typescriptlang.org/).

268 lines (267 loc) 13.3 kB
import { BaseMetric, EnvironmentalMetric, TemporalMetric, environmentalMetrics, temporalMetrics } from './models'; import { parseMetricsAsMap, parseVersion } from '../../parser'; // 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') { 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)) { 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-0/specification-document#8-1-Base // ModifiedImpact = // If ModifiedScope is Unchanged 6.42 × MISS // If ModifiedScope is Changed 7.52 × (MISS - 0.029) - 3.25 × (MISS - 0.02)^15 // ModifiedExploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction const calculateModifiedImpactV3 = (metricsMap, miss) => metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U' ? 6.42 * miss : 7.52 * (miss - 0.029) - 3.25 * Math.pow(miss * 1 - 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 const calculateModifiedImpactV31 = (metricsMap, miss) => metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U' ? 6.42 * miss : 7.52 * (miss - 0.029) - 3.25 * Math.pow(miss * 0.9731 - 0.02, 13); export const calculateModifiedImpact = (metricsMap, miss, versionStr) => versionStr === '3.0' ? calculateModifiedImpactV3(metricsMap, miss) : calculateModifiedImpactV31(metricsMap, miss); // 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 export const roundUp = (input) => { const intInput = Math.round(input * 100000); return intInput % 10000 === 0 ? intInput / 100000 : (Math.floor(intInput / 10000) + 1) / 10; }; const round = (input) => { const intInput = Math.round(input * 100000); return Math.round(intInput / 10000) / 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; }; export class CvssV3Calculator { calculate(cvssString) { const metricsMap = parseMetricsAsMap(cvssString); const versionStr = parseVersion(cvssString); const baseResult = this.calculateBaseScore(metricsMap, versionStr); const temporalResult = this.calculateTemporalScore(baseResult); const environmentalResult = this.calculateEnvironmentalScore(metricsMap, versionStr); return { ...baseResult, ...temporalResult, ...environmentalResult }; } /** * Calculate the base score for a CVSS v3.x string */ calculateBaseScore(metricsMap, versionStr) { const iss = calculateIss(metricsMap); const impact = calculateImpact(metricsMap, iss); const exploitability = calculateExploitability(metricsMap); const scopeUnchanged = metricsMap.get(BaseMetric.SCOPE) === 'U'; const baseScore = impact <= 0 ? 0 : scopeUnchanged ? roundUp(Math.min(impact + exploitability, 10)) : roundUp(Math.min(1.08 * (impact + exploitability), 10)); return { version: versionStr, baseScore, baseImpact: impact <= 0 ? 0 : round(impact), baseExploitability: exploitability <= 0 ? 0 : round(exploitability), metrics: metricsMap }; } /** * Calculate the temporal score for a CVSS v3.x string */ calculateTemporalScore(baseResult) { // populate temp metrics const metricsMap = populateTemporalMetricDefaults(baseResult.metrics); const temporalScore = roundUp(baseResult.baseScore * getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap) * getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap)); return { temporalScore }; } /** * Calculate the environmental score for a CVSS v3.x string */ calculateEnvironmentalScore(metricsMap, versionStr) { 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 environmentalScore = 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 { environmentalScore, modifiedImpact: impact <= 0 ? 0 : round(impact), modifiedExploitability: exploitability <= 0 ? 0 : round(exploitability) }; } }