-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
462 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,358 @@ | ||
import * as errors from './errors'; | ||
|
||
const cvss30header = 'CVSS:3.0/' | ||
|
||
/** | ||
* Implementation of the CVSS v3.0 specification (https://www.first.org/cvss/v3.0/specification-document). | ||
*/ | ||
export class CVSS30 { | ||
private _metrics = { | ||
// Set default values of non-mandatory metrics : Not Defined (X) | ||
// => Temporal | ||
'E': 'X', 'RL': 'X', 'RC': 'X', | ||
// => Environmental | ||
'CR': 'X', 'IR': 'X', 'AR': 'X', 'MAV': 'X', 'MAC': 'X', 'MPR': 'X', 'MUI': 'X', 'MS': 'X', 'MC': 'X', 'MI': 'X', 'MA': 'X' | ||
} | ||
|
||
/** | ||
* Construct a CVSS v3.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 = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/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) { | ||
// Check header | ||
if (!vector.startsWith(cvss30header)) { | ||
throw new Error('invalid vector') | ||
} | ||
vector = vector.substring(cvss30header.length) | ||
|
||
// Parse vector | ||
let kvm = {}; | ||
let metrics = vector.split('/'); | ||
for (let metric of metrics) { | ||
let pts = metric.split(':'); | ||
if (pts.length != 2) { | ||
throw new Error('invalid vector'); | ||
} | ||
if (kvm[pts[0]] != undefined) { | ||
throw new Error('metric ' + pts[0] + ' already defined'); | ||
} | ||
kvm[pts[0]] = pts[1]; | ||
this.Set(pts[0], pts[1]); | ||
} | ||
|
||
// Check all mandatory metrics are defined | ||
if (['AV', 'AC', 'AT', 'PR', 'UI', 'S', 'C', 'I', 'A'].some((metric) => { this._metrics[metric] == undefined })) { | ||
throw new Error('all mandatory metrics are not provided'); | ||
} | ||
} | ||
|
||
/** | ||
* Return the vector string representation of the CVSS v3.0 object. | ||
* | ||
* @return The vector string representation. | ||
*/ | ||
Vector() { | ||
let vector = 'CVSS:3.0'; | ||
for (let metric in Object.entries(this._metrics)) { | ||
let value = this.Get(metric); | ||
if (value == 'X') { | ||
continue | ||
} | ||
vector += '/' + metric + ':' + value; | ||
} | ||
return vector | ||
} | ||
|
||
/** | ||
* 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 { | ||
let value = this._metrics[metric]; | ||
if (value == undefined) { | ||
throw new errors.InvalidMetric('3.0', metric); | ||
} | ||
return value; | ||
} | ||
|
||
/** | ||
* 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) { | ||
if (metricValues[metric] == undefined) { | ||
throw new errors.InvalidMetric('3.0', metric); | ||
} | ||
if (!metricValues[metric].includes(value)) { | ||
throw new errors.InvalidMetricValue('3.0', metric, value); | ||
} | ||
this._metrics[metric] = value; | ||
} | ||
|
||
/** | ||
* Compute the CVSS v3.0 Impact 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 30. | ||
* | ||
* @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']]; | ||
const iss = 1 - ((1 - c) * (1 - i) * (1 - a)); | ||
if (this._metrics['S'] == 'U') { | ||
return 6.42 * iss; | ||
} | ||
return 7.52 * (iss - 0.029) - 3.25 * (iss - 0.02) ** 15; | ||
} | ||
|
||
/** | ||
* Compute the CVSS v3.0 Exploitability 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 30. | ||
* | ||
* @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 pr = scores['PR'][this._metrics['PR']][this._metrics['S']]; | ||
const ui = scores['UI'][this._metrics['UI']]; | ||
return 8.22 * av * ac * pr * ui | ||
} | ||
|
||
/** | ||
* Compute the CVSS v3.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 30. | ||
* | ||
* @return The score (between 0.0 and 10.0 both included). | ||
*/ | ||
BaseScore(): number { | ||
const impact = this.Impact(); | ||
const exploitability = this.Exploitability(); | ||
if (impact <= 0) { | ||
return 0 | ||
} | ||
if (this._metrics['S'] == 'U') { | ||
return CVSS30.roundup(Math.min(impact + exploitability, 10)); | ||
} | ||
return CVSS30.roundup(Math.min(1.08 * (impact + exploitability), 10)); | ||
} | ||
|
||
/** | ||
* Compute the CVSS v3.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 30. | ||
* | ||
* @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 CVSS30.roundup(this.BaseScore() * e * rl * rc); | ||
} | ||
|
||
/** | ||
* Compute the CVSS v3.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 30. | ||
* | ||
* @return The score (between 0.0 and 10.0 both included). | ||
*/ | ||
EnvironmentalScore(): number { | ||
const mav = scores['AV'][this.getReal('AV')]; | ||
const mac = scores['AC'][this.getReal('AC')]; | ||
const mpr = scores['PR'][this.getReal('PR')][this._metrics['S']]; | ||
const mui = scores['UI'][this.getReal('UI')]; | ||
const s = this.getReal('S'); | ||
const c = scores['CIA'][this.getReal('C')]; | ||
const i = scores['CIA'][this.getReal('I')]; | ||
const a = scores['CIA'][this.getReal('A')]; | ||
const cr = scores['CIAR'][this._metrics['CR']]; | ||
const ir = scores['CIAR'][this._metrics['IR']]; | ||
const ar = scores['CIAR'][this._metrics['AR']]; | ||
const e = scores['E'][this._metrics['E']]; | ||
const rl = scores['RL'][this._metrics['RL']]; | ||
const rc = scores['RC'][this._metrics['RC']]; | ||
|
||
const miss = Math.min(1 - (1 - cr * c) * (1 - ir * i) * (1 - ar * a), 0.915); | ||
let modifiedImpact: number; | ||
if (s == 'U') { | ||
modifiedImpact = 6.42 * miss; | ||
} else { | ||
modifiedImpact = 7.52 * (miss - 0.029) - 3.25 * (miss - 0.02) ** 15; | ||
} | ||
let modifiedExploitability = 8.22 * mav * mac * mpr * mui; | ||
if (modifiedImpact <= 0) { | ||
return 0; | ||
} | ||
if (s == 'U') { | ||
return CVSS30.roundup(CVSS30.roundup(Math.min(modifiedImpact + modifiedExploitability, 10)) * e * rl * rc); | ||
} | ||
return CVSS30.roundup(CVSS30.roundup(Math.min(1.08 * (modifiedImpact + modifiedExploitability), 10)) * e * rl * rc); | ||
} | ||
|
||
private getReal(metric: string): string { | ||
if (['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'].includes(metric)) { | ||
const v = this.Get('M' + metric); | ||
if (v != 'X') { | ||
return v | ||
} | ||
return this.Get(metric); | ||
} | ||
return this.Get(metric); | ||
} | ||
|
||
private static roundup(x: number): number { | ||
let bx = Math.round(x * 100_000); | ||
if (bx % 1000 == 0) { | ||
return bx / 100_000; | ||
} | ||
return (Math.floor(bx / 10_000) + 1) / 10 | ||
} | ||
|
||
/** | ||
* Give the corresponding rating of the provided score. | ||
* | ||
* @param score The score to rate. | ||
* @return The rating. | ||
* @throws When the score is out of bounds. | ||
*/ | ||
public static Rating(score: number): 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'NONE' { | ||
if (score < 0 || score > 10) { | ||
throw new Error('score out of bounds'); | ||
} | ||
if (score >= 9.0) { | ||
return 'CRITICAL'; | ||
} | ||
if (score >= 7.0) { | ||
return 'HIGH'; | ||
} | ||
if (score >= 4.0) { | ||
return 'MEDIUM'; | ||
} | ||
if (score >= 0.1) { | ||
return 'LOW'; | ||
} | ||
return 'NONE'; | ||
} | ||
} | ||
|
||
const metricValues = { | ||
'AV': ['N', 'A', 'L', 'P'], | ||
'AC': ['L', 'H'], | ||
'PR': ['N', 'L', 'H'], | ||
'UI': ['N', 'R'], | ||
'S': ['U', 'C'], | ||
'C': ['H', 'L', 'N'], | ||
'I': ['H', 'L', 'N'], | ||
'A': ['H', 'L', 'N'], | ||
'E': ['X', 'H', 'F', 'P', 'U'], | ||
'RL': ['X', 'U', 'W', 'T', 'O'], | ||
'RC': ['X', 'C', 'R', 'U'], | ||
'CR': ['X', 'H', 'M', 'L'], | ||
'IR': ['X', 'H', 'M', 'L'], | ||
'AR': ['X', 'H', 'M', 'L'], | ||
'MAV': ['X', 'N', 'A', 'L', 'P'], | ||
'MAC': ['X', 'L', 'H'], | ||
'MPR': ['X', 'N', 'L', 'H'], | ||
'MUI': ['X', 'N', 'R'], | ||
'MS': ['X', 'U', 'C'], | ||
'MC': ['X', 'H', 'L', 'N'], | ||
'MI': ['X', 'H', 'L', 'N'], | ||
'MA': ['X', 'H', 'L', 'N'], | ||
}; | ||
|
||
const scores = { | ||
'AV': { | ||
'N': 0.85, | ||
'A': 0.62, | ||
'L': 0.55, | ||
'P': 0.2, | ||
}, | ||
'AC': { | ||
'L': 0.77, | ||
'H': 0.44, | ||
}, | ||
'PR': { | ||
'N': { | ||
'U': 0.85, | ||
'C': 0.85, | ||
}, | ||
'L': { | ||
'U': 0.62, | ||
'C': 0.68, | ||
}, | ||
'H': { | ||
'U': 0.27, | ||
'C': 0.5, | ||
}, | ||
}, | ||
'UI': { | ||
'N': 0.85, | ||
'R': 0.62, | ||
}, | ||
'CIA': { | ||
'H': 0.56, | ||
'L': 0.22, | ||
'N': 0, | ||
}, | ||
'E': { | ||
'X': 1, | ||
'H': 1, | ||
'F': 0.97, | ||
'P': 0.94, | ||
'U': 0.91, | ||
}, | ||
'RL': { | ||
'X': 1, | ||
'U': 1, | ||
'W': 0.97, | ||
'T': 0.96, | ||
'O': 0.95, | ||
}, | ||
'RC': { | ||
'X': 1, | ||
'C': 1, | ||
'R': 0.96, | ||
'U': 0.92, | ||
}, | ||
'CIAR': { | ||
'X': 1, | ||
'M': 1, | ||
'H': 1.5, | ||
'L': 0.5, | ||
}, | ||
}; |
Oops, something went wrong.