Skip to content

Commit

Permalink
Merge pull request #32 from turingpointde/feat/#10-environmental-score
Browse files Browse the repository at this point in the history
fix #10: environmental score calculation
  • Loading branch information
Fubinator authored Oct 6, 2020
2 parents a527409 + d54817e commit bf18cf3
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 61 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<h1 align="center">cvss.js by <a href="https://turingpoint.eu" target="_blank">turingpoint.</a></h1>
<p>
<img alt="Version" src="https://img.shields.io/badge/version-1.2.0-blue.svg?cacheSeconds=2592000" />
<img alt="Version" src="https://img.shields.io/badge/version-1.3.0-blue.svg?cacheSeconds=2592000" />
<a href="#" target="_blank">
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" />
</a>
Expand Down Expand Up @@ -36,6 +36,7 @@ const vector = CVSS("CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:C/C:L/I:L/A:L/E:U/RL:T/RC:R"

console.log(vector.getScore()); // 5.5
console.log(vector.getTemporalScore()); // 4.7
console.log(vector.getEnvironmentalScore()); // 9.3 - When specified
console.log(vector.getRating()); // Medium - Based on Qualitative Severity Rating Scale
console.log(vector.isValid); // true
console.log(vector.vector); // CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:C/C:L/I:L/A:L/E:U/RL:T/RC:R
Expand Down
196 changes: 151 additions & 45 deletions lib/cvss.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,36 @@ function CVSS(vector) {
}
return vectorObject;
}

/**
* Retrieves an object of vector's metrics
*
* @returns {Object} Abbreviations & Vectors Detailed Values
*/
function getDetailedVectorObject() {
const vectorArray = vector.split("/");
const vectorObject = vectorArray.reduce((vectorObjectAccumulator , vectorItem, index) => {
const values = vectorItem.split(":");
const metrics = {...vectorObjectAccumulator.metrics};
if(index){
const vectorDef = findMetric(values[0]);
const detailedVectorObject = {
name: vectorDef.name,
abbr: vectorDef.abbr,
fullName: `${vectorDef.name} (${vectorDef.abbr})`,
value: vectorDef.metrics.find(def => def.abbr === values[1]).name,
valueAbbr: values[1]
};
return Object.assign(vectorObjectAccumulator , { metrics: Object.assign(metrics, { [values[0].trim()]: detailedVectorObject })});
} else {
return Object.assign(vectorObjectAccumulator , { [values[0].trim()]: values[1] });
}
}, { metrics: {} });
const vectorObject = vectorArray.reduce(
(vectorObjectAccumulator, vectorItem, index) => {
const values = vectorItem.split(":");
const metrics = { ...vectorObjectAccumulator.metrics };
if (index) {
const vectorDef = findMetric(values[0]);
const detailedVectorObject = {
name: vectorDef.name,
abbr: vectorDef.abbr,
fullName: `${vectorDef.name} (${vectorDef.abbr})`,
value: vectorDef.metrics.find((def) => def.abbr === values[1]).name,
valueAbbr: values[1]
};
return Object.assign(vectorObjectAccumulator, {
metrics: Object.assign(metrics, { [values[0].trim()]: detailedVectorObject })
});
} else {
return Object.assign(vectorObjectAccumulator, { [values[0].trim()]: values[1] });
}
},
{ metrics: {} }
);
return vectorObject;
}

Expand Down Expand Up @@ -97,14 +102,14 @@ function CVSS(vector) {
}

/**
* Parses the vector to a number score
* Parses the vector to the temporal score
*
* @returns {Number} Temporal Score
*/
function getTemporalScore() {
const vectorObject = getVectorObject();

const score = getScore();
const baseScore = getScore();

const eDef = findMetric("E");
const rlDef = findMetric("RL");
Expand All @@ -120,7 +125,47 @@ function CVSS(vector) {
(metric) => metric.abbr === (vectorObject.RC ? vectorObject.RC : "X")
).numerical;

return roundUp(score * exploitCodeMaturity * remediationLevel * reportConfidence, 1);
return roundUp(baseScore * exploitCodeMaturity * remediationLevel * reportConfidence, 1);
}

/**
* Parses the vector to the environmental score
*
* @returns {Number} Environmental Score
*/
function getEnvironmentalScore() {
const vectorObject = getVectorObject();
const scopeChanged = vectorObject.MS === "C";
const modifiedISCBase = calculateISCModifiedBase(vectorObject);
const modifiedExploitability = calculateModifiedExploitability(vectorObject, scopeChanged);
const modifiedISC = calculateISC(modifiedISCBase, scopeChanged);

if (modifiedISC <= 0) return 0;

const eDef = findMetric("E");
const rlDef = findMetric("RL");
const rcDef = findMetric("RC");

const e = eDef.metrics.find((metric) => metric.abbr === vectorObject.E);
const eValue = e ? e.numerical : 1;
const rl = rlDef.metrics.find((metric) => metric.abbr === vectorObject.RL);
const rlValue = rl ? rl.numerical : 1;
const rc = rcDef.metrics.find((metric) => metric.abbr === vectorObject.RC);
const rcValue = rc ? rc.numerical : 1;

if (!scopeChanged) {
return roundUp(
roundUp(Math.min(modifiedISC + modifiedExploitability, 10), 1) * eValue * rlValue * rcValue,
1
);
}
return roundUp(
roundUp(Math.min(1.08 * (modifiedISC + modifiedExploitability), 10), 1) *
eValue *
rlValue *
rcValue,
1
);
}

const calculateISCBase = function (vectorObject) {
Expand All @@ -141,6 +186,27 @@ function CVSS(vector) {
return 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
};

function calculateISCModifiedBase(vectorObject) {
const mcDef = findMetric("MC");
const miDef = findMetric("MI");
const maDef = findMetric("MA");
const crDef = findMetric("CR");
const irDef = findMetric("IR");
const arDef = findMetric("AR");

const mcValue = mcDef.metrics.find((metric) => metric.abbr === vectorObject.MC).numerical;
const miValue = miDef.metrics.find((metric) => metric.abbr === vectorObject.MI).numerical;
const maValue = maDef.metrics.find((metric) => metric.abbr === vectorObject.MA).numerical;
const crValue = crDef.metrics.find((metric) => metric.abbr === vectorObject.CR).numerical;
const irValue = irDef.metrics.find((metric) => metric.abbr === vectorObject.IR).numerical;
const arValue = arDef.metrics.find((metric) => metric.abbr === vectorObject.AR).numerical;

return Math.min(
1 - (1 - mcValue * crValue) * (1 - miValue * irValue) * (1 - maValue * arValue),
0.915
);
}

const calculateExploitability = function (vectorObject, scopeChanged) {
const avDef = findMetric("AV");
const acDef = findMetric("AC");
Expand All @@ -157,6 +223,22 @@ function CVSS(vector) {
return 8.22 * avValue * acValue * prValue * uiValue;
};

const calculateModifiedExploitability = function (vectorObject, scopeChanged) {
const mavDef = findMetric("MAV");
const macDef = findMetric("MAC");
const mprDef = findMetric("MPR");
const muiDef = findMetric("MUI");

const mavValue = mavDef.metrics.find((metric) => metric.abbr === vectorObject.MAV).numerical;
const macValue = macDef.metrics.find((metric) => metric.abbr === vectorObject.MAC).numerical;
const muiValue = muiDef.metrics.find((metric) => metric.abbr === vectorObject.MUI).numerical;

const mprMetrics = mprDef.metrics.find((metric) => metric.abbr === vectorObject.MPR).numerical;
const mprValue = scopeChanged ? mprMetrics.changed : mprMetrics.unchanged;

return 8.22 * mavValue * macValue * mprValue * muiValue;
};

const findMetric = function (abbr) {
return definitions.definitions.find((def) => def.abbr === abbr);
};
Expand All @@ -170,16 +252,19 @@ function CVSS(vector) {
/**
* This function is used to scan the definitions file and join all
* abbreviations in a format that RegExp understands.
*
*
* Exit example:
* ((((((((((AV:[NALP]|AC:[LH])|PR:[NLH])|UI:[NR])|S:[UC])|C:[NLW])|I:[NLW])|A:[NLW])|E:[XUPFH])|RL:[XOTWU])|RC:[XURC])
*/
const expression = definitions.definitions.reduce((accumulator, currentValue, index) => {
const serializedAbbr = `${currentValue.abbr}:[${currentValue.metrics.reduce((accumulator2, currentValue2) => {
return accumulator2 + currentValue2.abbr;
}, "")}]`;
const serializedAbbr = `${currentValue.abbr}:[${currentValue.metrics.reduce(
(accumulator2, currentValue2) => {
return accumulator2 + currentValue2.abbr;
},
""
)}]`;
if (index !== 0) {
return `(${accumulator }|${serializedAbbr})`;
return `(${accumulator}|${serializedAbbr})`;
} else {
return serializedAbbr;
}
Expand All @@ -188,36 +273,48 @@ function CVSS(vector) {
const totalExpressionVector = new RegExp("^CVSS:3.0(/" + expression + ")+$");

//Checks if the vector is in valid format
if(!totalExpressionVector.test(vector)){
if (!totalExpressionVector.test(vector)) {
return false;
}

/**
* Scans the definitions file and returns an array of each registered abbreviation
* with its possible values.
*
* Exit example:
* [/\/AV:[NALP]/g, /\/AC:[LH]/g, /\/PR:[NLH]/g, /\/UI:[NR]/g, /\/S:[UC]/g,]
*
* A / at the beginning serves for the algorithm not to confuse abbreviations as AC and C.
*/
const allExpressions = definitions.definitions.map(currentValue => {
return new RegExp(`/${currentValue.abbr}:[${currentValue.metrics.reduce((accumulator2, currentValue2) => {
return accumulator2 + currentValue2.abbr;
}, "")}]`, "g");
* Scans the definitions file and returns an array of each registered abbreviation
* with its possible values.
*
* Exit example:
* [/\/AV:[NALP]/g, /\/AC:[LH]/g, /\/PR:[NLH]/g, /\/UI:[NR]/g, /\/S:[UC]/g,]
*
* A / at the beginning serves for the algorithm not to confuse abbreviations as AC and C.
*/
const allExpressions = definitions.definitions.map((currentValue) => {
return new RegExp(
`/${currentValue.abbr}:[${currentValue.metrics.reduce((accumulator2, currentValue2) => {
return accumulator2 + currentValue2.abbr;
}, "")}]`,
"g"
);
});

for(const regex of allExpressions) {
if((vector.match(regex) || []).length > 1) {
for (const regex of allExpressions) {
if ((vector.match(regex) || []).length > 1) {
return false;
}
}

const mandatoryParams = [/\/AV:[NALP]/g, /\/AC:[LH]/g, /\/PR:[NLH]/g, /\/UI:[NR]/g, /\/S:[UC]/g, /\/C:[NLH]/g, /\/I:[NLH]/g, /\/A:[NLH]/g];
const mandatoryParams = [
/\/AV:[NALP]/g,
/\/AC:[LH]/g,
/\/PR:[NLH]/g,
/\/UI:[NR]/g,
/\/S:[UC]/g,
/\/C:[NLH]/g,
/\/I:[NLH]/g,
/\/A:[NLH]/g
];

//Checks whether all mandatory parameters are present in the vector
for (const regex of mandatoryParams) {
if((vector.match(regex) || []).length < 1) {
if ((vector.match(regex) || []).length < 1) {
return false;
}
}
Expand All @@ -227,11 +324,20 @@ function CVSS(vector) {

//Check if vector format is valid
const isValid = isVectorValid();
if(!isValid) {
if (!isValid) {
throw new Error("The vector format is not valid!");
}

return { vector, getScore, getRating, getVectorObject, getDetailedVectorObject, getTemporalScore, isValid };
return {
vector,
getScore,
getTemporalScore,
getEnvironmentalScore,
getRating,
getVectorObject,
getDetailedVectorObject,
isValid
};
}

module.exports = CVSS;
Loading

0 comments on commit bf18cf3

Please sign in to comment.