Skip to content

Commit

Permalink
Merge pull request #7 from softrams/kev
Browse files Browse the repository at this point in the history
CISA KEV Support
  • Loading branch information
mkmurali authored May 31, 2023
2 parents d6ca4be + a46f570 commit 61f9eaa
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 32 deletions.
72 changes: 46 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,60 @@
# NPM EPSS Audit

Currently NPM Audit reports severity of vulnerabilities based on the CVSS score. NPM bulk audit response do not include CVEs in the report as of May 2023. This interim tool uses the NPM Quick Audit end point to retrieve associated CVEs and reports corresponding EPSS scores to help prioritize vulnerabilities.
Currently NPM Audit reports severity of vulnerabilities based on the CVSS score. Also the response received from NPM bulk audit used within `npm audit` do not include CVEs in the report as of May 2023. This interim tool uses the NPM Quick Audit end point to retrieve associated CVEs and reports corresponding EPSS scores to help prioritize vulnerabilities.

> **Note**
> Version 0.0.12+ includes support to check if a CVE is included in the CISA Known Exploited Vulnerability (KEV) catalog.
## About EPSS

EPSS stands for Exploit Prediction Scoring System. It is a machine learning-based model that predicts the likelihood of a software vulnerability being exploited in the wild. The EPSS score is a number between 0 and 1, with a higher score indicating a higher likelihood of exploitation. The EPSS score is calculated using a variety of factors, including the severity of the vulnerability, the availability of exploit code, and the number of known attacks.

See EPSS at [https://www.first.org/epss](https://www.first.org/epss).

## About CISA Known Exploited Vulnerability (KEV) catalog

> For the benefit of the cybersecurity community and network defenders—and to help every organization better manage vulnerabilities and keep pace with threat activity—CISA maintains the authoritative source of vulnerabilities that have been exploited in the wild: the Known Exploited Vulnerability (KEV) catalog. CISA strongly recommends all organizations review and monitor the KEV catalog and prioritize remediation of the listed vulnerabilities to reduce the likelihood of compromise by known threat actors. All federal civilian executive branch (FCEB) agencies are required to remediate vulnerabilities in the KEV catalog within prescribed timeframes.
See CISA KEV Catalog at [https://www.cisa.gov/known-exploited-vulnerabilities](https://www.cisa.gov/known-exploited-vulnerabilities).

> **Warning**
> The CISA KEV catalog is very limited when it comes to individual NPM packages. This is included to support future use cases of the tool.
## Usage

### Usage via global install option
NPM Audit requires that all project dependencies are already installed and package-lock.json file exists.
Make sure to install dependencies in the project before running the tool.

> Note: NPM Audit requires that all project dependencies are already installed and package-lock.json file exists. Make sure to install dependencies in the project before running the tool.
### Usage via npx

```bash
npm install -g npm-epss-audit@latest

## Run the tool in the project directory
npm-epss-audit
npx npm-epss-audit@latest
```

### Usage via npx
### Usage via global install option

```bash
## Install the tool globally
npm install -g npm-epss-audit@latest

## Run the tool in the project directory
npx npm-epss-audit@latest
npm-epss-audit
```

### Options

```bash
Usage: npm-epss-audit [-v|--verbose] [-r|--refresh] [-t|--threshold]]
Usage: npm-epss-audit [-v|--verbose] [-r|--refresh] [-f|--fail-on-past-duedate] [-t|--threshold]

Options:
--version Show version number [boolean]
-v, --verbose Verbose output
-r, --refresh Refresh EPSS scores
-t, --threshold EPSS score threshold to fail the audit [number] [default: 0.0]
--help Show help [boolean]
--version Show version number [boolean]
-v, --verbose Verbose output
-r, --refresh Refresh EPSS scores
-f, --fail-on-past-duedate Fail on past CISA KVE due date
-t, --threshold EPSS score threshold to fail the audit
[number] [default: 0]
--help Show help [boolean]

```

Expand All @@ -50,6 +66,9 @@ For use in CI pipelines and automation tools, the tool will exit with the follow
- 1: Failed to run due to errors or other configuration issues
- 2: Ran successfully and vulnerabilities found that exceeded the EPSS Score threshold (default: 0.0, means all vulnerabilities are reported)

You may also use the `--fail-on-past-duedate` option to fail the audit if any of the vulnerabilities are past the CISA KEV due date or
set the `--threshold` option to a value of your choice greater than 0.0 to fail the audit if any of the vulnerabilities exceed the EPSS Score threshold.

### Example output

```bash
Expand All @@ -58,24 +77,24 @@ npm-epss-audit

Auditing <project> v0.1.0

┌─────────┬────────────────────────┬────────────┬──────────────────┬──────┬────────────────┐
│ (index) │ Module │ Severity │ CVE ID │ CVSS │ EPSS Score (%) │
├─────────┼────────────────────────┼────────────┼──────────────────┼──────┼────────────────┤
│ 0 │ 'json5''high''CVE-2022-46175' │ 7.1 │ 0.225 │
│ 1 │ 'loader-utils''critical''CVE-2022-37601' │ 9.8 │ 0.163 │
└─────────┴────────────────────────┴────────────┴──────────────────┴──────┴────────────────┘
┌─────────┬────────────────────────┬────────────┬──────────────────┬──────┬────────────────┬───────────┬──────────
│ (index) │ Module │ Severity │ CVE ID │ CVSS │ EPSS Score (%) │ CISA KEV? │ Due Date │
├─────────┼────────────────────────┼────────────┼──────────────────┼──────┼────────────────┼───────────┼──────────
│ 0 │ 'json5''high''CVE-2022-46175' │ 7.1 │ 0.225 │ 'No'''
│ 1 │ 'loader-utils''critical''CVE-2022-37601' │ 9.8 │ 0.163 │ 'No'''
└─────────┴────────────────────────┴────────────┴──────────────────┴──────┴────────────────┴───────────┴──────────

# Fail audit only for vulnerabilities with EPSS score greater than 0.0015 (0.15%)
npm-epss-audit --threshold 0.0015

Auditing <project> v0.1.0

┌─────────┬────────────────────────┬────────────┬──────────────────┬──────┬────────────────┐
│ (index) │ Module │ Severity │ CVE ID │ CVSS │ EPSS Score (%) │
├─────────┼────────────────────────┼────────────┼──────────────────┼──────┼────────────────┤
│ 0 │ 'json5''high''CVE-2022-46175' │ 7.1 │ 0.225 │
│ 1 │ 'loader-utils''critical''CVE-2022-37601' │ 9.8 │ 0.163 │
└─────────┴────────────────────────┴────────────┴──────────────────┴──────┴────────────────┘
┌─────────┬────────────────────────┬────────────┬──────────────────┬──────┬────────────────┬───────────┬──────────
│ (index) │ Module │ Severity │ CVE ID │ CVSS │ EPSS Score (%) │ CISA KEV? │ Due Date │
├─────────┼────────────────────────┼────────────┼──────────────────┼──────┼────────────────┼───────────┼──────────
│ 0 │ 'json5''high''CVE-2022-46175' │ 7.1 │ 0.225 │ 'No'''
│ 1 │ 'loader-utils''critical''CVE-2022-37601' │ 9.8 │ 0.163 │ 'No'''
└─────────┴────────────────────────┴────────────┴──────────────────┴──────┴────────────────┴───────────┴──────────


At least one CVE with EPSS Score threshold 0.0015 exceeded.
Expand All @@ -101,3 +120,4 @@ Otherwise, create an issue with your thoughts and ideas.
- [EPSS](https://www.first.org/epss/data_stats)
- [NPM Audit](https://docs.npmjs.com/cli/v9/commands/npm-audit)
- [NPM Quick Audit](https://docs.npmjs.com/cli/v9/commands/npm-audit#quick-audit-endpoint)
- [CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities)
98 changes: 93 additions & 5 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,29 @@ const EPSS_DATA_FOLDER =
process.env.EPSS_DATA_FOLDER || process.env.HOME || "/tmp";

const epssScores = {};
const kevData = {};

async function downloadFile(url, path) {
const outfile = fs.createWriteStream(path);
const response = await fetch(url);
await finished(Readable.fromWeb(response.body).pipe(outfile));
}

async function syncKEV(refresh = false) {
if (!fs.existsSync(`${EPSS_DATA_FOLDER}/.epss`)) {
console.log(`\nCreating ${EPSS_DATA_FOLDER}/.epss folder`);
fs.mkdirSync(`${EPSS_DATA_FOLDER}/.epss`);
}

if (!fs.existsSync(`${EPSS_DATA_FOLDER}/.epss/kev.json`) || refresh) {
console.log(`\nDownloading CISA Known Exploited Vulnerabilities catalog`);
await downloadFile(
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
`${EPSS_DATA_FOLDER}/.epss/kev.json`
);
}
}

async function syncEpss(refresh = false) {
if (!fs.existsSync(`${EPSS_DATA_FOLDER}/.epss`)) {
console.log(`\nCreating ${EPSS_DATA_FOLDER}/.epss folder`);
Expand All @@ -42,6 +58,19 @@ async function syncEpss(refresh = false) {
}
}

async function loadKEVCatalog(refresh = false) {
const kevContents = fs.readFileSync(
`${EPSS_DATA_FOLDER}/.epss/kev.json`,
"utf8"
);
const kev = JSON.parse(kevContents);
for (const item of kev.vulnerabilities) {
kevData[item.cveID] = {
...item,
};
}
}

async function loadScores(refresh = false) {
const csv = fs.readFileSync(`${EPSS_DATA_FOLDER}/.epss/epss.csv`, "utf8");
const lines = csv.split("\n");
Expand All @@ -65,7 +94,7 @@ async function loadScores(refresh = false) {
// console.log(`Loaded ${idx} EPSS scores`);
}

async function audit(verbose = false, threshold = 0.0) {
async function audit(verbose = false, threshold = 0.0, failOnPastDue = false) {
if (!fs.existsSync(process.cwd() + "/package.json")) {
console.log(
`\nError: package.json not found in ${process.cwd()}. Run 'npm-epss-audit' in the project root directory where package.json is located.`
Expand Down Expand Up @@ -131,6 +160,9 @@ async function audit(verbose = false, threshold = 0.0) {
}

let aboveThreshold = false;
let pastDueDate = false;

const today = new Date();

// Print results
// Metadata -> Vulnerabilities
Expand Down Expand Up @@ -166,6 +198,7 @@ async function audit(verbose = false, threshold = 0.0) {
);
console.log(`More info: ${value.url}`);
console.log(`\n`);

if (value.cves && value.cves.length > 0) {
console.log(`CVSS Score: ${value.cvss.score}`);
console.log(`CVE: ${value.cves[0]}`);
Expand All @@ -175,16 +208,36 @@ async function audit(verbose = false, threshold = 0.0) {
).toFixed(3)}%`
);

// Check if CVW is in Known Exploratory Vulnerabilities
const kve = kevData[value.cves[0]];
if (kve) {
console.log(`CISA Known Exploited Vulnerability: Yes`);
console.log(` Date Added: ${kve.dateAdded}`);
console.log(` Due Date: ${kve.dueDate}`);

// Check if due date is in the past
const dueDate = new Date(kve.dueDate);
if (today > dueDate) {
pastDueDate = true;
}
} else {
console.log(`CISA Known Exploited Vulnerability: No`);
}

// Check if EPSS score is above threshold
if (
+Number(epssScores[value.cves[0]].epss).toFixed(5) >
+Number(threshold).toFixed(5)
) {
aboveThreshold = true;
}
}

console.log(`\n`);
} else {
if (value.cves && value.cves.length > 0) {
const kve = kevData[value.cves[0]];

tabularData.push({
Module: value.module_name,
Severity: value.severity,
Expand All @@ -193,14 +246,25 @@ async function audit(verbose = false, threshold = 0.0) {
"EPSS Score (%)": +Number(
epssScores[value.cves[0]].epss * 100.0
).toFixed(3),
"CISA KEV?": kve ? "Yes" : "No",
"Due Date": kve ? kve.dueDate : "",
});

// Check if EPSS score is above threshold
if (
+Number(epssScores[value.cves[0]].epss).toFixed(5) >
+Number(threshold).toFixed(5)
) {
aboveThreshold = true;
}

// Check if due date is in the past
if (kve) {
const dueDate = new Date(kve.dueDate);
if (today > dueDate) {
pastDueDate = true;
}
}
}
}
count++;
Expand All @@ -222,6 +286,16 @@ async function audit(verbose = false, threshold = 0.0) {

console.log(`\n`);

if (pastDueDate) {
console.log(
`At least one CVE is past its due date as per CISA Known Exploited Vulnerabilities Catalog.\n`
);

if (failOnPastDue) {
process.exit(2);
}
}

if (aboveThreshold) {
if (Number(threshold) > 0.0) {
console.log(
Expand All @@ -231,9 +305,9 @@ async function audit(verbose = false, threshold = 0.0) {
);
}
process.exit(2);
} else {
process.exit(0);
}

process.exit(0);
} else {
console.log(`No vulnerabilities found`);
process.exit(0);
Expand All @@ -244,12 +318,18 @@ async function audit(verbose = false, threshold = 0.0) {
try {
const options = yargs
.scriptName("npm-epss-audit")
.usage("Usage: $0 [-v|--verbose] [-r|--refresh] [-t|--threshold]]")
.usage(
"Usage: $0 [-v|--verbose] [-r|--refresh] [-f|--fail-on-past-duedate] [-t|--threshold]"
)
.option("v", {
alias: "verbose",
describe: "Verbose output",
})
.option("r", { alias: "refresh", describe: "Refresh EPSS scores" })
.option("f", {
alias: "fail-on-past-duedate",
describe: "Fail on past CISA KVE due date",
})
.option("t", {
alias: "threshold",
describe: "EPSS score threshold to fail the audit",
Expand All @@ -259,8 +339,16 @@ async function audit(verbose = false, threshold = 0.0) {
.help(true).argv;

await syncEpss(options.refresh);
await syncKEV(options.refresh);

await loadScores(options.refresh);
await audit(options.verbose, options.threshold);
await loadKEVCatalog(options.refresh);

await audit(
options.verbose,
options.threshold,
options["fail-on-past-duedate"]
);
} catch (err) {
console.error(err);
process.exit(1);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "npm-epss-audit",
"version": "0.0.11",
"version": "0.0.12",
"description": "Use EPSS scores to prioritize NPM Audit findings",
"main": "bin/index.js",
"bin": {
Expand Down

0 comments on commit 61f9eaa

Please sign in to comment.