Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Exempt vulnerabilities for single archives (digests) #319

Merged
merged 36 commits into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d3c764d
Added new class BugExemption in order to exempt bugs for indiv libraries
henrikplate Jan 2, 2020
150fda5
Moved BugExemption to shared; Added JUnit test
henrikplate Jan 6, 2020
725a4a7
Updated JUnit test; Improved Html report
henrikplate Jan 6, 2020
7f20355
Added license and copyright header to new Java sources
henrikplate Jan 6, 2020
0720a27
Changed exemption format to vulas.report.exempt.<bugId>.<digest>.<sco…
henrikplate Jan 6, 2020
ec793f8
Introduced interface IExemption
henrikplate Jan 7, 2020
6bf0033
Modified rest-backend, frontend-app
henrikplate Jan 8, 2020
af4263c
Included exempted scopes and bugs in Html report
henrikplate Apr 15, 2020
142f0c7
Updated Json and Xml reports
henrikplate Apr 15, 2020
f3ff184
Display bug description and alt. bug description in Html report
henrikplate Apr 15, 2020
ae090b9
Added serializaton test for ExemptionScope
henrikplate Apr 15, 2020
06d7ef1
Small correction of the Html report
henrikplate Apr 15, 2020
c133fe1
Resolved compile problems that resulted from the rebase on master
henrikplate Apr 16, 2020
92dd29f
Changed bug exemption format to vulas.report.exemptBug.CVE-0000-1111.…
henrikplate Apr 20, 2020
f571026
Upated JUnit tests to reflect format change for bug exemptions
henrikplate Apr 20, 2020
e9e7a43
Differentiate exemption type during finding export
henrikplate Apr 20, 2020
5949a9b
improved test to get App VulnerableDependencies
serenaponta Apr 22, 2020
c7b7124
fix infinite recursion
serenaponta Apr 22, 2020
85de90b
Undo changes of checkbox labels in vuln view (historical and unconfir…
henrikplate Apr 22, 2020
fd41e3f
use backend.View
serenaponta Apr 22, 2020
ee6a6b1
Merge branch 'master' into exempt-vuln-digest
serenaponta Apr 22, 2020
bfe08a4
exemption img renamed
serenaponta Apr 23, 2020
36454ad
added failing test
serenaponta Apr 24, 2020
ff1e29f
modify expected value in tests
serenaponta Apr 24, 2020
5af8963
added handling of config keys from previous scans
serenaponta Apr 24, 2020
363033d
fix copy/paste
serenaponta Apr 27, 2020
2d4f66e
New format for bug exemptions, consider package URLs
henrikplate Apr 27, 2020
3eb12b0
Merge branch 'exempt-vuln-digest' of github.com:SAP/vulnerability-ass…
henrikplate Apr 27, 2020
bd1e5de
Updated doc
henrikplate Apr 28, 2020
20aed16
Updated exemption-related doc in Html template
henrikplate Apr 28, 2020
ddfd24d
Added checks for package URLs (supported types and required elements …
henrikplate Apr 28, 2020
6e280b8
Fixed NPE when reading exemptions from Map; Added option to created A…
henrikplate Apr 29, 2020
5d8d0dd
Added digest info to report console log (to facilitate digest exempti…
henrikplate May 4, 2020
c5dacf0
modified computation of affected flag for bug w/o cc
serenaponta May 4, 2020
57ad6a7
Merge branch 'master' into exempt-vuln-digest
henrikplate May 7, 2020
d2c9621
Fixed compilation error that has been introduced with the resolution …
henrikplate May 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions docs/public/content/user/manuals/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,18 +563,19 @@ Identified vulnerabilities including any information gathered during static and
#### Configure as follows

```ini
# A vulnerability in blacklisted scopes will not cause an exception (multiple scopes to be separated by comma)
# Default: test, provided
# Note: For CLI, all dependencies are considered as RUNTIME dependencies
vulas.report.exceptionScopeBlacklist = TEST, PROVIDED

# Specified vulnerabilities will not cause a build exception (multiple bugs to be separated by comma)
# Exempts the given vulnerability from causing a build exception
# This can apply to all libraries including the vulnerable code (by omitting `libraries` or explicitely specifying `*`)
# or to selected libraries with the given digest(s).
#
# Default: -
vulas.report.exceptionExcludeBugs = <vuln-id>
vulas.report.exemptBug.<vuln-id>.reason = <reason>
vulas.report.exemptBug.<vuln-id>.libraries = [ * | digest [, digest] ]

# Explanation why the given vulnerability is not relevant/exploitable in the specific application context
# Default: -
vulas.report.exceptionExcludeBugs.<vuln-id> = Not exploitable because ...
# A vulnerability in exempted scopes will not cause an exception (multiple scopes to be separated by comma)
#
# Default: test, provided
# Note: For CLI, all dependencies are considered as RUNTIME dependencies
vulas.report.exemptScope = TEST, PROVIDED

# Determines whether un-assessed vulnerabilities (e.g. vulnerabilities marked with an orange hourglass symbol) throw a build exception. Un-assessed vulns are those where
# the method signature(s) of a bug appear in an archive, however, it is yet unclear whether the methods
Expand Down Expand Up @@ -623,13 +624,13 @@ mvn -Dvulas vulas:report

#### Exemptions

The settings `vulas.report.exceptionExcludeBugs` and `vulas.report.exceptionExcludeBugs.<vuln-id>` can be used to capture the results of an audit or assessment by developers in regards to whether a vulnerability is problematic in a given application context. Exempted bugs do not result in build exceptions and are also shown in the apps Web frontend.
The setting `vulas.report.exemptBug.<vuln-id>.reason` can be used to capture the results of an audit or assessment by developers in regards to whether a vulnerability is problematic in a given application context. Exempted bugs do not result in build exceptions and are also shown in the apps Web frontend.

#### Build exceptions

Other settings to fine-tune the threshold for build exceptions are as follows:

- `vulas.report.exceptionScopeBlacklist` can be used to exclude certain Maven scopes (default: test)
- `vulas.report.exemptScope` can be used to exclude certain Maven scopes (default: test)
- `vulas.report.exceptionThreshold` can be used to specify whether a build exception is thrown when vulnerable code is included, potentially reachable, actually reached or not at all (values: `noException`, `dependsOn`, `potentiallyExecutes`, `actuallyExecutes`; default: `actuallyExecutes`)

## Clean and delete apps (clean)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
<vulas.reach.timeout>60</vulas.reach.timeout>

<!-- vulas:report -->
<vulas.report.exceptionExcludeBugs></vulas.report.exceptionExcludeBugs>
<vulas.report.reportDir>${project.build.directory}/vulas/report</vulas.report.reportDir>
</layeredConfiguration>
</configuration>
Expand Down
13 changes: 9 additions & 4 deletions lang/src/test/java/com/sap/psr/vulas/report/ReportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,14 @@ public void testReport() {
this.setupMockServices(this.testApp);

// Exemptions
vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2014-0050.dig:6F1EBC6CE20AD8B3D4825CEB2E625E5C432A0E10", "The vulnerable library with digest 6F1EBC is no problem because ...");
vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2013-2186.*", "Vulnerability CVE-2013-2186 is no problem because ...");
vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2019-1234.*", "Vulnerability CVE-2019-1234 is no problem because ...");
vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2014-0050.reason", "The vulnerable library with digest 6F1EBC is no problem because ...");
vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2014-0050.libraries", "6F1EBC6CE20AD8B3D4825CEB2E625E5C432A0E10");

vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2013-2186.reason", "Vulnerability CVE-2013-2186 is no problem because ...");
vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2013-2186.libraries", "*");

vulasConfiguration.setProperty(ExemptionBug.CFG_PREFIX + ".CVE-2019-1234.reason", "Vulnerability CVE-2019-1234 is no problem because ...");

vulasConfiguration.setProperty(ExemptionScope.CFG, "teST, provIDED");
vulasConfiguration.setProperty(ExemptionUnassessed.CFG, "knOWN");

Expand Down Expand Up @@ -158,7 +163,7 @@ public void testReport() {
assertTrue(FileUtil.isAccessibleFile(report_dir.resolve(Report.REPORT_FILE_JSON)));

// Validate Html
Tidy tidy = new Tidy();
final Tidy tidy = new Tidy();
tidy.parse(new ByteArrayInputStream(FileUtil.readInputStream(new FileInputStream(new File("./target/vulas/report/" + Report.REPORT_FILE_HTML)))), new FileOutputStream(new File("./target/jtidy-html.txt")));

// Allow no errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,9 @@ public void testGetAppVulnerabilities() throws Exception {

// Exempt bug
GoalExecution gexe = this.createExampleGoalExecution(app, GoalType.APP);
gexe.setConfiguration(this.createExampleProperties(PropertySource.GOAL_CONFIG, ExemptionBug.CFG_PREFIX + ".CVE-2015-5262.dig:16CF5A6B78951F50713D29BFAE3230A611DC01F0", "Lorem ipsum"));
final Collection<Property> props = this.createExampleProperties(PropertySource.GOAL_CONFIG, ExemptionBug.CFG_PREFIX + ".CVE-2015-5262.reason", "Lorem ipsum");
props.add(new Property(PropertySource.GOAL_CONFIG, ExemptionBug.CFG_PREFIX + ".CVE-2015-5262.libraries", "16CF5A6B78951F50713D29BFAE3230A611DC01F0, abc"));
gexe.setConfiguration(props);
this.gexeRepository.customSave(app, gexe);
//Get app vulnerabilies via http request
mockMvc.perform(get("/apps/" + APP_GROUP + "/" + APP_ARTIFACT + "/" + "0.0." + APP_VERSION + "/vulndeps?addExcemptionInfo=true")
Expand All @@ -740,7 +742,7 @@ public void testGetAppVulnerabilities() throws Exception {
.andExpect(content().contentType(contentTypeJson))
.andExpect(jsonPath("$[0].exempted", is(true)))
.andExpect(jsonPath("$[0].exemption.bugId", is("CVE-2015-5262")))
.andExpect(jsonPath("$[0].exemption.digest", is("dig:16CF5A6B78951F50713D29BFAE3230A611DC01F0")))
.andExpect(jsonPath("$[0].exemption.library", is("16CF5A6B78951F50713D29BFAE3230A611DC01F0")))
.andExpect(jsonPath("$[0].exemption.reason", is("Lorem ipsum")))
.andExpect(jsonPath("$[0].vulnDepOrigin", is("CC")));
}
Expand Down
5 changes: 5 additions & 0 deletions shared/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.github.package-url</groupId>
<artifactId>packageurl-java</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
*/
package com.sap.psr.vulas.shared.json.model;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sap.psr.vulas.shared.util.StringUtil;
import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;


/**
Expand Down Expand Up @@ -57,26 +57,26 @@ public class ExemptionBug implements IExemption {
private String bugId = null;

/** The digest of a library or star (*), which means that the exemption applies to all libraries. */
private String digest = null;
private String library = null;

private String reason = null;

/**
* Creates a new exemption, whereby parameters equal to null will be interpreted as star (*).
*
* @param _bug_id
* @param _digest
* @param _library
* @param _reason
*/
public ExemptionBug(String _bug_id, String _digest, String _reason) {
public ExemptionBug(String _bug_id, String _library, String _reason) {
this.bugId = (_bug_id==null ? ALL : _bug_id);
this.digest = (_digest==null ? ALL : _digest);
this.library = (_library==null ? ALL : _library);
this.reason = _reason;
}

public String getBugId() { return bugId; }

public String getDigest() { return digest; }
public String getLibrary() { return library; }

@Override
public String getReason() { return reason; }
Expand All @@ -89,17 +89,31 @@ public boolean isExempted(VulnerableDependency _vd) {
// Archive
if(is_exempted) {
// All
if(ALL.equals(this.digest)) { ; }
if(ALL.equals(this.library)) { ; }

// Digest
else if(this.digest.startsWith("dig:")) {
is_exempted = is_exempted && this.digest.substring(4).equals(_vd.getDep().getLib().getDigest());
// Package URL according to https://github.com/package-url/purl-spec
else if(this.library.startsWith("pkg:") && _vd.getDep().getLib().getLibraryId()!=null) {
try {
final LibraryId libid = _vd.getDep().getLib().getLibraryId();
final PackageURL purl = new PackageURL(this.library);
if(purl.getName()==null) {
log.error("The package URL [" + this.library + "] does not contain a name");
}
else {
is_exempted = is_exempted &&
(purl.getNamespace()==null || libid.getMvnGroup().equals(purl.getNamespace())) && // No purl.namespace || purl.namespace==libid.mvnGroup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we allow exemptions for library ids whose namespace (aka mvn group) is not specified?

Copy link
Contributor Author

@henrikplate henrikplate Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package URLs for Python packages will not have a namespace. Alternatively, we can consider the type, but such if-else conditions would not be nice either. Moreover, I do not know whether the prg. language can be easily determined at this point in time.

libid.getArtifact().equals(purl.getName()) &&
(purl.getVersion()==null || libid.getVersion().equals(purl.getVersion())); // No purl.version || purl.version==libid.version
}
} catch (MalformedPackageURLException e) {
log.error("Invalid package URL [" + this.library + "]: " + e.getMessage());
is_exempted = false;
}
}

//TODO: Support purl format to exempt findings: https://github.com/package-url/purl-spec
else if(this.digest.startsWith("pkg:")) {
log.warn("Purl format not yet supported");
is_exempted = false;
// Digest
else {
is_exempted = is_exempted && this.library.equals(_vd.getDep().getLib().getDigest());
}
}

Expand All @@ -117,17 +131,42 @@ public static ExemptionSet readFromConfiguration(Configuration _cfg) {
final ExemptionSet exempts = new ExemptionSet();

// New format
Iterator<String> iter = _cfg.subset(CFG_PREFIX).getKeys();
final Iterator<String> iter = _cfg.getKeys(CFG_PREFIX);
while(iter.hasNext()) {
final String k = iter.next();
if(!k.equals("")) {
if(k.endsWith("." + "reason")) {
final String[] key_elements = k.split("\\.");
final int l = key_elements.length;
if(l<2) {
log.error("Exemption with key [" + CFG_PREFIX + "." + k + "] has less than 2 elements");
if(key_elements.length == 5) {
final String vuln = key_elements[3];
final String reason = _cfg.getString(CFG_PREFIX + "." + vuln + "." + "reason");
final String[] libs = _cfg.getStringArray(CFG_PREFIX + "." + vuln + "." + "libraries");

if(libs==null || libs.length==0) {
exempts.add(new ExemptionBug(vuln, ExemptionBug.ALL, reason));
}
else {
for(String lib: libs) {
if(lib.startsWith("pkg:")) {
try {
final PackageURL purl = new PackageURL(lib);
if(purl.getName()==null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is linked to the previous comment, why not requiring that also the namespace is not null?

log.error("The package URL [" + lib + "] does not contain a name");
continue;
}
exempts.add(new ExemptionBug(vuln, lib, reason));
} catch(MalformedPackageURLException e) {
log.error("Invalid package URL [" + lib + "]: " + e.getMessage());
continue;
}
}
else {
exempts.add(new ExemptionBug(vuln, lib, reason));
}
}
}
}
else {
exempts.add(new ExemptionBug(StringUtil.join(Arrays.copyOfRange(key_elements, 0, l-1), "."), key_elements[l-1], _cfg.getString(CFG_PREFIX + "." + k)));
log.error("Invalid exemption format [" + CFG_PREFIX + "." + k + "]");
}
}
}
Expand Down Expand Up @@ -157,14 +196,38 @@ public static ExemptionSet readFromConfiguration(Map<String, String> _map) {

// New format
for(String k: _map.keySet()) {
if(k.startsWith((CFG_PREFIX) + ".")) {
final String[] key_elements = k.substring(CFG_PREFIX.length() + 1).split("\\.");
final int l = key_elements.length;
if(l<2) {
log.error("Exemption with key [" + k + "] has less than 2 elements");
if(k.startsWith((CFG_PREFIX) + ".") && k.endsWith("." + "reason")) {
final String[] key_elements = k.split("\\.");
if(key_elements.length == 5) {
final String vuln = key_elements[3];
final String reason = _map.get(CFG_PREFIX + "." + vuln + "." + "reason");
final String[] libs = _map.get(CFG_PREFIX + "." + vuln + "." + "libraries").split(",");

if(libs==null || libs.length==0) {
exempts.add(new ExemptionBug(vuln, ExemptionBug.ALL, reason));
}
else {
for(String lib: libs) {
if(lib.startsWith("pkg:")) {
try {
final PackageURL purl = new PackageURL(lib);
if(purl.getName()==null) {
log.error("The package URL [" + lib + "] does not contain a name");
continue;
}
exempts.add(new ExemptionBug(vuln, lib, reason));
} catch(MalformedPackageURLException e) {
log.error("Invalid package URL [" + lib + "]: " + e.getMessage());
}
}
else {
exempts.add(new ExemptionBug(vuln, lib, reason));
}
}
}
}
else {
exempts.add(new ExemptionBug(StringUtil.join(Arrays.copyOfRange(key_elements, 0, l-1), "."), key_elements[l-1], _map.get(k)));
log.error("Invalid exemption format: [" + CFG_PREFIX + "." + k + "]");
}
}
}
Expand Down Expand Up @@ -204,15 +267,15 @@ public String toString() {
}

public String toShortString() {
return this.bugId + "." + this.digest;
return this.bugId + "." + this.library;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bugId == null) ? 0 : bugId.hashCode());
result = prime * result + ((digest == null) ? 0 : digest.hashCode());
result = prime * result + ((library == null) ? 0 : library.hashCode());
result = prime * result + ((reason == null) ? 0 : reason.hashCode());
return result;
}
Expand All @@ -231,10 +294,10 @@ public boolean equals(Object obj) {
return false;
} else if (!bugId.equals(other.bugId))
return false;
if (digest == null) {
if (other.digest != null)
if (library == null) {
if (other.library != null)
return false;
} else if (!digest.equals(other.digest))
} else if (!library.equals(other.library))
return false;
if (reason == null) {
if (other.reason != null)
Expand Down
Loading