@pandatix/js-cvss
Version:
JavaScript implementation of the CVSS (Common Vulnerability Scoring System) standard from FIRST.ORG.
292 lines (276 loc) • 9.05 kB
text/typescript
import * as errors from './errors';
// https://go.dev/play/p/FpbKl0gOmWy
const re = /^(AV:[LAN])\/(AC:[LMH])\/(Au:[MSN])\/(C:[NPC])\/(I:[NPC])\/(A:[NPC])(?:\/(E:(?:ND|U|POC|F|H))\/(RL:(?:ND|OF|TF|W|U))\/(RC:(?:ND|UC|UR|C)))?(?:\/(CDP:(?:ND|N|L|LM|MH|H))\/(TD:(?:ND|N|L|M|H))\/(CR:(?:ND|L|M|H))\/(IR:(?:ND|L|M|H))\/(AR:(?:ND|L|M|H)))?$/g;
/**
* Implementation of the CVSS v2.0 specification (https://www.first.org/cvss/v2/guide).
*/
export class CVSS20 {
private _metrics = {
// Set default values of non-mandatory metrics : Not Defined (ND)
// => Temporal
'E': 'ND', 'RL': 'ND', 'RC': 'ND',
// => Environmental
'CDP': 'ND', 'TD': 'ND', 'CR': 'ND', 'IR': 'ND', 'AR': 'ND',
};
/**
* Construct a CVSS v2.0 object, and parse the vector if provided.
* If not, the Base metrics is set to the default values (score = 0).
*
* @param vector The vector to parse.
* @throws When the vector is invalid.
*/
constructor(vector = 'AV:L/AC:L/Au:M/C:N/I:N/A:N') {
this.parse(vector);
}
/**
* Parse the provided vector.
*
* @param vector The vector to parse.
* @throws When the vector is invalid.
*/
private parse(vector: string) {
// Ensure input is valid according to the regular expression
let matches = vector.matchAll(re).next().value;
if (matches == undefined) {
throw new Error('invalid CVSS v2.0 vector');
}
// Skip complete match
matches.shift();
// Parse each metric group
for (let match of matches) {
if (match == undefined) {
continue;
}
const pts = match.split(':');
this._metrics[pts[0]] = pts[1];
}
}
/**
* Return the vector string representation of the CVSS v2.0 object.
*
* @return The vector string representation.
*/
Vector() {
let vector = '';
const app = (metric: string) => { vector += '\/' + metric + ':' + this._metrics[metric] };
const def = (metric: string) => { this._metrics[metric] != 'ND' };
['AV', 'AC', 'Au', 'C', 'I', 'A'].forEach(app);
if (['E', 'RL', 'RC'].some(def)) {
['E', 'RL', 'RC'].forEach(app);
}
if (['CDP', 'TD', 'CR', 'IR', 'AR'].some(def)) {
['CDP', 'TD', 'CR', 'IR', 'AR'].forEach(app);
}
return vector.slice(1);
}
/**
* Get the metric value given its value (e.g. 'AV').
*
* @param metric The metric to get the value of.
* @return The corresponding metric value.
* @throws Metric does not exist.
*/
Get(metric: string): string {
const v = this._metrics[metric];
if (v == undefined) {
throw new errors.InvalidMetric('2.0', metric)
}
return v;
}
/**
* Set the metric value given its key and value (e.g. 'AV' and 'L').
*
* @param metric The metric to set the value of.
* @param value The corresponding metric value.
* @throws Metric does not exist or has an invalid value.
*/
Set(metric: string, value: string) {
const values: [string] = metrics[metric];
if (values == undefined) {
throw new errors.InvalidMetric('2.0', metric);
}
if (!values.includes(value)) {
throw new errors.InvalidMetricValue('2.0', metric, value);
}
this._metrics[metric] = value;
}
/**
* Compute the CVSS v2.0 Impact score of the current object, given its metrics and their
* corresponding values.
*
* The implementation internals are largely based upon https://github.com/pandatix/go-cvss
* submodule 20.
*
* @return The score (between 0.0 and 10.0 both included).
*/
Impact(): number {
const c = scores['CIA'][this._metrics['C']];
const i = scores['CIA'][this._metrics['I']];
const a = scores['CIA'][this._metrics['A']];
return 10.41 * (1 - (1 - c) * (1 - i) * (1 - a));
}
/**
* Compute the CVSS v2.0 Exploitability score of the current object, given its metrics and their
* corresponding values.
*
* The implementation internals are largely based upon https://github.com/pandatix/go-cvss
* submodule 20.
*
* @return The score (between 0.0 and 10.0 both included).
*/
Exploitability(): number {
const av = scores['AV'][this._metrics['AV']];
const ac = scores['AC'][this._metrics['AC']];
const au = scores['Au'][this._metrics['Au']];
return 20 * av * ac * au;
}
/**
* Compute the CVSS v2.0 Base score of the current object, given its metrics and their
* corresponding values.
*
* The implementation internals are largely based upon https://github.com/pandatix/go-cvss
* submodule 20.
*
* @return The score (between 0.0 and 10.0 both included).
*/
BaseScore(): number {
const impact = this.Impact();
let fimpact = 0;
if (impact != 0) {
fimpact = 1.176;
}
const exploitability = this.Exploitability();
return CVSS20.roundTo1Decimal(((0.6 * impact) + (0.4 * exploitability) - 1.5) * fimpact)
}
/**
* Compute the CVSS v2.0 Temporal score of the current object, given its metrics and their
* corresponding values.
*
* The implementation internals are largely based upon https://github.com/pandatix/go-cvss
* submodule 20.
*
* @return The score (between 0.0 and 10.0 both included).
*/
TemporalScore(): number {
const e = scores['E'][this._metrics['E']];
const rl = scores['RL'][this._metrics['RL']];
const rc = scores['RC'][this._metrics['RC']];
return CVSS20.roundTo1Decimal(this.BaseScore() * e * rl * rc);
}
/**
* Compute the CVSS v2.0 Environmental score of the current object, given its metrics and their
* corresponding values.
*
* The implementation internals are largely based upon https://github.com/pandatix/go-cvss
* submodule 20.
*
* @return The score (between 0.0 and 10.0 both included).
*/
EnvironmentalScore(): number {
const c = scores['CIA'][this._metrics['C']];
const i = scores['CIA'][this._metrics['I']];
const a = scores['CIA'][this._metrics['A']];
const cr = scores['CIAR'][this._metrics['CR']];
const ir = scores['CIAR'][this._metrics['IR']];
const ar = scores['CIAR'][this._metrics['AR']];
const adujstedImpact = Math.min(10, 10.41 * (1 - (1 - c * cr) * (1 - i * ir) * (1 - a * ar)));
let fimpacBase = 0;
if (adujstedImpact != 0) {
fimpacBase = 1.176;
}
const expltBase = this.Exploitability();
const e = scores['E'][this._metrics['E']];
const rl = scores['RL'][this._metrics['RL']];
const rc = scores['RC'][this._metrics['RC']];
const recBase = CVSS20.roundTo1Decimal(((0.6 * adujstedImpact) + (0.4 * expltBase) - 1.5) * fimpacBase);
const adjustedTemporal = CVSS20.roundTo1Decimal(recBase * e * rl * rc);
const cdp = scores['CDP'][this._metrics['CDP']];
const td = scores['TD'][this._metrics['TD']];
return CVSS20.roundTo1Decimal((adjustedTemporal + (10 - adjustedTemporal) * cdp) * td);
}
private static roundTo1Decimal(x: number): number {
return Math.round(x * 10) / 10
}
}
const metrics = {
// Base
'AV': ['L', 'A', 'N'],
'AC': ['L', 'M', 'H'],
'Au': ['M', 'S', 'N'],
'C': ['N', 'P', 'C'],
'I': ['N', 'P', 'C'],
'A': ['N', 'P', 'C'],
// Temporal
'E': ['ND', 'U', 'POC', 'F', 'H'],
'RL': ['ND', 'OF', 'TF', 'W', 'U'],
'RC': ['ND', 'UC', 'UR', 'C'],
// Environmental
'CDP': ['ND', 'N', 'L', 'LM', 'MH', 'H'],
'TD': ['ND', 'N', 'L', 'M', 'H'],
'CR': ['ND', 'L', 'M', 'H'],
'IR': ['ND', 'L', 'M', 'H'],
'AR': ['ND', 'L', 'M', 'H'],
};
const scores = {
'AV': {
'L': 0.395,
'A': 0.646,
'N': 1.0,
},
'AC': {
'H': 0.35,
'M': 0.61,
'L': 0.71,
},
'Au': {
'M': 0.45,
'S': 0.56,
'N': 0.704,
},
'CIA': {
'N': 0.0,
'P': 0.275,
'C': 0.660,
},
'E': {
'U': 0.85,
'POC': 0.9,
'F': 0.95,
'H': 1.0,
'ND': 1.0,
},
'RL': {
'OF': 0.87,
'TF': 0.90,
'W': 0.95,
'U': 1.0,
'ND': 1.0,
},
'RC': {
'UC': 0.90,
'UR': 0.95,
'C': 1.0,
'ND': 1.0,
},
'CDP': {
'N': 0,
'ND': 0,
'L': 0.1,
'LM': 0.3,
'MH': 0.4,
'H': 0.5,
},
'TD': {
'N': 0,
'L': 0.25,
'M': 0.75,
'H': 1.0,
'ND': 1.0,
},
'CIAR': {
'L': 0.5,
'M': 1.0,
'ND': 1.0,
'H': 0.51,
},
};