From 80daaf588d8892bc5fa44bf2bf02e6df39968428 Mon Sep 17 00:00:00 2001 From: Karni Wolf Date: Tue, 1 Jan 2019 12:22:49 +0200 Subject: [PATCH] feat: display fixed in version key & refactor binaries issues display logic --- src/cli/commands/test.js | 185 +++++++++------- test/acceptance/cli.acceptance.test.ts | 9 +- .../fixtures/docker/find-result-binaries.json | 203 ++++++++++++++++-- test/fixtures/cli-test-results/semver@2 | 5 +- 4 files changed, 305 insertions(+), 97 deletions(-) diff --git a/src/cli/commands/test.js b/src/cli/commands/test.js index 7e74a57f710..ac195a4e1b2 100644 --- a/src/cli/commands/test.js +++ b/src/cli/commands/test.js @@ -1,6 +1,7 @@ module.exports = test; var _ = require('lodash'); +var semver = require('semver'); var chalk = require('chalk'); var debug = require('debug')('snyk'); var snyk = require('../../lib/'); @@ -195,7 +196,6 @@ function summariseErrorResults(errorResults) { function displayResult(res, options) { var meta = metaForDisplay(res, options) + '\n\n'; var dockerAdvice = dockerRemediationForDisplay(res); - var binariesIssues = binariesIssuesForDisplay(res); var packageManager = options.packageManager; var prefix = chalk.bold.white('\nTesting ' + options.path + '...\n\n'); @@ -236,7 +236,6 @@ function displayResult(res, options) { return ( prefix + meta + summaryOKText + ( isCI ? '' : - binariesIssues + dockerAdvice + nextStepsText + dockerSuggestion) @@ -288,58 +287,118 @@ function displayResult(res, options) { ['metadata.severityValue', 'metadata.name'], ['asc', 'desc'] ); - var groupedVulnInfoOutput = sortedGroupedVulns.map(function (vuln) { - var vulnID = vuln.list[0].id; - var uniquePackages = _.uniq( - vuln.list.map(function (i) { - if (i.from[1]) { - return i.from && i.from[1]; - } - return i.from; - })) - .join(', '); - - var vulnOutput = { - issueHeading: createSeverityBasedIssueHeading( - vuln.metadata.severity, - vuln.metadata.type, - vuln.metadata.name - ), - introducedThrough: ' Introduced through: ' + uniquePackages, - description: ' Description: ' + vuln.title, - info: ' Info: ' + chalk.underline(config.ROOT + '/vuln/' + vulnID), - fromPaths: options.showVulnPaths - ? createTruncatedVulnsPathsText(vuln.list) : '', - extraInfo: vuln.note ? chalk.bold('\n Note: ' + vuln.note) : '', - remediationInfo: vuln.metadata.type !== 'license' - ? createRemediationText(vuln, packageManager) - : '', - fixedIn: options.docker ? createFixedInText(vuln) : '', - }; - return ( - vulnOutput.issueHeading + '\n' + - vulnOutput.description + '\n' + - vulnOutput.info + '\n' + - vulnOutput.introducedThrough + '\n' + - vulnOutput.fromPaths + - // Optional - not always there - vulnOutput.remediationInfo + - vulnOutput.fixedIn + - vulnOutput.extraInfo - ); + var filteredSortedGroupedVulns = sortedGroupedVulns.filter(function (vuln) { + return (vuln.metadata.packageManager !== 'upstream'); }); + var binariesSortedGroupedVulns = sortedGroupedVulns.filter(function (vuln) { + return (vuln.metadata.packageManager === 'upstream'); + }); + var groupedVulnInfoOutput = filteredSortedGroupedVulns.map(vuln => formatIssues(vuln, options)); + var groupedDockerBinariesVulnInfoOutput = (res.docker && res.docker.binariesVulns) ? + formatDockerBinariesIssues(binariesSortedGroupedVulns, res.docker.binariesVulns, options) : []; var body = - groupedVulnInfoOutput.join('\n\n') + '\n\n\n' + binariesIssues + '\n\n' + meta + summary; + groupedVulnInfoOutput.join('\n\n') + '\n\n\n' + + groupedDockerBinariesVulnInfoOutput.join('\n\n') + '\n\n' + meta + summary; return prefix + body + dockerAdvice + dockerSuggestion; }; -function createFixedInText(groupedVuln) { - var vulnerableRange = groupedVuln.list[0].semver.vulnerable[0]; - if (/^<\S+$/.test(vulnerableRange)) { - // removing the first char from the version. For example: <7.50.1-1 - return chalk.bold('\n Fixed in: ' + vulnerableRange.substr(1)); +function formatDockerBinariesIssues(dockerBinariesSortedGroupedVulns, binariesVulns, options) { + const binariesIssuesOutput = []; + for (const pkgInfo of _.values(binariesVulns.affectedPkgs)) { + binariesIssuesOutput.push(createDockerBinaryHeading(pkgInfo)); + let binaryIssues = dockerBinariesSortedGroupedVulns.filter(function (vuln) { + return (vuln.metadata.name === pkgInfo.pkg.name); + }); + const formattedBinaryIssues = binaryIssues.map(vuln => formatIssues(vuln, options)); + binariesIssuesOutput.push(formattedBinaryIssues.join('\n\n')); } - return ''; + return binariesIssuesOutput; +} + +function createDockerBinaryHeading(pkgInfo) { + const binaryName = pkgInfo.pkg.name; + const binaryVersion = pkgInfo.pkg.version; + const numOfVulns = _.values(pkgInfo.issues).length; + return numOfVulns ? + chalk.bold.white(`------------ Detected ${numOfVulns} vulnerabilities`+ + ` for ${binaryName}@${binaryVersion} ------------`, '\n') : ''; +} + +function formatIssues(vuln, options) { + var vulnID = vuln.list[0].id; + var packageManager = options.packageManager; + var uniquePackages = _.uniq( + vuln.list.map(function (i) { + if (i.from[1]) { + return i.from && i.from[1]; + } + return i.from; + })) + .join(', '); + + var version = undefined; + var vulnerableRange = vuln.list[0].semver.vulnerable; + if (vuln.metadata.packageManager.toLowerCase() === 'upstream') { + version = vuln.metadata.version; + }; + + var vulnOutput = { + issueHeading: createSeverityBasedIssueHeading( + vuln.metadata.severity, + vuln.metadata.type, + vuln.metadata.name + ), + introducedThrough: ' Introduced through: ' + uniquePackages, + description: ' Description: ' + vuln.title, + info: ' Info: ' + chalk.underline(config.ROOT + '/vuln/' + vulnID), + fromPaths: options.showVulnPaths + ? createTruncatedVulnsPathsText(vuln.list) : '', + extraInfo: vuln.note ? chalk.bold('\n Note: ' + vuln.note) : '', + remediationInfo: vuln.metadata.type !== 'license' + ? createRemediationText(vuln, packageManager) + : '', + fixedIn: options.docker ? createFixedInText(vulnerableRange, version) : '', + }; + return ( + vulnOutput.issueHeading + '\n' + + vulnOutput.description + '\n' + + vulnOutput.info + '\n' + + vulnOutput.introducedThrough + '\n' + + vulnOutput.fromPaths + + // Optional - not always there + vulnOutput.remediationInfo + + vulnOutput.fixedIn + + vulnOutput.extraInfo + ); +} + +function createFixedInText(versionRangeList, pkgVersion) { + let fixedVersion = ''; + let fixedVersionCandidate = ''; + const lesserThan = /^<\S+$/; + // pkgVersion is undefined for OS packages vulns + if (!pkgVersion) { + if (versionRangeList && versionRangeList.length) { + // OS packages vulns versionRangeList includes a single upper bound version + fixedVersionCandidate = versionRangeList[0]; + // trim relational operator `<` from first version in list + fixedVersion = lesserThan.test(fixedVersionCandidate) ? + fixedVersionCandidate.substr(1) : ''; + } + } else { + for (const versionRange of versionRangeList) { + if (!semver.valid(pkgVersion) || !semver.satisfies(pkgVersion, versionRange)) { + continue; + } + // e.g. extract '5.1.0' from version range: '>=4.1.0 <5.1.0' + fixedVersionCandidate = versionRange.split(' ')[1]; + if (lesserThan.test(fixedVersionCandidate)) { + fixedVersion = fixedVersionCandidate.substr(1); + break; + } + } + } + return fixedVersion ? chalk.bold('\n Fixed in: ' + fixedVersion): ''; } function createRemediationText(vuln, packageManager) { @@ -523,34 +582,6 @@ function dockerRemediationForDisplay(res) { return '\n\n' + out.join('\n'); } -function binariesIssuesForDisplay(res) { - const issues = []; - const dockerRes = res.docker; - if (dockerRes && dockerRes.binariesVulns) { - const binariesVulns = dockerRes.binariesVulns; - for (const pkgInfo of _.values(binariesVulns.affectedPkgs)) { - issues.push(chalk.bold.white( - `------------ Detected ${_.values(pkgInfo.issues).length} vulnerabilities`+ - ` for ${pkgInfo.pkg.name}@${pkgInfo.pkg.version} ------------`, '\n')); - for (const pkgIssue of _.values(pkgInfo.issues)) { - const issueID = pkgIssue.issueId; - const issueData = binariesVulns.issuesData[issueID]; - const issueHeading = createSeverityBasedIssueHeading( - issueData.severity, - issueData.type, - issueData.packageName - ); - issues.push( - issueHeading + - '\n Description: ' + issueData.title + - '\n Info: ' + chalk.underline(config.ROOT + '/vuln/' + issueID) + '\n' - ); - } - } - } - return issues.join('\n'); -} - function validateSeverityThreshold(severityThreshold) { return SEVERITIES .map(function (s) { @@ -626,5 +657,7 @@ function metadataForVuln(vuln) { severity: vuln.severity, severityValue: getSeverityValue(vuln.severity), isNew: isNewVuln(vuln), + version: vuln.version, + packageManager: vuln.packageManager, }; } diff --git a/test/acceptance/cli.acceptance.test.ts b/test/acceptance/cli.acceptance.test.ts index 8cc80883891..b64d2e0bb73 100644 --- a/test/acceptance/cli.acceptance.test.ts +++ b/test/acceptance/cli.acceptance.test.ts @@ -1629,6 +1629,9 @@ test('`test foo:latest --docker with binaries vulnerabilities`', async (t) => { 'bzip2/libbz2-1.0': { version: '1.0.6-8.1', }, + 'bzr/libbz2-1.0': { + version: '1.0.6-8.1', + }, }, docker: { binaries: { @@ -1656,14 +1659,16 @@ test('`test foo:latest --docker with binaries vulnerabilities`', async (t) => { t.fail('should have found vuln'); } catch (err) { const msg = err.message; - t.match(msg, 'Tested 2 dependencies for known vulnerabilities, found 2 vulnerabilities'); + t.match(msg, 'Tested 3 dependencies for known vulnerabilities, found 3 vulnerabilities'); t.match(msg, 'From: bzip2/libbz2-1.0@1.0.6-8.1'); t.match(msg, 'From: apt/libapt-pkg5.0@1.6.3ubuntu0.1 > bzip2/libbz2-1.0@1.0.6-8.1'); t.match(msg, 'Info: http://localhost:12345/vuln/SNYK-UPSTREAM-NODE-72359'); t.false(msg.includes('vulnerable paths'), 'docker should not includes number of vulnerable paths'); - t.match(msg, 'Detected 1 vulnerabilities for node@5.10.1'); + t.match(msg, 'Detected 2 vulnerabilities for node@5.10.1'); t.match(msg, 'High severity vulnerability found in node'); + t.match(msg, 'Fixed in: 5.13.1'); + t.match(msg, 'Fixed in: 5.15.1'); } }); diff --git a/test/acceptance/fixtures/docker/find-result-binaries.json b/test/acceptance/fixtures/docker/find-result-binaries.json index d8c2456ac76..98b6105a44c 100644 --- a/test/acceptance/fixtures/docker/find-result-binaries.json +++ b/test/acceptance/fixtures/docker/find-result-binaries.json @@ -1,22 +1,37 @@ { "result": { - "affectedPkgs": { - "bzip2/libbz2-1.0@1.0.6-8.1": { - "pkg": { - "version": "1.0.6-8.1", - "name": "bzip2/libbz2-1.0" - }, - "issues": { - "SNYK-LINUX-BZIP2-106947": { - "issueId": "SNYK-LINUX-BZIP2-106947", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false + "affectedPkgs": { + "bzip2/libbz2-1.0@1.0.6-8.1": { + "pkg": { + "version": "1.0.6-8.1", + "name": "bzip2/libbz2-1.0" + }, + "issues": { + "SNYK-LINUX-BZIP2-106947": { + "issueId": "SNYK-LINUX-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false + } } } - } - } }, + "bzr/libbz2-1.0@1.0.6-8.1": { + "pkg": { + "version": "1.0.6-8.1", + "name": "bzr/libbz2-1.0" + }, + "issues": { + "SNYK-LINUX-BZR-133048": { + "issueId": "SNYK-LINUX-BZR-133048", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false + } + } + } + } + }, "issuesData": { "SNYK-LINUX-BZIP2-106947": { "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", @@ -116,7 +131,106 @@ }, "severity": "low", "title": "Denial of Service (DoS)" - } + }, + "SNYK-LINUX-BZR-133048": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-06-27T16:12:23.571063Z", + "credit": [ + "" + ], + "cvssScore": 6.5, + "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", + "disclosureTime": null, + "id": "SNYK-LINUX-BZIP2-106947", + "identifiers": { + "CVE": [ + "CVE-2016-3189" + ], + "CWE": [] + }, + "internal": {}, + "language": "linux", + "modificationTime": "2018-10-22T04:31:58.564093Z", + "packageManager": "linux", + "packageName": "bzip2", + "patches": [], + "publicationTime": "2016-06-30T17:59:00Z", + "references": [ + { + "title": "GENTOO", + "url": "https://security.gentoo.org/glsa/201708-08" + }, + { + "title": "CONFIRM", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" + }, + { + "title": "SECTRACK", + "url": "http://www.securitytracker.com/id/1036132" + }, + { + "title": "BID", + "url": "http://www.securityfocus.com/bid/91297" + }, + { + "title": "CONFIRM", + "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" + }, + { + "title": "MLIST", + "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" + } + ], + "semver": { + "vulnerableByDistro": { + "alpine:3.4": [ + "<1.0.6-r5" + ], + "alpine:3.5": [ + "<1.0.6-r5" + ], + "alpine:3.6": [ + "<1.0.6-r5" + ], + "alpine:3.7": [ + "<1.0.6-r5" + ], + "alpine:3.8": [ + "<1.0.6-r5" + ], + "debian:10": [ + "<1.0.6-8.1" + ], + "debian:8": [ + "*" + ], + "debian:9": [ + "<1.0.6-8.1" + ], + "debian:unstable": [ + "<1.0.6-8.1" + ], + "ubuntu:12.04": [ + "*" + ], + "ubuntu:14.04": [ + "*" + ], + "ubuntu:16.04": [ + "*" + ], + "ubuntu:18.04": [ + "*" + ] + }, + "vulnerable": [ + "*" + ] + }, + "severity": "low", + "title": "Denial of Service (DoS)" + } }, "docker": { "binariesVulns": { @@ -133,7 +247,14 @@ "upgradePaths": [], "isPatchable": false } - } + }, + "SNYK-LINUX-BZR-133048": { + "issueId": "SNYK-LINUX-BZR-133048", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false + } + } } } }, @@ -180,7 +301,55 @@ "semver": { "vulnerable": [ ">=4.0.0 <4.2.3", - ">=5.0.0 <5.1.1" + ">=5.0.0 <5.13.1" + ] + }, + "severity": "high", + "title": "Denial of Service" + }, + "SNYK-LINUX-BZR-133048": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "alternativeIds": [], + "creationTime": "2018-09-12T13:04:09.124530Z", + "credit": [ + "Unknown" + ], + "cvssScore": 7.3, + "description": "## Overview\n[node](https://nodejs.org/en/) is a JavaScript runtime built on Chrome's V8 JavaScript engine.\r\n\r\nAffected versions of this package are vulnerable to Denial of Service (out-of-bounds read) due to improperly loading array elements.\r\n\r\n## Details\r\nDenial of Service (DoS) describes a family of attacks, all aimed at making a system inaccessible to its intended and legitimate users.\r\n\r\nUnlike other vulnerabilities, DoS attacks usually do not aim at breaching security. Rather, they are focused on making websites and services unavailable to genuine users resulting in downtime.\r\n\r\nOne popular Denial of Service vulnerability is DDoS (a Distributed Denial of Service), an attack that attempts to clog network pipes to the system by generating a large volume of traffic from many machines.\r\n\r\nWhen it comes to open source libraries, DoS vulnerabilities allow attackers to trigger such a crash or crippling of the service by using a flaw either in the application code or from the use of open source libraries.\r\n\r\nTwo common types of DoS vulnerabilities:\r\n\r\n* High CPU/Memory Consumption- An attacker sending crafted requests that could cause the system to take a disproportionate amount of time to process. For example, [commons-fileupload:commons-fileupload](SNYK-JAVA-COMMONSFILEUPLOAD-30082).\r\n\r\n* Crash - An attacker sending crafted requests that could cause the system to crash. For Example, [npm `ws` package](npm:ws:20171108)\r\n\r\n## Remediation\r\nUpgrade `node` to versions 5.1.1, 4.2.3 or higher.\n\n## References\n- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2015-6764)\n", + "disclosureTime": null, + "functions": [], + "id": "SNYK-UPSTREAM-NODE-72328", + "identifiers": { + "CVE": [ + "CVE-2015-6764" + ], + "CWE": [] + }, + "internal": { + "content": "templated", + "flags": { + "premium": false + }, + "source": "external" + }, + "language": "upstream", + "methods": [], + "modificationTime": "2018-12-12T17:12:34.427465Z", + "moduleName": "node", + "packageManager": "upstream", + "packageName": "node", + "patches": [], + "publicationTime": "2018-12-12T17:12:34.375733Z", + "references": [ + { + "title": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6764" + } + ], + "semver": { + "vulnerable": [ + ">=4.0.0 <4.2.3", + ">=5.0.0 <5.15.1" ] }, "severity": "high", diff --git a/test/fixtures/cli-test-results/semver@2 b/test/fixtures/cli-test-results/semver@2 index 80b0946b7f9..18fdbceb976 100644 --- a/test/fixtures/cli-test-results/semver@2 +++ b/test/fixtures/cli-test-results/semver@2 @@ -45,7 +45,8 @@ "semver@4.3.2" ], "version": "2.3.2", - "name": "semver" + "name": "semver", + "packageManager": "npm" } ] -} \ No newline at end of file +}