Skip to content

Commit

Permalink
fix(metrics): minor improvements (#11)
Browse files Browse the repository at this point in the history
relates to #7
  • Loading branch information
pmstss authored Mar 29, 2021
1 parent 86c66ad commit 0d5d5eb
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 52 deletions.
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The Common Vulnerability Scoring System ([CVSS](https://www.first.org/cvss/)) [b

## Basics 🧾

CVSS outputs numerical scores, indicating severity of vulnerability, based on some principal technical vulnerability characteristics.
CVSS outputs numerical scores, indicating severity of vulnerability, based on some principal technical vulnerability characteristics.
Its outputs include numerical scores indicating the severity of a vulnerability relative to other vulnerabilities. [Link](https://www.first.org/cvss/v3.1/specification-document#Introduction)

The CVSS v3 vector string begins with the label `CVSS:` and numeric representation of the version.
Expand All @@ -17,24 +17,24 @@ Sample CVSS v3.1 vector string: `CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N`

Score is: [3.8](https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N), severity: [Low](https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N)

### Current library limitations 🚧
### Current library limitations 🚧

CVSS specification defines three metric groups: `Base`, `Temporal`, and `Environmental`, but only `Base` metrics are supported by given library for now.

Supported CVSS versions: [3.0](https://www.first.org/cvss/v3-0/) and [3.1](https://www.first.org/cvss/v3-1/)

## Install 🚀
## Install 🚀

`npm i --save @neuralegion/cvss`

## API
## API

<details>
<summary>Score Calculator</summary>

`calculateBaseScore(cvssString): number`

Calculates [Base Score](https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations),
Calculates [Base Score](https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations),
which depends on sub-formulas for Impact Sub-Score (ISS), Impact, and Exploitability,

`calculateIss(metricsMap): number`
Expand All @@ -59,6 +59,7 @@ Calculates [Exploitability](https://www.first.org/cvss/v3.1/specification-docume
Throws an Error if given CVSS string is either invalid or unsupported.

Error contains verbose message with error details. Sample error messages:

- CVSS vector must start with "CVSS:"
- Invalid CVSS string. Example: CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L
- Unsupported CVSS version: 2.0. Only 3.0 and 3.1 are supported
Expand All @@ -77,7 +78,8 @@ Return un-abbreviated metric name: e.g. 'Confidentiality' for input 'C'

`humanizeBaseMetricValue(value, metric)`

Return un-abbreviated metric value: e.g. 'Network' for input ('AV', 'N')
Return un-abbreviated metric value: e.g. 'Network' for input ('AV', 'N')

</details>

## Usage
Expand All @@ -90,6 +92,7 @@ import { calculateBaseScore } from '@neuralegion/cvss';
console.log('score: ', calculateBaseScore('CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N'));
```

</details>

<details>
Expand All @@ -100,6 +103,7 @@ const cvss = require('@neuralegion/cvss');
console.log(cvss.calculateBaseScore('CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N'));
```

</details>

<details>
Expand Down Expand Up @@ -127,6 +131,7 @@ Running: `node --experimental-modules ./usage.mjs`
alert(`Score: ${cvss.calculateBaseScore('CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N')}`);
</script>
```

</details>

<details>
Expand All @@ -138,16 +143,16 @@ Running: `node --experimental-modules ./usage.mjs`
alert(`Score: ${calculateBaseScore('CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N')}`);
</script>
```
</details>

</details>

## Development 🛠
## Development 🛠

Issues and pull requests are highly welcome. 👍

Please, don't forget to lint (`npm run lint`) and test (`npm t`) the code.

## License
## License

Copyright © 2020 [NeuraLegion](https://github.com/NeuraLegion).

Expand Down
30 changes: 12 additions & 18 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum EnvironmentalMetric {
}

export type Metric = BaseMetric | TemporalMetric | EnvironmentalMetric;
export type Metrics = ReadonlyArray<Metric>;
export type BaseMetricValue = 'A' | 'C' | 'H' | 'L' | 'N' | 'P' | 'R' | 'U';
export type TemporalMetricValue =
| 'X'
Expand All @@ -42,6 +43,15 @@ export type TemporalMetricValue =
| 'P'
| 'C'
| 'R';
export type EnvironmentalMetricValue = BaseMetricValue | 'M' | 'X';
export type MetricValue =
| BaseMetricValue
| TemporalMetricValue
| EnvironmentalMetricValue;
export type MetricValues<
M extends Metric = Metric,
V extends MetricValue = MetricValue
> = Record<M, V[]>;

export const baseMetrics: ReadonlyArray<BaseMetric> = [
BaseMetric.ATTACK_VECTOR,
Expand All @@ -54,24 +64,13 @@ export const baseMetrics: ReadonlyArray<BaseMetric> = [
BaseMetric.AVAILABILITY
];

export type EnvironmentalMetricValue = BaseMetricValue | 'M' | 'X';
export type MetricValue =
| BaseMetricValue
| TemporalMetricValue
| EnvironmentalMetricValue;
export type MetricValues<
M extends Metric = Metric,
V extends MetricValue = MetricValue
> = Record<M, V[]>;
export type Metrics<M = Metric> = ReadonlyArray<M>;

export const temporalMetrics: Metrics<TemporalMetric> = [
export const temporalMetrics: ReadonlyArray<TemporalMetric> = [
TemporalMetric.EXPLOIT_CODE_MATURITY,
TemporalMetric.REMEDIATION_LEVEL,
TemporalMetric.REPORT_CONFIDENCE
];

export const environmentalMetrics: Metrics<EnvironmentalMetric> = [
export const environmentalMetrics: ReadonlyArray<EnvironmentalMetric> = [
EnvironmentalMetric.AVAILABILITY_REQUIREMENT,
EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT,
EnvironmentalMetric.INTEGRITY_REQUIREMENT,
Expand Down Expand Up @@ -121,8 +120,3 @@ export const environmentalMetricValues: MetricValues<
[EnvironmentalMetric.MODIFIED_INTEGRITY]: ['X', 'N', 'L', 'H'],
[EnvironmentalMetric.MODIFIED_AVAILABILITY]: ['X', 'N', 'L', 'H']
};

export type AllMetricValues =
| typeof baseMetricValues
| typeof temporalMetricValues
| typeof environmentalMetricValues;
28 changes: 13 additions & 15 deletions src/score-calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import {
BaseMetric,
BaseMetricValue,
EnvironmentalMetric,
environmentalMetrics,
EnvironmentalMetricValue,
Metric,
MetricValue,
TemporalMetric,
temporalMetrics,
TemporalMetricValue
TemporalMetricValue,
environmentalMetrics,
temporalMetrics
} from './models';
import { validate } from './validator';

Expand Down Expand Up @@ -216,7 +216,7 @@ export const calculateImpact = (
// 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 calculateMImpact = (
export const calculateModifiedImpact = (
metricsMap: Map<Metric, MetricValue>,
miss: number,
versionStr: string | null
Expand All @@ -239,7 +239,7 @@ export const calculateExploitability = (

// https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations
// Exploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction
export const calculateMExploitability = (
export const calculateModifiedExploitability = (
metricsMap: Map<Metric, MetricValue>
): number =>
8.22 *
Expand Down Expand Up @@ -362,14 +362,15 @@ export const calculateBaseScore = (cvssString: string): number => {
export const calculateEnvironmentalResult = (
cvssString: string
): ScoreResult => {
const result = validate(cvssString);
let { metricsMap } = result;
const validationResult = validate(cvssString);
const { versionStr } = validationResult;
let { metricsMap } = validationResult;

metricsMap = populateTemporalMetricDefaults(metricsMap);
metricsMap = populateEnvironmentalMetricDefaults(metricsMap);
const miss = calculateMiss(metricsMap);
const impact = calculateMImpact(metricsMap, miss, result.versionStr);
const exploitability = calculateMExploitability(metricsMap);
const impact = calculateModifiedImpact(metricsMap, miss, versionStr);
const exploitability = calculateModifiedExploitability(metricsMap);
const scopeUnchanged =
metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U';

Expand Down Expand Up @@ -419,13 +420,10 @@ export const calculateEnvironmentalScore = (cvssString: string): number => {
// https://www.first.org/cvss/v3.1/specification-document#7-2-Temporal-Metrics-Equations
// Roundup (BaseScore × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
export const calculateTemporalResult = (cvssString: string): ScoreResult => {
const { metricsMap } = validate(cvssString);
let { metricsMap } = validate(cvssString);
// populate temp metrics if not provided
[...temporalMetrics].forEach((metric) => {
if (![...metricsMap.keys()].includes(metric)) {
metricsMap.set(metric, 'X');
}
});
metricsMap = populateTemporalMetricDefaults(metricsMap);

const { score, impact, exploitability } = calculateBaseResult(cvssString);

const tempScore = roundUp(
Expand Down
16 changes: 8 additions & 8 deletions src/validator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {
BaseMetric,
EnvironmentalMetric,
Metric,
MetricValue,
Metrics,
TemporalMetric,
baseMetricValues,
baseMetrics,
temporalMetrics,
environmentalMetricValues,
environmentalMetrics,
AllMetricValues,
baseMetricValues,
temporalMetricValues,
environmentalMetricValues,
TemporalMetric,
EnvironmentalMetric
temporalMetrics
} from './models';
import { humanizeBaseMetric, humanizeBaseMetricValue } from './humanizer';
import { parseMetricsAsMap, parseVector, parseVersion } from './parser';
Expand Down Expand Up @@ -60,7 +60,7 @@ const checkUnknownMetrics = (

const checkMandatoryMetrics = (
metricsMap: Map<string, string>,
metrics: Metrics = baseMetrics
metrics: ReadonlyArray<BaseMetric> = baseMetrics
): void => {
metrics.forEach((metric: Metric) => {
if (!metricsMap.has(metric)) {
Expand All @@ -77,7 +77,7 @@ const checkMandatoryMetrics = (
const checkMetricsValues = (
metricsMap: Map<string, string>,
metrics: Metrics,
metricsValues: AllMetricValues
metricsValues: Record<Metric, MetricValue[]>
): void => {
metrics.forEach((metric: Metric) => {
const userValue = metricsMap.get(metric);
Expand Down
17 changes: 15 additions & 2 deletions tests/score-calculator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ const cvssTests = {
'CVSS:3.0/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:N': [0.0, 0.0, 0.0],
'CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H': [7.8, 7.8, 7.8], // https://www.first.org/cvss/user-guide#3-1-CVSS-Scoring-in-the-Exploit-Life-Cycle
'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N': [7.5, 7.5, 7.5], // https://www.first.org/cvss/user-guide#3-1-CVSS-Scoring-in-the-Exploit-Life-Cycle
'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N': [6.4, 6.4, 6.4], // https://www.first.org/cvss/user-guide#3-6-Vulnerable-Components-Protected-by-a-Firewall
'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N': [6.4, 6.4, 6.4], // https://www.first.org/cvss/v3.0/examples#2-4-CVSS-v3-0-Base-Score-6-4
'CVSS:3.1/S:C/C:L/I:L/A:N/AV:N/AC:L/PR:L/UI:N': [6.4, 6.4, 6.4], // non-normalized order
'CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N': [6.4, 6.4, 6.4],
'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N': [6.1, 6.1, 6.1], // https://www.first.org/cvss/v3.0/examples#1-4-CVSS-v3-0-Base-Score-6-1
'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N': [6.1, 6.1, 6.1],
'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N': [8.6, 8.6, 8.6],
'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H': [10.0, 10.0, 10.0],
'CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:N': [0.0, 0.0, 0.0],
Expand Down Expand Up @@ -86,7 +89,17 @@ const cvssTests = {
3.5,
6.1
],
'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N/RL:O/CR:L': [8.6, 8.2, 6.0]
'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N/RL:O/CR:L': [8.6, 8.2, 6.0],
'CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N/E:P/RL:W/RC:R/CR:H/IR:M/AR:M/MAV:N/MAC:H/MPR:L/MUI:R/MS:C/MC:L/MI:H/MA:L': [
7.7,
6.8,
6.5
],
'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N/E:P/RL:W/RC:R/CR:H/IR:M/AR:M/MAV:N/MAC:H/MPR:L/MUI:R/MS:C/MC:L/MI:H/MA:L': [
7.7,
6.8,
6.5
]
};

describe('Calculator', () => {
Expand Down

0 comments on commit 0d5d5eb

Please sign in to comment.