From bcbddff1d965ba5d08f102ae1351a7570789fc2d Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 10 Jun 2022 14:09:48 +0100 Subject: [PATCH 01/26] draft for using google OSV Signed-off-by: Sahiba Mittal --- .../event/EventSubsystemInitializer.java | 2 + .../event/GoogleOSVMirrorEvent.java | 10 + .../model/ConfigPropertyConstants.java | 1 + .../dependencytrack/model/Vulnerability.java | 3 +- .../graphql/GitHubSecurityAdvisoryParser.java | 16 +- .../parser/osv/GoogleOSVAdvisoryParser.java | 78 +++++++ .../parser/osv/model/OSVAdvisory.java | 146 +++++++++++++ .../parser/osv/model/OSVVulnerability.java | 74 +++++++ .../persistence/QueryManager.java | 4 + .../tasks/OSVDownloadTask.java | 199 ++++++++++++++++++ .../dependencytrack/tasks/TaskScheduler.java | 4 + .../org/dependencytrack/util/JsonUtil.java | 13 ++ .../task/OSVDownloadTaskTest.java | 37 ++++ 13 files changed, 572 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java create mode 100644 src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java create mode 100644 src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java create mode 100644 src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java create mode 100644 src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java create mode 100644 src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java diff --git a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java index e41c2c493a..4f23e3dfd7 100644 --- a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java +++ b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java @@ -31,6 +31,7 @@ import org.dependencytrack.tasks.EpssMirrorTask; import org.dependencytrack.tasks.FortifySscUploadTask; import org.dependencytrack.tasks.GitHubAdvisoryMirrorTask; +import org.dependencytrack.tasks.OSVDownloadTask; import org.dependencytrack.tasks.IndexTask; import org.dependencytrack.tasks.InternalComponentIdentificationTask; import org.dependencytrack.tasks.KennaSecurityUploadTask; @@ -80,6 +81,7 @@ public void contextInitialized(final ServletContextEvent event) { EVENT_SERVICE.subscribe(InternalAnalysisEvent.class, InternalAnalysisTask.class); EVENT_SERVICE.subscribe(OssIndexAnalysisEvent.class, OssIndexAnalysisTask.class); EVENT_SERVICE.subscribe(GitHubAdvisoryMirrorEvent.class, GitHubAdvisoryMirrorTask.class); + EVENT_SERVICE.subscribe(GoogleOSVMirrorEvent.class, OSVDownloadTask.class); EVENT_SERVICE.subscribe(VulnDbSyncEvent.class, VulnDbSyncTask.class); EVENT_SERVICE.subscribe(VulnDbAnalysisEvent.class, VulnDbAnalysisTask.class); EVENT_SERVICE.subscribe(VulnerabilityAnalysisEvent.class, VulnerabilityAnalysisTask.class); diff --git a/src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java b/src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java new file mode 100644 index 0000000000..bb3c347fb3 --- /dev/null +++ b/src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java @@ -0,0 +1,10 @@ +package org.dependencytrack.event; + +import alpine.event.framework.Event; + +/** + * Defines an event used to start a mirror of Google OSV. + */ +public class GoogleOSVMirrorEvent implements Event { + +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java index 58d2730db3..cfec529f88 100644 --- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java +++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java @@ -48,6 +48,7 @@ public enum ConfigPropertyConstants { VULNERABILITY_SOURCE_NVD_FEEDS_URL("vuln-source", "nvd.feeds.url", "https://nvd.nist.gov/feeds", PropertyType.URL, "A base URL pointing to the hostname and path of the NVD feeds"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ENABLED("vuln-source", "github.advisories.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable GitHub Advisories"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ACCESS_TOKEN("vuln-source", "github.advisories.access.token", null, PropertyType.STRING, "The access token used for GitHub API authentication"), + VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable Google OSV"), VULNERABILITY_SOURCE_EPSS_ENABLED("vuln-source", "epss.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Exploit Prediction Scoring System"), VULNERABILITY_SOURCE_EPSS_FEEDS_URL("vuln-source", "epss.feeds.url", "https://epss.cyentia.com", PropertyType.URL, "A base URL pointing to the hostname and path of the EPSS feeds"), ACCEPT_ARTIFACT_CYCLONEDX("artifact", "cyclonedx.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable the systems ability to accept CycloneDX uploads"), diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java index 486332a9c0..f0f6be7121 100644 --- a/src/main/java/org/dependencytrack/model/Vulnerability.java +++ b/src/main/java/org/dependencytrack/model/Vulnerability.java @@ -95,7 +95,8 @@ public enum Source { VULNDB, // VulnDB from Risk Based Security OSSINDEX, // Sonatype OSS Index RETIREJS, // Retire.js - INTERNAL // Internally-managed (and manually entered) vulnerability + INTERNAL, // Internally-managed (and manually entered) vulnerability + GOOGLE // Google OSV Advisories } @PrimaryKey diff --git a/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java index 7f0dc869a5..79667434b4 100644 --- a/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java @@ -24,12 +24,11 @@ import org.dependencytrack.parser.github.graphql.model.GitHubSecurityAdvisory; import org.dependencytrack.parser.github.graphql.model.GitHubVulnerability; import org.dependencytrack.parser.github.graphql.model.PageableList; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; +import static org.dependencytrack.util.JsonUtil.jsonStringToTimestamp; + public class GitHubSecurityAdvisoryParser { public PageableList parse(final JSONObject object) { @@ -162,15 +161,4 @@ private GitHubVulnerability parseVulnerability(final JSONObject object) { } return vulnerability; } - - private ZonedDateTime jsonStringToTimestamp(final String s) { - if (s == null) { - return null; - } - try { - return ZonedDateTime.parse(s); - } catch (DateTimeParseException e) { - return null; - } - } } diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java new file mode 100644 index 0000000000..12e01faacc --- /dev/null +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -0,0 +1,78 @@ +package org.dependencytrack.parser.osv; + +import kong.unirest.json.JSONArray; +import kong.unirest.json.JSONObject; +import org.dependencytrack.parser.osv.model.OSVAdvisory; +import org.dependencytrack.parser.osv.model.OSVVulnerability; + +import static org.dependencytrack.util.JsonUtil.jsonStringToTimestamp; + +/* + Parser for Google OSV, an aggregator of vulnerability databases including GitHub Security Advisories, PyPA, RustSec, and Global Security Database, and more. + */ +public class GoogleOSVAdvisoryParser { + + public OSVAdvisory parse(final JSONObject object) { + + final OSVAdvisory advisory = new OSVAdvisory(); + if(object != null) { + advisory.setId(object.optString("id", null)); + advisory.setSummary(object.optString("summary", null)); + advisory.setDetails(object.optString("details", null)); + advisory.setPublished(jsonStringToTimestamp(object.optString("published", null))); + advisory.setModified(jsonStringToTimestamp(object.optString("modified", null))); + advisory.setSchema_version(object.optString("schema_version", null)); + + final JSONArray references = object.optJSONArray("references"); + if (references != null) { + for (int i=0; i aliases; + + private ZonedDateTime modified; + + private ZonedDateTime published; + + private List cweIds; + + private List references; + + private String schema_version; + + private List vulnerabilities; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public List getCweIds() { + return cweIds; + } + + public void addCweId(String cweId) { + if (cweId == null) { + cweIds = new ArrayList<>(); + } + cweIds.add(cweId); + } + + public void setCweIds(List cweIds) { + this.cweIds = cweIds; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public List getAliases() { + return aliases; + } + + public void addAlias(String alias) { + if (alias == null) { + aliases = new ArrayList<>(); + } + aliases.add(alias); + } + + public void setAliases(List aliases) { + this.aliases = aliases; + } + + public ZonedDateTime getModified() { + return modified; + } + + public void setModified(ZonedDateTime modified) { + this.modified = modified; + } + + public ZonedDateTime getPublished() { + return published; + } + + public void setPublished(ZonedDateTime published) { + this.published = published; + } + + public List getReferences() { + return references; + } + + public void addReference(String reference) { + if (this.references == null) { + this.references = new ArrayList<>(); + } + this.references.add(reference); + } + + public void setReferences(List references) { + this.references = references; + } + + public String getSchema_version() { + return schema_version; + } + + public void setSchema_version(String schema_version) { + this.schema_version = schema_version; + } + + public List getVulnerabilities() { + return vulnerabilities; + } + + public void addVulnerability(OSVVulnerability vulnerability) { + if (this.vulnerabilities == null) { + this.vulnerabilities = new ArrayList<>(); + } + this.vulnerabilities.add(vulnerability); + } + + public void setVulnerabilities(List vulnerabilities) { + this.vulnerabilities = vulnerabilities; + } + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java new file mode 100644 index 0000000000..e98663a4ba --- /dev/null +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java @@ -0,0 +1,74 @@ +package org.dependencytrack.parser.osv.model; + +import java.util.ArrayList; +import java.util.List; + +public class OSVVulnerability { + + private List versions; + + private String packageName; + + private String packageEcosystem; + + private String purl; + + private String vulnerableVersionRange; + + private String databaseSpecificSource; + + public List getVersions() { + return versions; + } + + public void addVersion(String version) { + if (version == null) { + versions = new ArrayList<>(); + } + versions.add(version); + } + + public void setVersions(List versions) { + this.versions = versions; + } + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getPackageEcosystem() { + return packageEcosystem; + } + + public void setPackageEcosystem(String packageEcosystem) { + this.packageEcosystem = packageEcosystem; + } + + public String getPurl() { + return purl; + } + + public void setPurl(String purl) { + this.purl = purl; + } + + public String getVulnerableVersionRange() { + return vulnerableVersionRange; + } + + public void setVulnerableVersionRange(String vulnerableVersionRange) { + this.vulnerableVersionRange = vulnerableVersionRange; + } + + public String getDatabaseSpecificSource() { + return databaseSpecificSource; + } + + public void setDatabaseSpecificSource(String databaseSpecificSource) { + this.databaseSpecificSource = databaseSpecificSource; + } +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 0b065d4b84..56cea91e89 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -724,6 +724,10 @@ public List getAllVulnerableSoftwareByPurl(final PackageURL return getVulnerableSoftwareQueryManager().getAllVulnerableSoftwareByPurl(purl); } + public VulnerableSoftware getVulnerableSoftwareByPurl(final String purl) { + return getVulnerableSoftwareQueryManager().getVulnerableSoftwareByPurl(purl); + } + public List getAllVulnerableSoftware(final String cpePart, final String cpeVendor, final String cpeProduct, final String cpeVersion, final PackageURL purl) { return getVulnerableSoftwareQueryManager().getAllVulnerableSoftware(cpePart, cpeVendor, cpeProduct, cpeVersion, purl); } diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java new file mode 100644 index 0000000000..b385a3d707 --- /dev/null +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -0,0 +1,199 @@ +package org.dependencytrack.tasks; + +import alpine.common.logging.Logger; +import alpine.event.framework.Event; +import alpine.event.framework.LoggableSubscriber; +import alpine.model.ConfigProperty; +import kong.unirest.json.JSONObject; +import org.dependencytrack.event.GoogleOSVMirrorEvent; +import org.dependencytrack.event.IndexEvent; +import org.dependencytrack.model.Cwe; +import org.dependencytrack.model.Severity; +import org.dependencytrack.model.Vulnerability; +import org.dependencytrack.model.VulnerableSoftware; +import org.dependencytrack.parser.common.resolver.CweResolver; +import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; +import org.dependencytrack.parser.osv.model.OSVAdvisory; +import org.dependencytrack.parser.osv.model.OSVVulnerability; +import org.dependencytrack.persistence.QueryManager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static org.dependencytrack.model.ConfigPropertyConstants.VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED; + +public class OSVDownloadTask implements LoggableSubscriber { + + private static final Logger LOGGER = Logger.getLogger(OSVDownloadTask.class); + private final boolean isEnabled; + + public OSVDownloadTask() { + try (final QueryManager qm = new QueryManager()) { + final ConfigProperty enabled = qm.getConfigProperty(VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getGroupName(), VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getPropertyName()); + this.isEnabled = enabled != null && Boolean.valueOf(enabled.getPropertyValue()); + } + } + + @Override + public void inform(Event e) { + + if (e instanceof GoogleOSVMirrorEvent && this.isEnabled) { + + try { + // TODO: Below functionality is being run for one ecosystem for example, it will be run for all ecosystems + URL url = new URL("https://osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip"); + ZipInputStream zipIn = new ZipInputStream(url.openStream()); + unzipFolder(zipIn); + zipIn.closeEntry(); + } catch (IOException exception) { + exception.printStackTrace(); + } + } + } + + private void unzipFolder(ZipInputStream zipIn) throws IOException { + + BufferedReader reader; + GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + try { + ZipEntry zipEntry = zipIn.getNextEntry(); + while (zipEntry != null) { + + reader = new BufferedReader(new InputStreamReader(zipIn)); + String line = null; + StringBuilder out = new StringBuilder(); + while ((line = reader.readLine()) != null) { + out.append(line); + } + JSONObject json = new JSONObject(out.toString()); + System.out.println(json); + final OSVAdvisory osvAdvisory = parser.parse(json); + updateDatasource(osvAdvisory); + zipEntry = zipIn.getNextEntry(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void updateDatasource(final OSVAdvisory advisory) { + LOGGER.info("Updating datasource with Google OSV advisories"); + try (QueryManager qm = new QueryManager()) { + + LOGGER.debug("Synchronizing Google OSV advisory: " + advisory.getId()); + final Vulnerability synchronizedVulnerability = qm.synchronizeVulnerability(mapAdvisoryToVulnerability(qm, advisory), false); + final List vsList = new ArrayList<>(); + for (OSVVulnerability osvVulnerability: advisory.getVulnerabilities()) { + VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); + if (vs != null) { + vsList.add(vs); + } + } + LOGGER.debug("Updating vulnerable software for OSV advisory: " + advisory.getId()); + qm.persist(vsList); + synchronizedVulnerability.setVulnerableSoftware(vsList); + qm.persist(synchronizedVulnerability); + } + Event.dispatch(new IndexEvent(IndexEvent.Action.COMMIT, Vulnerability.class)); + } + + private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSVAdvisory advisory) { + + final Vulnerability vuln = new Vulnerability(); + vuln.setSource(Vulnerability.Source.GOOGLE); + vuln.setVulnId(String.valueOf(advisory.getId())); + vuln.setTitle(advisory.getSummary()); + vuln.setDescription(advisory.getDetails()); + vuln.setPublished(Date.from(advisory.getPublished().toInstant())); + vuln.setUpdated(Date.from(advisory.getModified().toInstant())); + + if (advisory.getReferences() != null && advisory.getReferences().size() > 0) { + final StringBuilder sb = new StringBuilder(); + for (String ref : advisory.getReferences()) { + sb.append("* [").append(ref).append("](").append(ref).append(")\n"); + } + vuln.setReferences(sb.toString()); + } + + if (advisory.getCweIds() != null) { + for (int i=0; i versions = vuln.getVersions(); + String versionStartIncluding = null; + String versionEndIncluding = null; + if (versions != null) { + versionStartIncluding = versions.get(0); + versionEndIncluding = versions.get(versions.size() - 1); + } + final String purl = vuln.getPurl(); + if (purl == null) return null; + + VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(purl); + if (vs != null) { + return vs; + } + vs = new VulnerableSoftware(); + vs.setVulnerable(true); + vs.setPurlType(vuln.getPackageEcosystem()); + vs.setPurlNamespace(vuln.getPackageName()); + vs.setPurlName(purl); + vs.setVersionStartIncluding(versionStartIncluding); + vs.setVersionEndIncluding(versionEndIncluding); + return vs; + } + +// WAY 2 : to use google storage library instead of downloading data from https + +// private void downloadFromStorage() { +// +// String bucketName = "osv-vulnerabilities"; +// +// Storage storage = StorageOptions.getUnauthenticatedInstance().getService(); +// Page blobs = storage.list(bucketName); +// +// for (Blob blob : blobs.iterateAll()) { +// System.out.println(blob.getName()); +// } +// Blob blob = blobs.getValues().iterator().next(); +// System.out.println(blob); +// if (blob.getMetadata() != null) { +// System.out.println("\n\n\nUser metadata:"); +// for (Map.Entry userMetadata : blob.getMetadata().entrySet()) { +// System.out.println(userMetadata.getKey() + "=" + userMetadata.getValue()); +// } +// } +// } +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/tasks/TaskScheduler.java b/src/main/java/org/dependencytrack/tasks/TaskScheduler.java index 0a2d341a49..c55bf2feef 100644 --- a/src/main/java/org/dependencytrack/tasks/TaskScheduler.java +++ b/src/main/java/org/dependencytrack/tasks/TaskScheduler.java @@ -27,6 +27,7 @@ import org.dependencytrack.event.DefectDojoUploadEventAbstract; import org.dependencytrack.event.FortifySscUploadEventAbstract; import org.dependencytrack.event.GitHubAdvisoryMirrorEvent; +import org.dependencytrack.event.GoogleOSVMirrorEvent; import org.dependencytrack.event.InternalComponentIdentificationEvent; import org.dependencytrack.event.KennaSecurityUploadEventAbstract; import org.dependencytrack.event.MetricsUpdateEvent; @@ -61,6 +62,9 @@ private TaskScheduler() { // Creates a new event that executes every 24 hours (86400000) after an initial 10 second (10000) delay scheduleEvent(new GitHubAdvisoryMirrorEvent(), 10000, 86400000); + // Creates a new event that executes every 24 hours (86400000) after an initial 10 second (10000) delay + scheduleEvent(new GoogleOSVMirrorEvent(), 10000, 86400000); + // Creates a new event that executes every 24 hours (86400000) after an initial 1 minute (60000) delay scheduleEvent(new NistMirrorEvent(), 60000, 86400000); diff --git a/src/main/java/org/dependencytrack/util/JsonUtil.java b/src/main/java/org/dependencytrack/util/JsonUtil.java index 7eaf10aa27..155e839f4d 100644 --- a/src/main/java/org/dependencytrack/util/JsonUtil.java +++ b/src/main/java/org/dependencytrack/util/JsonUtil.java @@ -21,6 +21,8 @@ import javax.json.JsonObjectBuilder; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; public final class JsonUtil { @@ -57,4 +59,15 @@ public static JsonObjectBuilder add(final JsonObjectBuilder builder, final Strin return builder; } + public static ZonedDateTime jsonStringToTimestamp(final String s) { + if (s == null) { + return null; + } + try { + return ZonedDateTime.parse(s); + } catch (DateTimeParseException e) { + return null; + } + } + } diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java new file mode 100644 index 0000000000..c37ded0e5d --- /dev/null +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 OWASP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dependencytrack.task; + +import org.dependencytrack.PersistenceCapableTest; +import org.dependencytrack.event.GoogleOSVMirrorEvent; +import org.dependencytrack.model.ConfigPropertyConstants; +import org.dependencytrack.tasks.OSVDownloadTask; +import org.junit.Test; + +public class OSVDownloadTaskTest extends PersistenceCapableTest { + + @Test + public void informTest() { + final var task = new OSVDownloadTask(); + final var event = new GoogleOSVMirrorEvent(); + final var acceptArtifactProp = ConfigPropertyConstants.VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED; + qm.createConfigProperty(acceptArtifactProp.getGroupName(), acceptArtifactProp.getPropertyName(), "true", acceptArtifactProp.getPropertyType(), acceptArtifactProp.getDescription()); + + // TODO : add assertions on QueryManager database + // Commenting task below to avoid database write + // task.inform(event); + } +} \ No newline at end of file From ee624bc73338c34cac3eca16023673d64e7bdee3 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 10 Jun 2022 15:32:53 +0100 Subject: [PATCH 02/26] ConfigProperties test fix Signed-off-by: Sahiba Mittal --- .../dependencytrack/persistence/DefaultObjectGeneratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java b/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java index fc030cc49e..e378971287 100644 --- a/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java +++ b/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java @@ -78,7 +78,7 @@ public void testLoadDefaultConfigProperties() throws Exception { Method method = generator.getClass().getDeclaredMethod("loadDefaultConfigProperties"); method.setAccessible(true); method.invoke(generator); - Assert.assertEquals(42, qm.getConfigProperties().size()); + Assert.assertEquals(43, qm.getConfigProperties().size()); } @Test From 594af37621a6b7586038fce99f0a934e7d3f6251 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 14 Jun 2022 16:21:51 +0100 Subject: [PATCH 03/26] Vulnerability mapping done Signed-off-by: Sahiba Mittal --- .../graphql/GitHubSecurityAdvisoryParser.java | 2 +- .../parser/osv/GoogleOSVAdvisoryParser.java | 83 ++++++++++++++----- .../parser/osv/model/Ecosystem.java | 31 +++++++ .../parser/osv/model/OSVAdvisory.java | 27 ++++-- .../parser/osv/model/OSVVulnerability.java | 44 +++------- .../persistence/QueryManager.java | 8 +- .../VulnerableSoftwareQueryManager.java | 10 +++ .../tasks/OSVDownloadTask.java | 51 +++--------- 8 files changed, 153 insertions(+), 103 deletions(-) create mode 100644 src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java diff --git a/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java index 79667434b4..a06ce36b82 100644 --- a/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/github/graphql/GitHubSecurityAdvisoryParser.java @@ -101,7 +101,7 @@ private GitHubSecurityAdvisory parseSecurityAdvisory(final JSONObject object) { final JSONObject cvss = object.optJSONObject("cvss"); if (cvss != null) { advisory.setCvssScore(cvss.optInt("score", 0)); - advisory.setCvssVector(cvss.optString("vectorString", null)); + advisory.setCvssVector(cvss.optString("score", null)); } final JSONObject cwes = object.optJSONObject("cwes"); diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 12e01faacc..4e0ce11e63 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -5,6 +5,9 @@ import org.dependencytrack.parser.osv.model.OSVAdvisory; import org.dependencytrack.parser.osv.model.OSVVulnerability; +import java.util.ArrayList; +import java.util.List; + import static org.dependencytrack.util.JsonUtil.jsonStringToTimestamp; /* @@ -50,29 +53,71 @@ public OSVAdvisory parse(final JSONObject object) { } } - final JSONArray vulnerabilities = object.optJSONArray("affected"); - if (vulnerabilities != null) { - for(int i=0; i vulnerabilities = parseVulnerabilities(object); + advisory.setVulnerabilities(vulnerabilities); + } + return advisory; + } + + private List parseVulnerabilities(JSONObject object) { + + List osvVulnerabilityList = new ArrayList<>(); + final JSONArray vulnerabilities = object.optJSONArray("affected"); + if (vulnerabilities != null) { + for(int i=0; i parseVersionRanges(JSONObject affectedPackageJson, JSONObject range) { + + final List osvVulnerabilityList = new ArrayList<>(); + final JSONArray rangeEvents = range.optJSONArray("events"); + if(rangeEvents != null) { + int k = 0; + while (k < rangeEvents.length()) { + + OSVVulnerability osvVulnerability = new OSVVulnerability(); + osvVulnerability.setPackageName(affectedPackageJson.optString("name", null)); + osvVulnerability.setPackageEcosystem(affectedPackageJson.optString("ecosystem", null)); + osvVulnerability.setPurl(affectedPackageJson.optString("purl", null)); + + JSONObject event = rangeEvents.getJSONObject(k); + String lower = event.optString("introduced", null); + if(lower != null) { + osvVulnerability.setLowerVersionRange(lower); + k += 1; + } + event = rangeEvents.getJSONObject(k); + String upper = event.optString("fixed", null); + if(upper != null) { + osvVulnerability.setUpperVersionRange(upper); + k += 1; + } + osvVulnerabilityList.add(osvVulnerability); + } + } + return osvVulnerabilityList; } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java b/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java new file mode 100644 index 0000000000..5162f062ab --- /dev/null +++ b/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java @@ -0,0 +1,31 @@ +package org.dependencytrack.parser.osv.model; + +public enum Ecosystem { + + ANDROID("Android"), + GSD("GSD"), + GO("Go"), + HEX("Hex"), + JAVASCRIPT("JavaScript"), + LINUX("Linux"), + MAVEN("Maven"), + NUGET("NuGet"), + OSSFUZZ("OSS-Fuzz"), + PACKAGIST("Packagist"), + PYPI("PyPI"), + RUBYGEMS("RubyGems"), + UVI("UVI"), + CRATES("crates.io"), + NPM("npm"), + DWF("DWF"); + + private final String value; + + Ecosystem(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java index 8c2cef333d..c28582b4e0 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java @@ -28,6 +28,10 @@ public class OSVAdvisory { private List vulnerabilities; + private double cvssScore; + + private String cvssVector; + public String getId() { return id; } @@ -125,13 +129,6 @@ public List getVulnerabilities() { return vulnerabilities; } - public void addVulnerability(OSVVulnerability vulnerability) { - if (this.vulnerabilities == null) { - this.vulnerabilities = new ArrayList<>(); - } - this.vulnerabilities.add(vulnerability); - } - public void setVulnerabilities(List vulnerabilities) { this.vulnerabilities = vulnerabilities; } @@ -143,4 +140,20 @@ public String getSeverity() { public void setSeverity(String severity) { this.severity = severity; } + + public double getCvssScore() { + return cvssScore; + } + + public void setCvssScore(double cvssScore) { + this.cvssScore = cvssScore; + } + + public String getCvssVector() { + return cvssVector; + } + + public void setCvssVector(String cvssVector) { + this.cvssVector = cvssVector; + } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java index e98663a4ba..1de84802f5 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java @@ -1,36 +1,15 @@ package org.dependencytrack.parser.osv.model; -import java.util.ArrayList; -import java.util.List; - public class OSVVulnerability { - - private List versions; - private String packageName; private String packageEcosystem; private String purl; - private String vulnerableVersionRange; - - private String databaseSpecificSource; - - public List getVersions() { - return versions; - } - - public void addVersion(String version) { - if (version == null) { - versions = new ArrayList<>(); - } - versions.add(version); - } + private String lowerVersionRange; - public void setVersions(List versions) { - this.versions = versions; - } + private String upperVersionRange; public String getPackageName() { return packageName; @@ -56,19 +35,16 @@ public void setPurl(String purl) { this.purl = purl; } - public String getVulnerableVersionRange() { - return vulnerableVersionRange; + public String getLowerVersionRange() { + return lowerVersionRange; } - - public void setVulnerableVersionRange(String vulnerableVersionRange) { - this.vulnerableVersionRange = vulnerableVersionRange; + public void setLowerVersionRange(String lowerVersionRange) { + this.lowerVersionRange = lowerVersionRange; } - - public String getDatabaseSpecificSource() { - return databaseSpecificSource; + public String getUpperVersionRange() { + return upperVersionRange; } - - public void setDatabaseSpecificSource(String databaseSpecificSource) { - this.databaseSpecificSource = databaseSpecificSource; + public void setUpperVersionRange(String upperVersionRange) { + this.upperVersionRange = upperVersionRange; } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 56cea91e89..4a4aefd981 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -724,10 +724,6 @@ public List getAllVulnerableSoftwareByPurl(final PackageURL return getVulnerableSoftwareQueryManager().getAllVulnerableSoftwareByPurl(purl); } - public VulnerableSoftware getVulnerableSoftwareByPurl(final String purl) { - return getVulnerableSoftwareQueryManager().getVulnerableSoftwareByPurl(purl); - } - public List getAllVulnerableSoftware(final String cpePart, final String cpeVendor, final String cpeProduct, final String cpeVersion, final PackageURL purl) { return getVulnerableSoftwareQueryManager().getAllVulnerableSoftware(cpePart, cpeVendor, cpeProduct, cpeVersion, purl); } @@ -1109,4 +1105,8 @@ public boolean hasAccessManagementPermission(final ApiKey apiKey) { public PaginatedResult getTags(String policyUuid) { return getTagQueryManager().getTags(policyUuid); } + + public VulnerableSoftware getVulnerableSoftwareByPurl(String purl, String versionEndExcluding, String versionStartIncluding) { + return getVulnerableSoftwareQueryManager().getVulnerableSoftwareByPurl(purl, versionEndExcluding, versionStartIncluding); + } } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java index a93edbaa5c..3de8fa06f0 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java @@ -168,6 +168,16 @@ public VulnerableSoftware getVulnerableSoftwareByPurl(String purlType, String pu return singleResult(query.executeWithArray(purlType, purlNamespace, purlName, versionEndExcluding, versionEndIncluding, versionStartExcluding, versionStartIncluding)); } + /** + * Returns a List of all VulnerableSoftware objects that match the specified PackageURL + * @return a List of matching VulnerableSoftware objects + */ + @SuppressWarnings("unchecked") + public VulnerableSoftware getVulnerableSoftwareByPurl(String purl, String versionEndExcluding, String versionStartIncluding) { + final Query query = pm.newQuery(VulnerableSoftware.class, "purl == :purl && versionEndExcluding == :versionEndExcluding && versionStartIncluding == :versionStartIncluding"); + return singleResult(query.executeWithArray(purl, versionEndExcluding, versionStartIncluding)); + } + /** * Returns a List of all VulnerableSoftware objects that match the specified PackageURL * @return a List of matching VulnerableSoftware objects diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index b385a3d707..0540d77d62 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -13,6 +13,7 @@ import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.parser.common.resolver.CweResolver; import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; +import org.dependencytrack.parser.osv.model.Ecosystem; import org.dependencytrack.parser.osv.model.OSVAdvisory; import org.dependencytrack.parser.osv.model.OSVVulnerability; import org.dependencytrack.persistence.QueryManager; @@ -47,11 +48,12 @@ public void inform(Event e) { if (e instanceof GoogleOSVMirrorEvent && this.isEnabled) { try { - // TODO: Below functionality is being run for one ecosystem for example, it will be run for all ecosystems - URL url = new URL("https://osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip"); - ZipInputStream zipIn = new ZipInputStream(url.openStream()); - unzipFolder(zipIn); - zipIn.closeEntry(); + for (Ecosystem ecosystem : Ecosystem.values()) { + URL url = new URL("https://osv-vulnerabilities.storage.googleapis.com/"+ ecosystem.getValue() +"/all.zip"); + ZipInputStream zipIn = new ZipInputStream(url.openStream()); + unzipFolder(zipIn); + zipIn.closeEntry(); + } } catch (IOException exception) { exception.printStackTrace(); } @@ -151,49 +153,22 @@ private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OS private VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { - List versions = vuln.getVersions(); - String versionStartIncluding = null; - String versionEndIncluding = null; - if (versions != null) { - versionStartIncluding = versions.get(0); - versionEndIncluding = versions.get(versions.size() - 1); - } + String versionStartIncluding = vuln.getLowerVersionRange(); + String versionEndExcluding = vuln.getUpperVersionRange(); + final String purl = vuln.getPurl(); if (purl == null) return null; - VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(purl); + VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(vuln.getPurl(), versionEndExcluding, versionStartIncluding); if (vs != null) { return vs; } vs = new VulnerableSoftware(); vs.setVulnerable(true); vs.setPurlType(vuln.getPackageEcosystem()); - vs.setPurlNamespace(vuln.getPackageName()); - vs.setPurlName(purl); + vs.setPurl(vuln.getPurl()); vs.setVersionStartIncluding(versionStartIncluding); - vs.setVersionEndIncluding(versionEndIncluding); + vs.setVersionEndExcluding(versionEndExcluding); return vs; } - -// WAY 2 : to use google storage library instead of downloading data from https - -// private void downloadFromStorage() { -// -// String bucketName = "osv-vulnerabilities"; -// -// Storage storage = StorageOptions.getUnauthenticatedInstance().getService(); -// Page blobs = storage.list(bucketName); -// -// for (Blob blob : blobs.iterateAll()) { -// System.out.println(blob.getName()); -// } -// Blob blob = blobs.getValues().iterator().next(); -// System.out.println(blob); -// if (blob.getMetadata() != null) { -// System.out.println("\n\n\nUser metadata:"); -// for (Map.Entry userMetadata : blob.getMetadata().entrySet()) { -// System.out.println(userMetadata.getKey() + "=" + userMetadata.getValue()); -// } -// } -// } } \ No newline at end of file From 7b6ea43c765dfff1b24334ac03892417d65427fd Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 15 Jun 2022 15:54:46 +0100 Subject: [PATCH 04/26] unit test for osv task Signed-off-by: Sahiba Mittal --- .../event/EventSubsystemInitializer.java | 1 + .../parser/osv/GoogleOSVAdvisoryParser.java | 1 - .../parser/osv/model/Ecosystem.java | 4 +- .../tasks/OSVDownloadTask.java | 2 +- .../task/OSVDownloadTaskTest.java | 56 ++- .../https---osv-GHSA-77rv-6vfw-x4gc.json | 318 ++++++++++++++++++ 6 files changed, 367 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json diff --git a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java index 4f23e3dfd7..809a651a0b 100644 --- a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java +++ b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java @@ -116,6 +116,7 @@ public void contextDestroyed(final ServletContextEvent event) { EVENT_SERVICE.unsubscribe(InternalAnalysisTask.class); EVENT_SERVICE.unsubscribe(OssIndexAnalysisTask.class); EVENT_SERVICE.unsubscribe(GitHubAdvisoryMirrorTask.class); + EVENT_SERVICE.unsubscribe(OSVDownloadTask.class); EVENT_SERVICE.unsubscribe(VulnDbSyncTask.class); EVENT_SERVICE.unsubscribe(VulnDbAnalysisTask.class); EVENT_SERVICE.unsubscribe(VulnerabilityAnalysisTask.class); diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 4e0ce11e63..3dcfbc4e18 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -56,7 +56,6 @@ public OSVAdvisory parse(final JSONObject object) { final JSONObject cvss = object.optJSONObject("severity"); if (cvss != null) { advisory.setCvssVector(cvss.optString("vectorString", null)); - // TODO: add cvss score calculation if required } final List vulnerabilities = parseVulnerabilities(object); diff --git a/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java b/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java index 5162f062ab..51f8603e80 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java @@ -5,7 +5,7 @@ public enum Ecosystem { ANDROID("Android"), GSD("GSD"), GO("Go"), - HEX("Hex"), + ERLANG("Hex"), JAVASCRIPT("JavaScript"), LINUX("Linux"), MAVEN("Maven"), @@ -15,7 +15,7 @@ public enum Ecosystem { PYPI("PyPI"), RUBYGEMS("RubyGems"), UVI("UVI"), - CRATES("crates.io"), + RUST("crates.io"), NPM("npm"), DWF("DWF"); diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 0540d77d62..936d59ec96 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -85,7 +85,7 @@ private void unzipFolder(ZipInputStream zipIn) throws IOException { } } - private void updateDatasource(final OSVAdvisory advisory) { + public void updateDatasource(final OSVAdvisory advisory) { LOGGER.info("Updating datasource with Google OSV advisories"); try (QueryManager qm = new QueryManager()) { diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index c37ded0e5d..c6b09da9c8 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -15,23 +15,57 @@ */ package org.dependencytrack.task; +import alpine.common.logging.Logger; +import kong.unirest.json.JSONObject; import org.dependencytrack.PersistenceCapableTest; -import org.dependencytrack.event.GoogleOSVMirrorEvent; -import org.dependencytrack.model.ConfigPropertyConstants; +import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; +import org.dependencytrack.parser.osv.model.OSVAdvisory; +import org.dependencytrack.persistence.QueryManager; import org.dependencytrack.tasks.OSVDownloadTask; +import org.junit.Assert; import org.junit.Test; +import java.nio.file.Files; +import java.nio.file.Paths; + public class OSVDownloadTaskTest extends PersistenceCapableTest { + private static final Logger LOGGER = Logger.getLogger(OSVDownloadTaskTest.class); + @Test - public void informTest() { - final var task = new OSVDownloadTask(); - final var event = new GoogleOSVMirrorEvent(); - final var acceptArtifactProp = ConfigPropertyConstants.VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED; - qm.createConfigProperty(acceptArtifactProp.getGroupName(), acceptArtifactProp.getPropertyName(), "true", acceptArtifactProp.getPropertyType(), acceptArtifactProp.getDescription()); - - // TODO : add assertions on QueryManager database - // Commenting task below to avoid database write - // task.inform(event); + public void testParseOSVJsonToAdvisoryAndSave() { + + try { + // parse OSV json file to Advisory object + GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + String file = "src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json"; + String jsonString = readFileAsString(file); + JSONObject jsonObject = new JSONObject(jsonString); + OSVAdvisory advisory = parser.parse(jsonObject); + LOGGER.info("Advisory parsed is "+advisory); + Assert.assertNotNull(advisory); + Assert.assertEquals(advisory.getId(), "GHSA-77rv-6vfw-x4gc"); + Assert.assertEquals(advisory.getSeverity(), "CRITICAL"); + Assert.assertTrue(advisory.getCweIds().contains("CWE-601")); + Assert.assertEquals(advisory.getVulnerabilities().size(), 8); + Assert.assertEquals(advisory.getVulnerabilities().get(0).getUpperVersionRange(), "2.0.17"); + Assert.assertEquals(advisory.getVulnerabilities().get(0).getPurl(), "pkg:maven/org.springframework.security.oauth/spring-security-oauth"); + + // pass the mapped advisory to OSV task to update the database + final var task = new OSVDownloadTask(); + task.updateDatasource(advisory); + var qm = new QueryManager(); + final var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); + Assert.assertNotNull(vulnerableSoftware); + Assert.assertEquals(vulnerableSoftware.getPurlNamespace(), "MAVEN"); + + } catch (Exception ex) { + LOGGER.error("Exception reading JSON file"); + } + } + + public static String readFileAsString(String file) throws Exception + { + return new String(Files.readAllBytes(Paths.get(file))); } } \ No newline at end of file diff --git a/src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json b/src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json new file mode 100644 index 0000000000..d3bd3b6f6c --- /dev/null +++ b/src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json @@ -0,0 +1,318 @@ +{ + "id": "GHSA-77rv-6vfw-x4gc", + "summary": "Critical severity vulnerability that affects org.springframework.security.oauth:spring-security-oauth and org.springframework.security.oauth:spring-security-oauth2", + "details": "Spring Security OAuth, versions 2.3 prior to 2.3.5, and 2.2 prior to 2.2.4, and 2.1 prior to 2.1.4, and 2.0 prior to 2.0.17, and older unsupported versions could be susceptible to an open redirector attack that can leak an authorization code.\n\nA malicious user or attacker can craft a request to the authorization endpoint using the authorization code grant type, and specify a manipulated redirection URI via the \"redirect_uri\" parameter. This can cause the authorization server to redirect the resource owner user-agent to a URI under the control of the attacker with the leaked authorization code.\n\nThis vulnerability exposes applications that meet all of the following requirements: Act in the role of an Authorization Server (e.g. @EnableAuthorizationServer) and uses the DefaultRedirectResolver in the AuthorizationEndpoint. \n\nThis vulnerability does not expose applications that: Act in the role of an Authorization Server and uses a different RedirectResolver implementation other than DefaultRedirectResolver, act in the role of a Resource Server only (e.g. @EnableResourceServer), act in the role of a Client only (e.g. @EnableOAuthClient).", + "aliases": [ + "CVE-2019-3778" + ], + "modified": "2022-06-09T07:01:32.587163Z", + "published": "2019-03-14T15:39:30Z", + "database_specific": { + "severity": "CRITICAL", + "cwe_ids": [ + "CWE-601" + ], + "github_reviewed": true + }, + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-3778" + }, + { + "type": "ADVISORY", + "url": "https://github.com/advisories/GHSA-77rv-6vfw-x4gc" + }, + { + "type": "WEB", + "url": "https://pivotal.io/security/cve-2019-3778" + }, + { + "type": "WEB", + "url": "https://www.oracle.com/security-alerts/cpujan2021.html" + }, + { + "type": "WEB", + "url": "http://packetstormsecurity.com/files/153299/Spring-Security-OAuth-2.3-Open-Redirection.html" + }, + { + "type": "WEB", + "url": "http://www.securityfocus.com/bid/107153" + } + ], + "affected": [ + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "2.0.17" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "1.0.1.RELEASE", + "1.0.2.RELEASE", + "1.0.3.RELEASE", + "1.0.4.RELEASE", + "1.0.5.RELEASE", + "2.0.0.RELEASE", + "2.0.1.RELEASE", + "2.0.10.RELEASE", + "2.0.11.RELEASE", + "2.0.12.RELEASE", + "2.0.13.RELEASE", + "2.0.14.RELEASE", + "2.0.15.RELEASE", + "2.0.16.RELEASE", + "2.0.2.RELEASE", + "2.0.3.RELEASE", + "2.0.4.RELEASE", + "2.0.5.RELEASE", + "2.0.6.RELEASE", + "2.0.7.RELEASE", + "2.0.8.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth2", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth2" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "2.0.17" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "1.0.1.RELEASE", + "1.0.2.RELEASE", + "1.0.3.RELEASE", + "1.0.4.RELEASE", + "1.0.5.RELEASE", + "2.0.0.RELEASE", + "2.0.1.RELEASE", + "2.0.10.RELEASE", + "2.0.11.RELEASE", + "2.0.12.RELEASE", + "2.0.13.RELEASE", + "2.0.14.RELEASE", + "2.0.15.RELEASE", + "2.0.16.RELEASE", + "2.0.2.RELEASE", + "2.0.3.RELEASE", + "2.0.4.RELEASE", + "2.0.5.RELEASE", + "2.0.6.RELEASE", + "2.0.7.RELEASE", + "2.0.8.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.1.0" + }, + { + "fixed": "2.1.4" + } + ] + } + ], + "versions": [ + "2.1.0.RELEASE", + "2.1.1.RELEASE", + "2.1.2.RELEASE", + "2.1.3.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth2", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth2" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.1.0" + }, + { + "fixed": "2.1.4" + } + ] + } + ], + "versions": [ + "2.1.0.RELEASE", + "2.1.1.RELEASE", + "2.1.2.RELEASE", + "2.1.3.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.2.0" + }, + { + "fixed": "2.2.4" + } + ] + } + ], + "versions": [ + "2.2.0.RELEASE", + "2.2.1.RELEASE", + "2.2.2.RELEASE", + "2.2.3.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth2", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth2" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.2.0" + }, + { + "fixed": "2.2.4" + } + ] + } + ], + "versions": [ + "2.2.0.RELEASE", + "2.2.1.RELEASE", + "2.2.2.RELEASE", + "2.2.3.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.3.0" + }, + { + "fixed": "2.3.5" + } + ] + } + ], + "versions": [ + "2.3.0.RELEASE", + "2.3.1.RELEASE", + "2.3.2.RELEASE", + "2.3.3.RELEASE", + "2.3.4.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth2", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth2" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.3.0" + }, + { + "fixed": "2.3.5" + } + ] + } + ], + "versions": [ + "2.3.0.RELEASE", + "2.3.1.RELEASE", + "2.3.2.RELEASE", + "2.3.3.RELEASE", + "2.3.4.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From a9a0783694f15760d6a74d64a4629a4f09618b14 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 16 Jun 2022 16:12:31 +0100 Subject: [PATCH 05/26] osv enabled default to true Signed-off-by: Sahiba Mittal --- .../java/org/dependencytrack/model/ConfigPropertyConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java index cfec529f88..954cfab756 100644 --- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java +++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java @@ -48,7 +48,7 @@ public enum ConfigPropertyConstants { VULNERABILITY_SOURCE_NVD_FEEDS_URL("vuln-source", "nvd.feeds.url", "https://nvd.nist.gov/feeds", PropertyType.URL, "A base URL pointing to the hostname and path of the NVD feeds"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ENABLED("vuln-source", "github.advisories.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable GitHub Advisories"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ACCESS_TOKEN("vuln-source", "github.advisories.access.token", null, PropertyType.STRING, "The access token used for GitHub API authentication"), - VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable Google OSV"), + VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Google OSV"), VULNERABILITY_SOURCE_EPSS_ENABLED("vuln-source", "epss.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Exploit Prediction Scoring System"), VULNERABILITY_SOURCE_EPSS_FEEDS_URL("vuln-source", "epss.feeds.url", "https://epss.cyentia.com", PropertyType.URL, "A base URL pointing to the hostname and path of the EPSS feeds"), ACCEPT_ARTIFACT_CYCLONEDX("artifact", "cyclonedx.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable the systems ability to accept CycloneDX uploads"), From a8adf36ca79d3dcc5fd3097c2a9f85d419eea56e Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 17 Jun 2022 15:10:47 +0100 Subject: [PATCH 06/26] fixes and tests Signed-off-by: Sahiba Mittal --- .../parser/osv/GoogleOSVAdvisoryParser.java | 73 +++++++---- .../parser/osv/model/OSVAdvisory.java | 34 +++-- .../tasks/OSVDownloadTask.java | 46 +++---- .../osv/GoogleOSVAdvisoryParserTest.java | 104 +++++++++++++++ .../task/OSVDownloadTaskTest.java | 63 ++++----- .../osv-GHSA-77rv-6vfw-x4gc.json} | 8 +- .../osv-vulnerability-with-ranges.json | 123 ++++++++++++++++++ .../unit/osv.jsons/osv-withdrawn.json | 47 +++++++ 8 files changed, 411 insertions(+), 87 deletions(-) create mode 100644 src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java rename src/test/resources/unit/{tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json => osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json} (98%) create mode 100644 src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json create mode 100644 src/test/resources/unit/osv.jsons/osv-withdrawn.json diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 3dcfbc4e18..f8ccdd5afe 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -2,6 +2,7 @@ import kong.unirest.json.JSONArray; import kong.unirest.json.JSONObject; +import org.apache.commons.lang3.StringUtils; import org.dependencytrack.parser.osv.model.OSVAdvisory; import org.dependencytrack.parser.osv.model.OSVVulnerability; @@ -17,10 +18,16 @@ public class GoogleOSVAdvisoryParser { public OSVAdvisory parse(final JSONObject object) { - final OSVAdvisory advisory = new OSVAdvisory(); - if(object != null) { + OSVAdvisory advisory = null; + + // initial check if advisory is valid or withdrawn + String withdrawn = object.optString("withdrawn", null); + + if(object != null && withdrawn == null) { + + advisory = new OSVAdvisory(); advisory.setId(object.optString("id", null)); - advisory.setSummary(object.optString("summary", null)); + advisory.setSummary(trimSummary(object.optString("summary", null))); advisory.setDetails(object.optString("details", null)); advisory.setPublished(jsonStringToTimestamp(object.optString("published", null))); advisory.setModified(jsonStringToTimestamp(object.optString("modified", null))); @@ -53,9 +60,18 @@ public OSVAdvisory parse(final JSONObject object) { } } - final JSONObject cvss = object.optJSONObject("severity"); - if (cvss != null) { - advisory.setCvssVector(cvss.optString("vectorString", null)); + final JSONArray cvssList = object.optJSONArray("severity"); + if (cvssList != null) { + for (int i=0; i vulnerabilities = parseVulnerabilities(object); @@ -70,19 +86,23 @@ private List parseVulnerabilities(JSONObject object) { final JSONArray vulnerabilities = object.optJSONArray("affected"); if (vulnerabilities != null) { for(int i=0; i parseVulnerabilityRange(JSONObject vulnerability) { - } + List osvVulnerabilityList = new ArrayList<>(); + final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); + final JSONArray ranges = vulnerability.optJSONArray("ranges"); + + if (ranges != null) { + for (int j=0; j parseVersionRanges(JSONObject affectedPackageJson osvVulnerability.setLowerVersionRange(lower); k += 1; } - event = rangeEvents.getJSONObject(k); - String upper = event.optString("fixed", null); - if(upper != null) { - osvVulnerability.setUpperVersionRange(upper); - k += 1; + if(k < rangeEvents.length()) { + event = rangeEvents.getJSONObject(k); + String upper = event.optString("fixed", null); + if(upper != null) { + osvVulnerability.setUpperVersionRange(upper); + k += 1; + } } osvVulnerabilityList.add(osvVulnerability); } } return osvVulnerabilityList; } + + public String trimSummary(String summary) { + + final int MAX_LEN = 255; + // NPE safe + return StringUtils.substring(summary, 0, MAX_LEN-2) + ".."; + } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java index c28582b4e0..5dbf7327e0 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java @@ -20,6 +20,8 @@ public class OSVAdvisory { private ZonedDateTime published; + private ZonedDateTime withdrawn; + private List cweIds; private List references; @@ -28,9 +30,9 @@ public class OSVAdvisory { private List vulnerabilities; - private double cvssScore; + private String cvssV2Vector; - private String cvssVector; + private String cvssV3Vector; public String getId() { return id; @@ -45,7 +47,7 @@ public List getCweIds() { } public void addCweId(String cweId) { - if (cweId == null) { + if (cweIds == null) { cweIds = new ArrayList<>(); } cweIds.add(cweId); @@ -76,7 +78,7 @@ public List getAliases() { } public void addAlias(String alias) { - if (alias == null) { + if (aliases == null) { aliases = new ArrayList<>(); } aliases.add(alias); @@ -141,19 +143,27 @@ public void setSeverity(String severity) { this.severity = severity; } - public double getCvssScore() { - return cvssScore; + public ZonedDateTime getWithdrawn() { + return withdrawn; + } + + public void setWithdrawn(ZonedDateTime withdrawn) { + this.withdrawn = withdrawn; + } + + public String getCvssV2Vector() { + return cvssV2Vector; } - public void setCvssScore(double cvssScore) { - this.cvssScore = cvssScore; + public void setCvssV2Vector(String cvssV2Vector) { + this.cvssV2Vector = cvssV2Vector; } - public String getCvssVector() { - return cvssVector; + public String getCvssV3Vector() { + return cvssV3Vector; } - public void setCvssVector(String cvssVector) { - this.cvssVector = cvssVector; + public void setCvssV3Vector(String cvssV3Vector) { + this.cvssV3Vector = cvssV3Vector; } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 936d59ec96..b98cc329d2 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -49,13 +49,14 @@ public void inform(Event e) { try { for (Ecosystem ecosystem : Ecosystem.values()) { - URL url = new URL("https://osv-vulnerabilities.storage.googleapis.com/"+ ecosystem.getValue() +"/all.zip"); - ZipInputStream zipIn = new ZipInputStream(url.openStream()); - unzipFolder(zipIn); - zipIn.closeEntry(); + LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); + URL url = new URL("https://osv-vulnerabilities.storage.googleapis.com/" + ecosystem.getValue() +"/all.zip"); + try (ZipInputStream zipIn = new ZipInputStream(url.openStream())) { + unzipFolder(zipIn); + } } - } catch (IOException exception) { - exception.printStackTrace(); + } catch (Exception exception) { + LOGGER.error(exception.getMessage()); } } } @@ -64,29 +65,26 @@ private void unzipFolder(ZipInputStream zipIn) throws IOException { BufferedReader reader; GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); - try { - ZipEntry zipEntry = zipIn.getNextEntry(); - while (zipEntry != null) { - - reader = new BufferedReader(new InputStreamReader(zipIn)); - String line = null; - StringBuilder out = new StringBuilder(); - while ((line = reader.readLine()) != null) { - out.append(line); - } - JSONObject json = new JSONObject(out.toString()); - System.out.println(json); - final OSVAdvisory osvAdvisory = parser.parse(json); + ZipEntry zipEntry = zipIn.getNextEntry(); + while (zipEntry != null) { + + reader = new BufferedReader(new InputStreamReader(zipIn)); + String line = null; + StringBuilder out = new StringBuilder(); + while ((line = reader.readLine()) != null) { + out.append(line); + } + JSONObject json = new JSONObject(out.toString()); + final OSVAdvisory osvAdvisory = parser.parse(json); + if (osvAdvisory != null) { updateDatasource(osvAdvisory); - zipEntry = zipIn.getNextEntry(); } - } catch (IOException e) { - e.printStackTrace(); + zipEntry = zipIn.getNextEntry(); } } public void updateDatasource(final OSVAdvisory advisory) { - LOGGER.info("Updating datasource with Google OSV advisories"); + try (QueryManager qm = new QueryManager()) { LOGGER.debug("Synchronizing Google OSV advisory: " + advisory.getId()); @@ -115,6 +113,8 @@ private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OS vuln.setDescription(advisory.getDetails()); vuln.setPublished(Date.from(advisory.getPublished().toInstant())); vuln.setUpdated(Date.from(advisory.getModified().toInstant())); + vuln.setCvssV2Vector(advisory.getCvssV2Vector()); + vuln.setCvssV3Vector(advisory.getCvssV3Vector()); if (advisory.getReferences() != null && advisory.getReferences().size() > 0) { final StringBuilder sb = new StringBuilder(); diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java new file mode 100644 index 0000000000..e50712a0f8 --- /dev/null +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -0,0 +1,104 @@ +package org.dependencytrack.parser.osv; + +import kong.unirest.json.JSONArray; +import kong.unirest.json.JSONObject; +import org.dependencytrack.parser.osv.model.OSVAdvisory; +import org.dependencytrack.parser.osv.model.OSVVulnerability; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class GoogleOSVAdvisoryParserTest { + + GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + + @Test + public void testTrimSummary() { + + String osvLongSummary = "In uvc_scan_chain_forward of uvc_driver.c, there is a possible linked list corruption due to an unusual root cause. This could lead to local escalation of privilege in the kernel with no additional execution privileges needed. User interaction is not needed for exploitation."; + String trimmedSummary = parser.trimSummary(osvLongSummary); + Assert.assertNotNull(trimmedSummary); + Assert.assertEquals(trimmedSummary.length(), 255); + } + + @Test + public void testVulnerabilityRangeEmpty() throws IOException { + + String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); + JSONObject jsonObject = new JSONObject(jsonString); + final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(0)); + Assert.assertNotNull(osvVulnerabilityList); + Assert.assertEquals(osvVulnerabilityList.size(), 0); + } + + @Test + public void testVulnerabilityRangeSingle() throws IOException { + + String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); + JSONObject jsonObject = new JSONObject(jsonString); + final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(1)); + Assert.assertNotNull(osvVulnerabilityList); + Assert.assertEquals(osvVulnerabilityList.size(), 1); + OSVVulnerability vuln = osvVulnerabilityList.get(0); + Assert.assertEquals(vuln.getPurl(), "pkg:maven/org.springframework.security.oauth/spring-security-oauth"); + Assert.assertEquals(vuln.getLowerVersionRange(), "0"); + Assert.assertEquals(vuln.getUpperVersionRange(), "2.0.17"); + Assert.assertEquals(vuln.getPackageEcosystem(), "Maven"); + + } + + @Test + public void testVulnerabilityRangeMultiple() throws IOException { + + String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); + JSONObject jsonObject = new JSONObject(jsonString); + final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); + + // range test full pairs + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(2)); + Assert.assertNotNull(osvVulnerabilityList); + Assert.assertEquals(osvVulnerabilityList.size(), 2); + Assert.assertEquals(osvVulnerabilityList.get(0).getLowerVersionRange(), "1"); + Assert.assertEquals(osvVulnerabilityList.get(0).getUpperVersionRange(), "2"); + Assert.assertEquals(osvVulnerabilityList.get(1).getLowerVersionRange(), "3"); + Assert.assertEquals(osvVulnerabilityList.get(1).getUpperVersionRange(), "4"); + + // range test half pairs + osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(3)); + Assert.assertNotNull(osvVulnerabilityList); + Assert.assertEquals(osvVulnerabilityList.size(), 3); + Assert.assertEquals(osvVulnerabilityList.get(0).getLowerVersionRange(), null); + Assert.assertEquals(osvVulnerabilityList.get(0).getUpperVersionRange(), "2"); + Assert.assertEquals(osvVulnerabilityList.get(1).getLowerVersionRange(), "3"); + Assert.assertEquals(osvVulnerabilityList.get(1).getUpperVersionRange(), null); + Assert.assertEquals(osvVulnerabilityList.get(2).getLowerVersionRange(), "4"); + Assert.assertEquals(osvVulnerabilityList.get(2).getUpperVersionRange(), "5"); + } + + @Test + public void testParseOSVJson() throws IOException { + + String jsonFile = "src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); + JSONObject jsonObject = new JSONObject(jsonString); + OSVAdvisory advisory = parser.parse(jsonObject); + Assert.assertNotNull(advisory); + Assert.assertEquals(advisory.getId(), "GHSA-77rv-6vfw-x4gc"); + Assert.assertEquals(advisory.getSeverity(), "CRITICAL"); + Assert.assertEquals(advisory.getCweIds().size(), 1); + Assert.assertEquals(advisory.getReferences().size(), 6); + Assert.assertEquals(advisory.getVulnerabilities().size(), 8); + Assert.assertEquals(advisory.getCvssV3Vector(), "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"); + Assert.assertEquals(advisory.getAliases().get(0), "CVE-2019-3778"); + Assert.assertEquals(advisory.getModified().toString(), "2022-06-09T07:01:32.587163Z"); + } +} diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index c6b09da9c8..460754d1ed 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -33,39 +33,44 @@ public class OSVDownloadTaskTest extends PersistenceCapableTest { private static final Logger LOGGER = Logger.getLogger(OSVDownloadTaskTest.class); @Test - public void testParseOSVJsonToAdvisoryAndSave() { + public void testParseOSVJsonToAdvisoryAndSave() throws Exception { - try { - // parse OSV json file to Advisory object - GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); - String file = "src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json"; - String jsonString = readFileAsString(file); - JSONObject jsonObject = new JSONObject(jsonString); - OSVAdvisory advisory = parser.parse(jsonObject); - LOGGER.info("Advisory parsed is "+advisory); - Assert.assertNotNull(advisory); - Assert.assertEquals(advisory.getId(), "GHSA-77rv-6vfw-x4gc"); - Assert.assertEquals(advisory.getSeverity(), "CRITICAL"); - Assert.assertTrue(advisory.getCweIds().contains("CWE-601")); - Assert.assertEquals(advisory.getVulnerabilities().size(), 8); - Assert.assertEquals(advisory.getVulnerabilities().get(0).getUpperVersionRange(), "2.0.17"); - Assert.assertEquals(advisory.getVulnerabilities().get(0).getPurl(), "pkg:maven/org.springframework.security.oauth/spring-security-oauth"); + // parse OSV json file to Advisory object + GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + String file = "src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(file))); + JSONObject jsonObject = new JSONObject(jsonString); + OSVAdvisory advisory = parser.parse(jsonObject); + LOGGER.info("Advisory parsed is "+advisory); + Assert.assertNotNull(advisory); + Assert.assertEquals(advisory.getVulnerabilities().size(), 8); - // pass the mapped advisory to OSV task to update the database - final var task = new OSVDownloadTask(); - task.updateDatasource(advisory); - var qm = new QueryManager(); - final var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); - Assert.assertNotNull(vulnerableSoftware); - Assert.assertEquals(vulnerableSoftware.getPurlNamespace(), "MAVEN"); + // pass the mapped advisory to OSV task to update the database + final var task = new OSVDownloadTask(); + task.updateDatasource(advisory); + var qm = new QueryManager(); + var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); + Assert.assertNotNull(vulnerableSoftware); + Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); + Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "0"); + Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.0.17"); - } catch (Exception ex) { - LOGGER.error("Exception reading JSON file"); - } + vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.1.4", "2.1.0"); + Assert.assertNotNull(vulnerableSoftware); + Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); + Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "2.1.0"); + Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.1.4"); } - public static String readFileAsString(String file) throws Exception - { - return new String(Files.readAllBytes(Paths.get(file))); + @Test + public void testWithdrawnAdvisory() throws Exception { + + GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + String file = "src/test/resources/unit/osv.jsons/osv-withdrawn.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(file))); + JSONObject jsonObject = new JSONObject(jsonString); + OSVAdvisory advisory = parser.parse(jsonObject); + LOGGER.info("Advisory parsed is "+advisory); + Assert.assertNull(advisory); } } \ No newline at end of file diff --git a/src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json b/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json similarity index 98% rename from src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json rename to src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json index d3bd3b6f6c..3852834bb9 100644 --- a/src/test/resources/unit/tasks/repositories/https---osv-GHSA-77rv-6vfw-x4gc.json +++ b/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json @@ -314,5 +314,11 @@ } } ], - "schema_version": "1.2.0" + "schema_version": "1.2.0", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ] } \ No newline at end of file diff --git a/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json b/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json new file mode 100644 index 0000000000..6820d0db6f --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json @@ -0,0 +1,123 @@ +{ + "affected": [ + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth2", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth2" + }, + "ranges": [], + "versions": [ + "1.0.0.RELEASE", + "1.0.1.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "2.0.17" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "1" + }, + { + "fixed": "2" + }, + { + "introduced": "3" + }, + { + "fixed": "4" + } + ] + }, + { + "type": "SERVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "fixed": "2" + }, + { + "introduced": "3" + }, + { + "introduced": "4" + }, + { + "fixed": "5" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/unit/osv.jsons/osv-withdrawn.json b/src/test/resources/unit/osv.jsons/osv-withdrawn.json new file mode 100644 index 0000000000..9fe9a2d7e7 --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-withdrawn.json @@ -0,0 +1,47 @@ +{ + "id": "GHSA-77rv-6vfw-x4gc", + "summary": "Critical severity vulnerability that affects org.springframework.security.oauth:spring-security-oauth and org.springframework.security.oauth:spring-security-oauth2", + "details": "Spring Security OAuth, versions 2.3 prior to 2.3.5, and 2.2 prior to 2.2.4, and 2.1 prior to 2.1.4, and 2.0 prior to 2.0.17, and older unsupported versions could be susceptible to an open redirector attack that can leak an authorization code.\n\nA malicious user or attacker can craft a request to the authorization endpoint using the authorization code grant type, and specify a manipulated redirection URI via the \"redirect_uri\" parameter. This can cause the authorization server to redirect the resource owner user-agent to a URI under the control of the attacker with the leaked authorization code.\n\nThis vulnerability exposes applications that meet all of the following requirements: Act in the role of an Authorization Server (e.g. @EnableAuthorizationServer) and uses the DefaultRedirectResolver in the AuthorizationEndpoint. \n\nThis vulnerability does not expose applications that: Act in the role of an Authorization Server and uses a different RedirectResolver implementation other than DefaultRedirectResolver, act in the role of a Resource Server only (e.g. @EnableResourceServer), act in the role of a Client only (e.g. @EnableOAuthClient).", + "aliases": [ + "CVE-2019-3778" + ], + "modified": "2022-06-09T07:01:32.587163Z", + "published": "2019-03-14T15:39:30Z", + "withdrawn": "2020-03-14T15:39:30Z", + "database_specific": { + "severity": "CRITICAL", + "cwe_ids": [ + "CWE-601" + ], + "github_reviewed": true + }, + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-3778" + } + ], + "affected": [ + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "2.0.17" + } + ] + } + ] + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From eeb137209fef96d5355b647c2bb5596d85c9cfae Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 17 Jun 2022 17:18:29 +0100 Subject: [PATCH 07/26] fix http client Signed-off-by: Sahiba Mittal --- .../tasks/OSVDownloadTask.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index b98cc329d2..3fa714ab49 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -5,6 +5,11 @@ import alpine.event.framework.LoggableSubscriber; import alpine.model.ConfigProperty; import kong.unirest.json.JSONObject; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.dependencytrack.common.HttpClientPool; import org.dependencytrack.event.GoogleOSVMirrorEvent; import org.dependencytrack.event.IndexEvent; import org.dependencytrack.model.Cwe; @@ -20,8 +25,8 @@ import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -34,6 +39,7 @@ public class OSVDownloadTask implements LoggableSubscriber { private static final Logger LOGGER = Logger.getLogger(OSVDownloadTask.class); private final boolean isEnabled; + private HttpUriRequest request; public OSVDownloadTask() { try (final QueryManager qm = new QueryManager()) { @@ -50,9 +56,18 @@ public void inform(Event e) { try { for (Ecosystem ecosystem : Ecosystem.values()) { LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); - URL url = new URL("https://osv-vulnerabilities.storage.googleapis.com/" + ecosystem.getValue() +"/all.zip"); - try (ZipInputStream zipIn = new ZipInputStream(url.openStream())) { - unzipFolder(zipIn); + String url = "https://osv-vulnerabilities.storage.googleapis.com/" + ecosystem.getValue() + "/all.zip"; + request = new HttpGet(url); + try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { + final StatusLine status = response.getStatusLine(); + if (status.getStatusCode() == 200) { + try (InputStream in = response.getEntity().getContent()) { + ZipInputStream zipInput = new ZipInputStream(in); + unzipFolder(zipInput); + } + } else { + LOGGER.error("Download failed : " + status.getStatusCode() + ": " + status.getReasonPhrase()); + } } } } catch (Exception exception) { From dba49b5fe14770f0dbbbfe4065e4bd99425f484b Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 23 Jun 2022 14:15:11 +0100 Subject: [PATCH 08/26] update source of vulnerability Signed-off-by: Sahiba Mittal --- .../tasks/OSVDownloadTask.java | 14 ++++++++++++- .../task/OSVDownloadTaskTest.java | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 3fa714ab49..75f51ba93c 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -122,7 +122,9 @@ public void updateDatasource(final OSVAdvisory advisory) { private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSVAdvisory advisory) { final Vulnerability vuln = new Vulnerability(); - vuln.setSource(Vulnerability.Source.GOOGLE); + if(advisory.getId() != null) { + vuln.setSource(extractSource(advisory.getId())); + } vuln.setVulnId(String.valueOf(advisory.getId())); vuln.setTitle(advisory.getSummary()); vuln.setDescription(advisory.getDetails()); @@ -166,6 +168,16 @@ private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OS return vuln; } + public Vulnerability.Source extractSource(String vulnId) { + + final String sourceId = vulnId.split("-")[0]; + switch (sourceId) { + case "GHSA": return Vulnerability.Source.GITHUB; + case "CVE": return Vulnerability.Source.NVD; + default: return Vulnerability.Source.GOOGLE; + } + } + private VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { String versionStartIncluding = vuln.getLowerVersionRange(); diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index 460754d1ed..04f6047cd0 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -18,6 +18,7 @@ import alpine.common.logging.Logger; import kong.unirest.json.JSONObject; import org.dependencytrack.PersistenceCapableTest; +import org.dependencytrack.model.Vulnerability; import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; import org.dependencytrack.parser.osv.model.OSVAdvisory; import org.dependencytrack.persistence.QueryManager; @@ -73,4 +74,24 @@ public void testWithdrawnAdvisory() throws Exception { LOGGER.info("Advisory parsed is "+advisory); Assert.assertNull(advisory); } + + @Test + public void testSourceOfVulnerability() { + + String sourceTestId = "GHSA-77rv-6vfw-x4gc"; + final var task = new OSVDownloadTask(); + Vulnerability.Source source = task.extractSource(sourceTestId); + Assert.assertNotNull(source); + Assert.assertEquals(Vulnerability.Source.GITHUB, source); + + sourceTestId = "CVE-2022-tyhg"; + source = task.extractSource(sourceTestId); + Assert.assertNotNull(source); + Assert.assertEquals(Vulnerability.Source.NVD, source); + + sourceTestId = "anyOther-2022-tyhg"; + source = task.extractSource(sourceTestId); + Assert.assertNotNull(source); + Assert.assertEquals(Vulnerability.Source.GOOGLE, source); + } } \ No newline at end of file From 0fb6ad104dfee856e458658d92b1241677717acd Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 23 Jun 2022 15:30:15 +0100 Subject: [PATCH 09/26] map credits Signed-off-by: Sahiba Mittal --- .../parser/osv/GoogleOSVAdvisoryParser.java | 9 ++++ .../parser/osv/model/OSVAdvisory.java | 18 ++++++++ .../tasks/OSVDownloadTask.java | 7 +-- .../osv/GoogleOSVAdvisoryParserTest.java | 1 + .../task/OSVDownloadTaskTest.java | 43 ++++++++++++------- .../osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json | 8 ++++ 6 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index f8ccdd5afe..cfa2e31876 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -42,6 +42,15 @@ public OSVAdvisory parse(final JSONObject object) { } } + final JSONArray credits = object.optJSONArray("credits"); + if (credits != null) { + for (int i=0; i references; + private List credits; + private String schema_version; private List vulnerabilities; @@ -166,4 +168,20 @@ public String getCvssV3Vector() { public void setCvssV3Vector(String cvssV3Vector) { this.cvssV3Vector = cvssV3Vector; } + + public List getCredits() { + return credits; + } + + public void addCredit(String credit) { + if (this.credits == null) { + this.credits = new ArrayList<>(); + } + this.credits.add(credit); + } + + public void setCredits(List credits) { + this.credits = credits; + } + } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 75f51ba93c..1f32e6b89c 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -119,7 +119,7 @@ public void updateDatasource(final OSVAdvisory advisory) { Event.dispatch(new IndexEvent(IndexEvent.Action.COMMIT, Vulnerability.class)); } - private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSVAdvisory advisory) { + public Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSVAdvisory advisory) { final Vulnerability vuln = new Vulnerability(); if(advisory.getId() != null) { @@ -130,8 +130,7 @@ private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OS vuln.setDescription(advisory.getDetails()); vuln.setPublished(Date.from(advisory.getPublished().toInstant())); vuln.setUpdated(Date.from(advisory.getModified().toInstant())); - vuln.setCvssV2Vector(advisory.getCvssV2Vector()); - vuln.setCvssV3Vector(advisory.getCvssV3Vector()); + vuln.setCredits(String.join(", ", advisory.getCredits())); if (advisory.getReferences() != null && advisory.getReferences().size() > 0) { final StringBuilder sb = new StringBuilder(); @@ -165,6 +164,8 @@ private Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OS } else { vuln.setSeverity(Severity.UNASSIGNED); } + vuln.setCvssV2Vector(advisory.getCvssV2Vector()); + vuln.setCvssV3Vector(advisory.getCvssV3Vector()); return vuln; } diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java index e50712a0f8..cc2556bf4d 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -96,6 +96,7 @@ public void testParseOSVJson() throws IOException { Assert.assertEquals(advisory.getSeverity(), "CRITICAL"); Assert.assertEquals(advisory.getCweIds().size(), 1); Assert.assertEquals(advisory.getReferences().size(), 6); + Assert.assertEquals(advisory.getCredits().size(), 2); Assert.assertEquals(advisory.getVulnerabilities().size(), 8); Assert.assertEquals(advisory.getCvssV3Vector(), "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"); Assert.assertEquals(advisory.getAliases().get(0), "CVE-2019-3778"); diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index 04f6047cd0..7bdaeeaf59 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -15,9 +15,9 @@ */ package org.dependencytrack.task; -import alpine.common.logging.Logger; import kong.unirest.json.JSONObject; import org.dependencytrack.PersistenceCapableTest; +import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; import org.dependencytrack.parser.osv.model.OSVAdvisory; @@ -26,28 +26,24 @@ import org.junit.Assert; import org.junit.Test; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; public class OSVDownloadTaskTest extends PersistenceCapableTest { - - private static final Logger LOGGER = Logger.getLogger(OSVDownloadTaskTest.class); + private JSONObject jsonObject; + private final GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + private final OSVDownloadTask task = new OSVDownloadTask(); @Test public void testParseOSVJsonToAdvisoryAndSave() throws Exception { - // parse OSV json file to Advisory object - GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); - String file = "src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"; - String jsonString = new String(Files.readAllBytes(Paths.get(file))); - JSONObject jsonObject = new JSONObject(jsonString); + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); OSVAdvisory advisory = parser.parse(jsonObject); - LOGGER.info("Advisory parsed is "+advisory); Assert.assertNotNull(advisory); Assert.assertEquals(advisory.getVulnerabilities().size(), 8); // pass the mapped advisory to OSV task to update the database - final var task = new OSVDownloadTask(); task.updateDatasource(advisory); var qm = new QueryManager(); var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); @@ -63,15 +59,25 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.1.4"); } + @Test + public void testParseAdvisoryToVulnerability() throws IOException { + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); + OSVAdvisory advisory = parser.parse(jsonObject); + Assert.assertNotNull(advisory); + Vulnerability vuln = task.mapAdvisoryToVulnerability(new QueryManager(), advisory); + Assert.assertNotNull(vuln); + Assert.assertEquals("Skywalker, Solo", vuln.getCredits()); + Assert.assertEquals("GITHUB", vuln.getSource()); + Assert.assertEquals(Severity.CRITICAL, vuln.getSeverity()); + Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", vuln.getCvssV3Vector()); + } + @Test public void testWithdrawnAdvisory() throws Exception { - GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); - String file = "src/test/resources/unit/osv.jsons/osv-withdrawn.json"; - String jsonString = new String(Files.readAllBytes(Paths.get(file))); - JSONObject jsonObject = new JSONObject(jsonString); + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-withdrawn.json"); OSVAdvisory advisory = parser.parse(jsonObject); - LOGGER.info("Advisory parsed is "+advisory); Assert.assertNull(advisory); } @@ -79,7 +85,6 @@ public void testWithdrawnAdvisory() throws Exception { public void testSourceOfVulnerability() { String sourceTestId = "GHSA-77rv-6vfw-x4gc"; - final var task = new OSVDownloadTask(); Vulnerability.Source source = task.extractSource(sourceTestId); Assert.assertNotNull(source); Assert.assertEquals(Vulnerability.Source.GITHUB, source); @@ -94,4 +99,10 @@ public void testSourceOfVulnerability() { Assert.assertNotNull(source); Assert.assertEquals(Vulnerability.Source.GOOGLE, source); } + + private void prepareJsonObject(String filePath) throws IOException { + // parse OSV json file to Advisory object + String jsonString = new String(Files.readAllBytes(Paths.get(filePath))); + jsonObject = new JSONObject(jsonString); + } } \ No newline at end of file diff --git a/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json b/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json index 3852834bb9..26dc926981 100644 --- a/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json +++ b/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json @@ -40,6 +40,14 @@ "url": "http://www.securityfocus.com/bid/107153" } ], + "credits": [ + { + "name": "Skywalker" + }, + { + "name": "Solo" + } + ], "affected": [ { "package": { From 6f725af90908648d3e6f6172d4da8e44db583920 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 24 Jun 2022 11:19:32 +0100 Subject: [PATCH 10/26] minor changes Signed-off-by: Sahiba Mittal --- .../tasks/OSVDownloadTask.java | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 1f32e6b89c..57c8b6f11b 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -53,25 +53,25 @@ public void inform(Event e) { if (e instanceof GoogleOSVMirrorEvent && this.isEnabled) { - try { - for (Ecosystem ecosystem : Ecosystem.values()) { - LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); - String url = "https://osv-vulnerabilities.storage.googleapis.com/" + ecosystem.getValue() + "/all.zip"; - request = new HttpGet(url); - try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { - final StatusLine status = response.getStatusLine(); - if (status.getStatusCode() == 200) { - try (InputStream in = response.getEntity().getContent()) { - ZipInputStream zipInput = new ZipInputStream(in); - unzipFolder(zipInput); - } - } else { - LOGGER.error("Download failed : " + status.getStatusCode() + ": " + status.getReasonPhrase()); + for (Ecosystem ecosystem : Ecosystem.values()) { + LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); + String url = "https://osv-vulnerabilities.storage.googleapis.com/" + ecosystem.getValue() + "/all.zip"; + request = new HttpGet(url); + request.setHeader("Accept", "*/*"); + request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"); + try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { + final StatusLine status = response.getStatusLine(); + if (status.getStatusCode() == 200) { + try (InputStream in = response.getEntity().getContent(); + ZipInputStream zipInput = new ZipInputStream(in)) { + unzipFolder(zipInput); } + } else { + LOGGER.error("Download failed : " + status.getStatusCode() + ": " + status.getReasonPhrase()); } + } catch (Exception ex) { + LOGGER.error("Exception while executing Http client request", ex); } - } catch (Exception exception) { - LOGGER.error(exception.getMessage()); } } } @@ -94,6 +94,7 @@ private void unzipFolder(ZipInputStream zipIn) throws IOException { if (osvAdvisory != null) { updateDatasource(osvAdvisory); } + reader.close(); zipEntry = zipIn.getNextEntry(); } } @@ -130,7 +131,10 @@ public Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSV vuln.setDescription(advisory.getDetails()); vuln.setPublished(Date.from(advisory.getPublished().toInstant())); vuln.setUpdated(Date.from(advisory.getModified().toInstant())); - vuln.setCredits(String.join(", ", advisory.getCredits())); + + if (advisory.getCredits() != null) { + vuln.setCredits(String.join(", ", advisory.getCredits())); + } if (advisory.getReferences() != null && advisory.getReferences().size() > 0) { final StringBuilder sb = new StringBuilder(); From b987030ffaceca82921826984f97b5fb82648d94 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 24 Jun 2022 15:11:41 +0100 Subject: [PATCH 11/26] close reader Signed-off-by: Sahiba Mittal --- .../java/org/dependencytrack/tasks/OSVDownloadTask.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 57c8b6f11b..a0e26bbc8b 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -78,12 +78,11 @@ public void inform(Event e) { private void unzipFolder(ZipInputStream zipIn) throws IOException { - BufferedReader reader; + BufferedReader reader = new BufferedReader(new InputStreamReader(zipIn)); GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); ZipEntry zipEntry = zipIn.getNextEntry(); while (zipEntry != null) { - reader = new BufferedReader(new InputStreamReader(zipIn)); String line = null; StringBuilder out = new StringBuilder(); while ((line = reader.readLine()) != null) { @@ -94,9 +93,10 @@ private void unzipFolder(ZipInputStream zipIn) throws IOException { if (osvAdvisory != null) { updateDatasource(osvAdvisory); } - reader.close(); zipEntry = zipIn.getNextEntry(); + reader = new BufferedReader(new InputStreamReader(zipIn)); } + reader.close(); } public void updateDatasource(final OSVAdvisory advisory) { From cca6c9fe6984e7ab9281ba713bf746ad306f8930 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 28 Jun 2022 11:58:18 +0100 Subject: [PATCH 12/26] update severity calculation and prioritize Signed-off-by: Sahiba Mittal --- .../org/dependencytrack/model/Severity.java | 28 ++++-- .../parser/osv/GoogleOSVAdvisoryParser.java | 48 ++++++++- .../parser/osv/model/OSVVulnerability.java | 21 ++++ .../resources/v1/VulnerabilityResource.java | 2 +- .../tasks/OSVDownloadTask.java | 53 +++++++--- .../osv/GoogleOSVAdvisoryParserTest.java | 6 +- .../task/OSVDownloadTaskTest.java | 24 ++++- .../osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json | 4 +- .../osv-severity-test-ecosystem-cvss.json | 97 +++++++++++++++++++ .../osv-severity-test-ecosystem.json | 72 ++++++++++++++ 10 files changed, 326 insertions(+), 29 deletions(-) create mode 100644 src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem-cvss.json create mode 100644 src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem.json diff --git a/src/main/java/org/dependencytrack/model/Severity.java b/src/main/java/org/dependencytrack/model/Severity.java index 98124d7544..bf8cf730b4 100644 --- a/src/main/java/org/dependencytrack/model/Severity.java +++ b/src/main/java/org/dependencytrack/model/Severity.java @@ -18,6 +18,8 @@ */ package org.dependencytrack.model; +import java.util.Arrays; + /** * Defines internal severity labels. * @@ -25,10 +27,24 @@ * @since 3.0.0 */ public enum Severity { - CRITICAL, - HIGH, - MEDIUM, - LOW, - INFO, - UNASSIGNED + CRITICAL (5), + HIGH (4), + MEDIUM (3), + LOW (2), + INFO (1), + UNASSIGNED (0); + + private final int level; + + Severity(final int level) { + this.level = level; + } + + public int getLevel() { + return level; + } + + public static Severity getSeverityByLevel(final int level){ + return Arrays.stream(values()).filter(value -> value.level == level).findFirst().orElse(UNASSIGNED); + } } diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index cfa2e31876..51b6d97ceb 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -3,13 +3,17 @@ import kong.unirest.json.JSONArray; import kong.unirest.json.JSONObject; import org.apache.commons.lang3.StringUtils; +import org.dependencytrack.model.Severity; import org.dependencytrack.parser.osv.model.OSVAdvisory; import org.dependencytrack.parser.osv.model.OSVVulnerability; +import us.springett.cvss.Cvss; +import us.springett.cvss.Score; import java.util.ArrayList; import java.util.List; import static org.dependencytrack.util.JsonUtil.jsonStringToTimestamp; +import static org.dependencytrack.util.VulnerabilityUtil.normalizedCvssV3Score; /* Parser for Google OSV, an aggregator of vulnerability databases including GitHub Security Advisories, PyPA, RustSec, and Global Security Database, and more. @@ -104,21 +108,23 @@ private List parseVulnerabilities(JSONObject object) { public List parseVulnerabilityRange(JSONObject vulnerability) { List osvVulnerabilityList = new ArrayList<>(); - final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); final JSONArray ranges = vulnerability.optJSONArray("ranges"); if (ranges != null) { for (int j=0; j parseVersionRanges(JSONObject affectedPackageJson, JSONObject range) { + private List parseVersionRanges(JSONObject vulnerability, JSONObject range) { + + final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); + final JSONObject ecosystemSpecific = vulnerability.optJSONObject("ecosystem_specific"); + final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); + Severity ecosystemSeverity = parseEcosystemSeverity(ecosystemSpecific, databaseSpecific); final List osvVulnerabilityList = new ArrayList<>(); final JSONArray rangeEvents = range.optJSONArray("events"); @@ -130,6 +136,7 @@ private List parseVersionRanges(JSONObject affectedPackageJson osvVulnerability.setPackageName(affectedPackageJson.optString("name", null)); osvVulnerability.setPackageEcosystem(affectedPackageJson.optString("ecosystem", null)); osvVulnerability.setPurl(affectedPackageJson.optString("purl", null)); + osvVulnerability.setSeverity(ecosystemSeverity); JSONObject event = rangeEvents.getJSONObject(k); String lower = event.optString("introduced", null); @@ -151,6 +158,37 @@ private List parseVersionRanges(JSONObject affectedPackageJson return osvVulnerabilityList; } + private Severity parseEcosystemSeverity(JSONObject ecosystemSpecific, JSONObject databaseSpecific) { + + String severity = null; + + if (databaseSpecific != null) { + String cvssVector = databaseSpecific.optString("cvss", null); + if (cvssVector != null) { + Cvss cvss = Cvss.fromVector(cvssVector); + Score score = cvss.calculateScore(); + severity = String.valueOf(normalizedCvssV3Score(score.getBaseScore())); + } + } + + if(severity == null && ecosystemSpecific != null) { + severity = ecosystemSpecific.optString("severity", null); + } + + if (severity != null) { + if (severity.equalsIgnoreCase("CRITICAL")) { + return Severity.CRITICAL; + } else if (severity.equalsIgnoreCase("HIGH")) { + return Severity.HIGH; + } else if (severity.equalsIgnoreCase("MODERATE") || severity.equalsIgnoreCase("MEDIUM")) { + return Severity.MEDIUM; + } else if (severity.equalsIgnoreCase("LOW")) { + return Severity.LOW; + } + } + return Severity.UNASSIGNED; + } + public String trimSummary(String summary) { final int MAX_LEN = 255; diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java index 1de84802f5..305bef3c9d 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java @@ -1,5 +1,7 @@ package org.dependencytrack.parser.osv.model; +import org.dependencytrack.model.Severity; + public class OSVVulnerability { private String packageName; @@ -11,6 +13,10 @@ public class OSVVulnerability { private String upperVersionRange; + private Severity severity; + + private Double cvssScore; + public String getPackageName() { return packageName; } @@ -47,4 +53,19 @@ public String getUpperVersionRange() { public void setUpperVersionRange(String upperVersionRange) { this.upperVersionRange = upperVersionRange; } + + public Severity getSeverity() { + return severity; + } + public void setSeverity(Severity severity) { + this.severity = severity; + } + + public Double getCvssScore() { + return cvssScore; + } + + public void setCvssScore(double cvssScore) { + this.cvssScore = cvssScore; + } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java index 2433868e58..4ad9ee616e 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java @@ -421,7 +421,7 @@ public Response generateInternalVulnerabilityIdentifier() { return Response.ok(vulnId).build(); } - private void recalculateScoresFromVector(Vulnerability vuln) { + public void recalculateScoresFromVector(Vulnerability vuln) { // Recalculate V2 score based on vector passed to resource and normalize vector final Cvss v2 = Cvss.fromVector(vuln.getCvssV2Vector()); if (v2 != null) { diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index a0e26bbc8b..4d7a689610 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -23,17 +23,23 @@ import org.dependencytrack.parser.osv.model.OSVVulnerability; import org.dependencytrack.persistence.QueryManager; +import us.springett.cvss.Cvss; +import us.springett.cvss.Score; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import static org.dependencytrack.model.ConfigPropertyConstants.VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED; +import static org.dependencytrack.model.Severity.getSeverityByLevel; +import static org.dependencytrack.util.VulnerabilityUtil.*; public class OSVDownloadTask implements LoggableSubscriber { @@ -152,25 +158,50 @@ public Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSV } } } + vuln.setSeverity(calculateOSVSeverity(advisory)); + vuln.setCvssV2Vector(advisory.getCvssV2Vector()); + vuln.setCvssV3Vector(advisory.getCvssV3Vector()); + return vuln; + } + + // calculate severity of vulnerability on priority-basis (database, ecosystem) + public Severity calculateOSVSeverity(OSVAdvisory advisory) { + // derive from database_specific cvss v3 vector if available + if(advisory.getCvssV3Vector() != null) { + Cvss cvss = Cvss.fromVector(advisory.getCvssV3Vector()); + Score score = cvss.calculateScore(); + return normalizedCvssV3Score(score.getBaseScore()); + } + // derive from database_specific cvss v2 vector if available + if (advisory.getCvssV2Vector() != null) { + Cvss cvss = Cvss.fromVector(advisory.getCvssV2Vector()); + Score score = cvss.calculateScore(); + return normalizedCvssV2Score(score.getBaseScore()); + } + // get database_specific severity string if available if (advisory.getSeverity() != null) { if (advisory.getSeverity().equalsIgnoreCase("CRITICAL")) { - vuln.setSeverity(Severity.CRITICAL); + return Severity.CRITICAL; } else if (advisory.getSeverity().equalsIgnoreCase("HIGH")) { - vuln.setSeverity(Severity.HIGH); + return Severity.HIGH; } else if (advisory.getSeverity().equalsIgnoreCase("MODERATE")) { - vuln.setSeverity(Severity.MEDIUM); + return Severity.MEDIUM; } else if (advisory.getSeverity().equalsIgnoreCase("LOW")) { - vuln.setSeverity(Severity.LOW); - } else { - vuln.setSeverity(Severity.UNASSIGNED); + return Severity.LOW; } - } else { - vuln.setSeverity(Severity.UNASSIGNED); } - vuln.setCvssV2Vector(advisory.getCvssV2Vector()); - vuln.setCvssV3Vector(advisory.getCvssV3Vector()); - return vuln; + // get largest ecosystem_specific severity from its affected packages + if (advisory.getVulnerabilities() != null) { + List severityLevels = new ArrayList<>(); + for (OSVVulnerability vuln : advisory.getVulnerabilities()) { + severityLevels.add(vuln.getSeverity().getLevel()); + } + Collections.sort(severityLevels); + Collections.reverse(severityLevels); + return getSeverityByLevel(severityLevels.get(0)); + } + return Severity.UNASSIGNED; } public Vulnerability.Source extractSource(String vulnId) { diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java index cc2556bf4d..e40b158ab8 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -66,7 +66,7 @@ public void testVulnerabilityRangeMultiple() throws IOException { // range test full pairs List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(2)); Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(osvVulnerabilityList.size(), 2); + Assert.assertEquals(osvVulnerabilityList.size(), 3); Assert.assertEquals(osvVulnerabilityList.get(0).getLowerVersionRange(), "1"); Assert.assertEquals(osvVulnerabilityList.get(0).getUpperVersionRange(), "2"); Assert.assertEquals(osvVulnerabilityList.get(1).getLowerVersionRange(), "3"); @@ -93,12 +93,12 @@ public void testParseOSVJson() throws IOException { OSVAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Assert.assertEquals(advisory.getId(), "GHSA-77rv-6vfw-x4gc"); - Assert.assertEquals(advisory.getSeverity(), "CRITICAL"); + Assert.assertEquals(advisory.getSeverity(), "LOW"); Assert.assertEquals(advisory.getCweIds().size(), 1); Assert.assertEquals(advisory.getReferences().size(), 6); Assert.assertEquals(advisory.getCredits().size(), 2); Assert.assertEquals(advisory.getVulnerabilities().size(), 8); - Assert.assertEquals(advisory.getCvssV3Vector(), "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"); + Assert.assertEquals(advisory.getCvssV3Vector(), "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H"); Assert.assertEquals(advisory.getAliases().get(0), "CVE-2019-3778"); Assert.assertEquals(advisory.getModified().toString(), "2022-06-09T07:01:32.587163Z"); } diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index 7bdaeeaf59..98d949fb0c 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -51,6 +51,8 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "0"); Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.0.17"); + final Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); + Assert.assertNotNull(vulnerability); vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.1.4", "2.1.0"); Assert.assertNotNull(vulnerableSoftware); @@ -70,7 +72,7 @@ public void testParseAdvisoryToVulnerability() throws IOException { Assert.assertEquals("Skywalker, Solo", vuln.getCredits()); Assert.assertEquals("GITHUB", vuln.getSource()); Assert.assertEquals(Severity.CRITICAL, vuln.getSeverity()); - Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", vuln.getCvssV3Vector()); + Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", vuln.getCvssV3Vector()); } @Test @@ -100,6 +102,26 @@ public void testSourceOfVulnerability() { Assert.assertEquals(Vulnerability.Source.GOOGLE, source); } + @Test + public void testCalculateOSVSeverity() throws IOException { + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); + OSVAdvisory advisory = parser.parse(jsonObject); + Assert.assertNotNull(advisory); + Severity severity = task.calculateOSVSeverity(advisory); + Assert.assertEquals(Severity.CRITICAL, severity); + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem-cvss.json"); + advisory = parser.parse(jsonObject); + severity = task.calculateOSVSeverity(advisory); + Assert.assertEquals(Severity.CRITICAL, severity); + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem.json"); + advisory = parser.parse(jsonObject); + severity = task.calculateOSVSeverity(advisory); + Assert.assertEquals(Severity.MEDIUM, severity); + } + private void prepareJsonObject(String filePath) throws IOException { // parse OSV json file to Advisory object String jsonString = new String(Files.readAllBytes(Paths.get(filePath))); diff --git a/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json b/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json index 26dc926981..c57ee1dfa2 100644 --- a/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json +++ b/src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json @@ -8,7 +8,7 @@ "modified": "2022-06-09T07:01:32.587163Z", "published": "2019-03-14T15:39:30Z", "database_specific": { - "severity": "CRITICAL", + "severity": "LOW", "cwe_ids": [ "CWE-601" ], @@ -326,7 +326,7 @@ "severity": [ { "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H" } ] } \ No newline at end of file diff --git a/src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem-cvss.json b/src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem-cvss.json new file mode 100644 index 0000000000..f0e50022a6 --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem-cvss.json @@ -0,0 +1,97 @@ +{ + "id": "RUSTSEC-2022-0025", + "summary": "Resource leakage when decoding certificates and keys", + "details": "The `OPENSSL_LH_flush()` function, which empties a hash table, contains\na bug that breaks reuse of the memory occupied by the removed hash\ntable entries.\n\nThis function is used when decoding certificates or keys. If a long lived\nprocess periodically decodes certificates or keys its memory usage will\nexpand without bounds and the process might be terminated by the operating\nsystem causing a denial of service. Also traversing the empty hash table\nentries will take increasingly more time.\n\nTypically such long lived processes might be TLS clients or TLS servers\nconfigured to accept client certificate authentication.", + "aliases": [ + "CVE-2022-1473" + ], + "modified": "2022-06-20T12:19:49Z", + "published": "2022-05-03T12:00:00Z", + "references": [ + { + "type": "PACKAGE", + "url": "https://crates.io/crates/openssl-src" + }, + { + "type": "ADVISORY", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0025.html" + }, + { + "type": "WEB", + "url": "https://www.openssl.org/news/secadv/20220503.txt" + } + ], + "affected": [ + { + "package": { + "name": "openssl-src", + "ecosystem": "crates.io", + "purl": "pkg:cargo/openssl-src" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "300.0.0" + }, + { + "fixed": "300.0.6" + } + ] + } + ], + "ecosystem_specific": { + "affects": { + "functions": [], + "arch": [], + "os": [] + } + }, + "database_specific": { + "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0025.json", + "informational": null, + "categories": [ + "denial-of-service" + ] + } + }, + { + "package": { + "name": "openssl-src", + "ecosystem": "crates.io", + "purl": "pkg:cargo/openssl-src" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "300.0.0" + }, + { + "fixed": "300.0.6" + } + ] + } + ], + "ecosystem_specific": { + "affects": { + "functions": [], + "arch": [], + "os": [] + } + }, + "database_specific": { + "cvss": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", + "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0025.json", + "informational": null, + "categories": [ + "denial-of-service" + ] + } + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file diff --git a/src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem.json b/src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem.json new file mode 100644 index 0000000000..302f9ba170 --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-severity-test-ecosystem.json @@ -0,0 +1,72 @@ +{ + "id": "OSV-2021-60", + "summary": "Heap-buffer-overflow in H5O__sdspace_decode", + "details": "OSS-Fuzz report: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29517\n\n```\nCrash type: Heap-buffer-overflow READ 1\nCrash state:\nH5O__sdspace_decode\nH5O_sdspace_shared_decode\nH5O_attr_decode\n```\n", + "modified": "2022-04-13T03:04:43.038723Z", + "published": "2021-01-13T00:00:18.784221Z", + "references": [ + { + "type": "REPORT", + "url": "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29517" + } + ], + "affected": [ + { + "package": { + "name": "matio", + "ecosystem": "OSS-Fuzz", + "purl": "pkg:generic/matio" + }, + "ranges": [ + { + "type": "GIT", + "repo": "git://git.code.sf.net/p/matio/matio", + "events": [ + { + "introduced": "1ce8f2d1845ecdde19a35605cabdbb884776d52d" + }, + { + "fixed": "2f19958fade08de870257f099d88ecc813ab623f" + } + ] + } + ], + "ecosystem_specific": { + "severity": "MEDIUM" + }, + "database_specific": { + "source": "https://github.com/google/oss-fuzz-vulns/blob/main/vulns/matio/OSV-2021-60.yaml" + }, + "versions": [] + }, + { + "package": { + "name": "matio", + "ecosystem": "OSS-Fuzz", + "purl": "pkg:generic/matio" + }, + "ranges": [ + { + "type": "GIT", + "repo": "git://git.code.sf.net/p/matio/matio", + "events": [ + { + "introduced": "1ce8f2d1845ecdde19a35605cabdbb884776d52d" + }, + { + "fixed": "2f19958fade08de870257f099d88ecc813ab623f" + } + ] + } + ], + "ecosystem_specific": { + "severity": "LOW" + }, + "database_specific": { + "source": "https://github.com/google/oss-fuzz-vulns/blob/main/vulns/matio/OSV-2021-60.yaml" + }, + "versions": [] + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From cdf1e90537af06b370722e0c054067564e4196b2 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 28 Jun 2022 12:21:35 +0100 Subject: [PATCH 13/26] handle vulnerability mapping to avoid whole task Signed-off-by: Sahiba Mittal --- .../org/dependencytrack/tasks/OSVDownloadTask.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 4d7a689610..b5c19893dd 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -113,10 +113,15 @@ public void updateDatasource(final OSVAdvisory advisory) { final Vulnerability synchronizedVulnerability = qm.synchronizeVulnerability(mapAdvisoryToVulnerability(qm, advisory), false); final List vsList = new ArrayList<>(); for (OSVVulnerability osvVulnerability: advisory.getVulnerabilities()) { - VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); - if (vs != null) { - vsList.add(vs); + try { + VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); + if (vs != null) { + vsList.add(vs); + } + } catch (Exception e) { + LOGGER.error("Error while mapping the vulnerability " + osvVulnerability.getPurl()); } + } LOGGER.debug("Updating vulnerable software for OSV advisory: " + advisory.getId()); qm.persist(vsList); From 7fe4c5d72751970be02815295d162f4e95092633 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 28 Jun 2022 15:11:37 +0100 Subject: [PATCH 14/26] fix out of bound exception Signed-off-by: Sahiba Mittal --- .../parser/osv/GoogleOSVAdvisoryParser.java | 28 +++++++++++-------- .../task/OSVDownloadTaskTest.java | 6 ++++ .../osv.jsons/osv-vulnerability-no-range.json | 20 +++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 51b6d97ceb..70516dd895 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -115,29 +115,21 @@ public List parseVulnerabilityRange(JSONObject vulnerability) final JSONObject range = ranges.getJSONObject(j); osvVulnerabilityList.addAll(parseVersionRanges(vulnerability, range)); } + } else { + osvVulnerabilityList.add(createOSVVulnerability(vulnerability)); } return osvVulnerabilityList; } private List parseVersionRanges(JSONObject vulnerability, JSONObject range) { - final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); - final JSONObject ecosystemSpecific = vulnerability.optJSONObject("ecosystem_specific"); - final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); - Severity ecosystemSeverity = parseEcosystemSeverity(ecosystemSpecific, databaseSpecific); - final List osvVulnerabilityList = new ArrayList<>(); final JSONArray rangeEvents = range.optJSONArray("events"); if(rangeEvents != null) { int k = 0; while (k < rangeEvents.length()) { - OSVVulnerability osvVulnerability = new OSVVulnerability(); - osvVulnerability.setPackageName(affectedPackageJson.optString("name", null)); - osvVulnerability.setPackageEcosystem(affectedPackageJson.optString("ecosystem", null)); - osvVulnerability.setPurl(affectedPackageJson.optString("purl", null)); - osvVulnerability.setSeverity(ecosystemSeverity); - + OSVVulnerability osvVulnerability = createOSVVulnerability(vulnerability); JSONObject event = rangeEvents.getJSONObject(k); String lower = event.optString("introduced", null); if(lower != null) { @@ -158,6 +150,20 @@ private List parseVersionRanges(JSONObject vulnerability, JSON return osvVulnerabilityList; } + private OSVVulnerability createOSVVulnerability(JSONObject vulnerability) { + + OSVVulnerability osvVulnerability = new OSVVulnerability(); + final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); + final JSONObject ecosystemSpecific = vulnerability.optJSONObject("ecosystem_specific"); + final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); + Severity ecosystemSeverity = parseEcosystemSeverity(ecosystemSpecific, databaseSpecific); + osvVulnerability.setPackageName(affectedPackageJson.optString("name", null)); + osvVulnerability.setPackageEcosystem(affectedPackageJson.optString("ecosystem", null)); + osvVulnerability.setPurl(affectedPackageJson.optString("purl", null)); + osvVulnerability.setSeverity(ecosystemSeverity); + return osvVulnerability; + } + private Severity parseEcosystemSeverity(JSONObject ecosystemSpecific, JSONObject databaseSpecific) { String severity = null; diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index 98d949fb0c..a9d9a54ab2 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -120,6 +120,12 @@ public void testCalculateOSVSeverity() throws IOException { advisory = parser.parse(jsonObject); severity = task.calculateOSVSeverity(advisory); Assert.assertEquals(Severity.MEDIUM, severity); + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json"); + advisory = parser.parse(jsonObject); + Assert.assertNotNull(advisory); + severity = task.calculateOSVSeverity(advisory); + Assert.assertEquals(Severity.UNASSIGNED, severity); } private void prepareJsonObject(String filePath) throws IOException { diff --git a/src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json b/src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json new file mode 100644 index 0000000000..7a47ab4514 --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json @@ -0,0 +1,20 @@ +{ + "id": "GSD-2022-1000008", + "summary": "faker.js 6.6.6 is broken and the developer has wiped the original GitHub repo", + "details": "faker.js had it's version updated to 6.6.6 in NPM (which reports it as having 2,571 dependent packages that rely upon it) and the GitHub repo has been wiped of content. This appears to have been done intentionally as the repo only has a single commit (so it was likjely deleted, recreated and a single commit with \"endgame\" added). It appears that both GitHub and NPM have locked out the original developer accountbut that the faker.js package is still broken. Please note that this issue is directly related to GSD-2022-1000007 and appears to be part of the same incident. A fork of the repo with the original code appears to now be available at https://github.com/faker-js/faker", + "modified": "2022-01-09T11:37:01.199689Z", + "published": "2022-01-09T02:46:05.199689Z", + "affected": [ + { + "package": { + "name": "faker.js", + "ecosystem": "JavaScript" + }, + "database_specific": { + "source": "https://github.com/cloudsecurityalliance/gsd-database/blob/main/2022/1000xxx/GSD-2022-1000008.json" + }, + "versions": [] + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From 2edf945b276fde77a755c1243a0da960fb022636 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 29 Jun 2022 15:48:01 +0100 Subject: [PATCH 15/26] changes to avoid clashing with github or nvd Signed-off-by: Sahiba Mittal --- .../parser/osv/GoogleOSVAdvisoryParser.java | 7 ++- .../tasks/OSVDownloadTask.java | 45 ++++++++++++++++--- .../osv/GoogleOSVAdvisoryParserTest.java | 6 +++ .../task/OSVDownloadTaskTest.java | 44 +++++++++++++++++- .../osv.jsons/new-GHSA-77rv-6vfw-x4gc.json | 45 +++++++++++++++++++ 5 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 70516dd895..92534ff056 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -198,7 +198,10 @@ private Severity parseEcosystemSeverity(JSONObject ecosystemSpecific, JSONObject public String trimSummary(String summary) { final int MAX_LEN = 255; - // NPE safe - return StringUtils.substring(summary, 0, MAX_LEN-2) + ".."; + if(summary.length() > 255) { + // NPE safe + return StringUtils.substring(summary, 0, MAX_LEN-2) + ".."; + } + return summary; } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index b5c19893dd..119ff1d2be 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -34,6 +34,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Set; +import java.util.HashSet; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -110,8 +112,19 @@ public void updateDatasource(final OSVAdvisory advisory) { try (QueryManager qm = new QueryManager()) { LOGGER.debug("Synchronizing Google OSV advisory: " + advisory.getId()); - final Vulnerability synchronizedVulnerability = qm.synchronizeVulnerability(mapAdvisoryToVulnerability(qm, advisory), false); - final List vsList = new ArrayList<>(); + // Set to avoid duplicates + Set vsList = new HashSet<>(); + Vulnerability synchronizedVulnerability; + Vulnerability vulnerability = mapAdvisoryToVulnerability(qm, advisory); + Vulnerability existingVuln = findExistingClashingVulnerability(qm, vulnerability, advisory); + + if (existingVuln != null) { + synchronizedVulnerability = existingVuln; + vsList = new HashSet<>(existingVuln.getVulnerableSoftware()); + } else { + synchronizedVulnerability = qm.synchronizeVulnerability(vulnerability, false); + } + for (OSVVulnerability osvVulnerability: advisory.getVulnerabilities()) { try { VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); @@ -121,12 +134,11 @@ public void updateDatasource(final OSVAdvisory advisory) { } catch (Exception e) { LOGGER.error("Error while mapping the vulnerability " + osvVulnerability.getPurl()); } - } + synchronizedVulnerability.setVulnerableSoftware(new ArrayList<> (vsList)); + qm.persist(synchronizedVulnerability); LOGGER.debug("Updating vulnerable software for OSV advisory: " + advisory.getId()); qm.persist(vsList); - synchronizedVulnerability.setVulnerableSoftware(vsList); - qm.persist(synchronizedVulnerability); } Event.dispatch(new IndexEvent(IndexEvent.Action.COMMIT, Vulnerability.class)); } @@ -239,4 +251,27 @@ private VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManag vs.setVersionEndExcluding(versionEndExcluding); return vs; } + + public Vulnerability findExistingClashingVulnerability(QueryManager qm, Vulnerability vulnerability, OSVAdvisory advisory) { + + Vulnerability existing = null; + if (isVulnerabilitySourceClashingWithGithubOrNvd(vulnerability.getSource())) { + existing = qm.getVulnerabilityByVulnId(vulnerability.getSource(), vulnerability.getVulnId(), true); + } else if (advisory.getAliases() != null) { + for(String alias : advisory.getAliases()) { + String sourceOfAlias = extractSource(alias).toString(); + if(isVulnerabilitySourceClashingWithGithubOrNvd(sourceOfAlias)) { + existing = qm.getVulnerabilityByVulnId(sourceOfAlias, alias, true); + if (existing != null) break; + } + } + } + return existing; + } + + private boolean isVulnerabilitySourceClashingWithGithubOrNvd(String source) { + + return Vulnerability.Source.GITHUB.toString().equals(source) + || Vulnerability.Source.NVD.toString().equals(source); + } } \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java index e40b158ab8..f162cbff86 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -23,6 +23,12 @@ public void testTrimSummary() { String trimmedSummary = parser.trimSummary(osvLongSummary); Assert.assertNotNull(trimmedSummary); Assert.assertEquals(trimmedSummary.length(), 255); + Assert.assertEquals("In uvc_scan_chain_forward of uvc_driver.c, there is a possible linked list corruption due to an unusual root cause. This could lead to local escalation of privilege in the kernel with no additional execution privileges needed. User interaction is not ne..", trimmedSummary); + + osvLongSummary = "I'm a short Summary"; + trimmedSummary = parser.trimSummary(osvLongSummary); + Assert.assertNotNull(trimmedSummary); + Assert.assertEquals("I'm a short Summary", trimmedSummary); } @Test diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index a9d9a54ab2..700d132d0e 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -46,19 +46,31 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { // pass the mapped advisory to OSV task to update the database task.updateDatasource(advisory); var qm = new QueryManager(); + + Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); + Assert.assertNotNull(vulnerability); + var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); Assert.assertNotNull(vulnerableSoftware); Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "0"); Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.0.17"); - final Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); - Assert.assertNotNull(vulnerability); vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.1.4", "2.1.0"); Assert.assertNotNull(vulnerableSoftware); Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "2.1.0"); Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.1.4"); + + // incoming vulnerability from osv when vulnerability already exists from github + prepareJsonObject("src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json"); + advisory = parser.parse(jsonObject); + Assert.assertNotNull(advisory); + task.updateDatasource(advisory); + vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); + Assert.assertNotNull(vulnerability); + Assert.assertEquals(9, vulnerability.getVulnerableSoftware().size()); + Assert.assertEquals(Severity.CRITICAL, vulnerability.getSeverity()); } @Test @@ -128,6 +140,34 @@ public void testCalculateOSVSeverity() throws IOException { Assert.assertEquals(Severity.UNASSIGNED, severity); } + @Test + public void testFindExistingClashingVulnerability() throws IOException { + + // insert a vulnerability in database + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); + OSVAdvisory advisory = parser.parse(jsonObject); + task.updateDatasource(advisory); + var qm = new QueryManager(); + + // tests for incoming vulnerabilities if it or its alias already exists + prepareJsonObject("src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json"); + advisory = parser.parse(jsonObject); + Vulnerability vulnerabilityIncoming = task.mapAdvisoryToVulnerability(qm, advisory); + Vulnerability existingVuln = task.findExistingClashingVulnerability(qm, vulnerabilityIncoming, advisory); + Assert.assertNotNull(existingVuln); + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json"); + advisory = parser.parse(jsonObject); + vulnerabilityIncoming = task.mapAdvisoryToVulnerability(qm, advisory); + existingVuln = task.findExistingClashingVulnerability(qm, vulnerabilityIncoming, advisory); + Assert.assertNull(existingVuln); + + advisory.addAlias("GHSA-77rv-6vfw-x4gc"); + vulnerabilityIncoming = task.mapAdvisoryToVulnerability(qm, advisory); + existingVuln = task.findExistingClashingVulnerability(qm, vulnerabilityIncoming, advisory); + Assert.assertNotNull(existingVuln); + } + private void prepareJsonObject(String filePath) throws IOException { // parse OSV json file to Advisory object String jsonString = new String(Files.readAllBytes(Paths.get(filePath))); diff --git a/src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json b/src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json new file mode 100644 index 0000000000..3deb3a678b --- /dev/null +++ b/src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json @@ -0,0 +1,45 @@ +{ + "id": "GHSA-77rv-6vfw-x4gc", + "summary": "summary new", + "details": "details new", + "aliases": [ + "CVE-2019-3778" + ], + "modified": "2022-06-09T07:01:32.587163Z", + "published": "2019-03-14T15:39:30Z", + "database_specific": { + "severity": "LOW", + "cwe_ids": [ + "CWE-601" + ], + "github_reviewed": true + }, + "references": [], + "credits": [], + "affected": [ + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth2", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth2" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "3.1.0" + }, + { + "fixed": "3.3.0" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From 7fb3b420e07dd231ca232135575a7ef3577a30ac Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 30 Jun 2022 17:07:29 +0100 Subject: [PATCH 16/26] fix for commit hash ranges and small changes requested Signed-off-by: Sahiba Mittal --- .../parser/osv/GoogleOSVAdvisoryParser.java | 24 ++++-- .../parser/osv/model/OSVVulnerability.java | 10 +++ .../tasks/OSVDownloadTask.java | 19 +++-- .../osv/GoogleOSVAdvisoryParserTest.java | 73 ++++++++++++------- .../task/OSVDownloadTaskTest.java | 30 ++++++-- .../osv.jsons/osv-git-commit-hash-ranges.json | 67 +++++++++++++++++ 6 files changed, 176 insertions(+), 47 deletions(-) create mode 100644 src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 92534ff056..1170841f33 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -4,6 +4,7 @@ import kong.unirest.json.JSONObject; import org.apache.commons.lang3.StringUtils; import org.dependencytrack.model.Severity; +import org.dependencytrack.model.Vulnerability; import org.dependencytrack.parser.osv.model.OSVAdvisory; import org.dependencytrack.parser.osv.model.OSVVulnerability; import us.springett.cvss.Cvss; @@ -99,6 +100,7 @@ private List parseVulnerabilities(JSONObject object) { final JSONArray vulnerabilities = object.optJSONArray("affected"); if (vulnerabilities != null) { for(int i=0; i parseVulnerabilityRange(JSONObject vulnerability) List osvVulnerabilityList = new ArrayList<>(); final JSONArray ranges = vulnerability.optJSONArray("ranges"); - + final JSONArray versions = vulnerability.optJSONArray("versions"); if (ranges != null) { for (int j=0; j 0) { + for (int j=0; j 255) { - // NPE safe + if(summary != null && summary.length() > 255) { return StringUtils.substring(summary, 0, MAX_LEN-2) + ".."; } return summary; diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java index 305bef3c9d..b0946a88a8 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java @@ -17,6 +17,8 @@ public class OSVVulnerability { private Double cvssScore; + private String version; + public String getPackageName() { return packageName; } @@ -68,4 +70,12 @@ public Double getCvssScore() { public void setCvssScore(double cvssScore) { this.cvssScore = cvssScore; } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 119ff1d2be..b0074d9eab 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -4,6 +4,8 @@ import alpine.event.framework.Event; import alpine.event.framework.LoggableSubscriber; import alpine.model.ConfigProperty; +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; @@ -65,8 +67,6 @@ public void inform(Event e) { LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); String url = "https://osv-vulnerabilities.storage.googleapis.com/" + ecosystem.getValue() + "/all.zip"; request = new HttpGet(url); - request.setHeader("Accept", "*/*"); - request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"); try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { final StatusLine status = response.getStatusLine(); if (status.getStatusCode() == 200) { @@ -132,7 +132,7 @@ public void updateDatasource(final OSVAdvisory advisory) { vsList.add(vs); } } catch (Exception e) { - LOGGER.error("Error while mapping the vulnerability " + osvVulnerability.getPurl()); + LOGGER.error("Error while mapping vulnerable software " + osvVulnerability.getPurl(), e); } } synchronizedVulnerability.setVulnerableSoftware(new ArrayList<> (vsList)); @@ -231,13 +231,14 @@ public Vulnerability.Source extractSource(String vulnId) { } } - private VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { + public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) throws MalformedPackageURLException { String versionStartIncluding = vuln.getLowerVersionRange(); String versionEndExcluding = vuln.getUpperVersionRange(); - final String purl = vuln.getPurl(); - if (purl == null) return null; + if (vuln.getPurl() == null) return null; + + PackageURL purl = new PackageURL(vuln.getPurl()); VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(vuln.getPurl(), versionEndExcluding, versionStartIncluding); if (vs != null) { @@ -245,10 +246,14 @@ private VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManag } vs = new VulnerableSoftware(); vs.setVulnerable(true); - vs.setPurlType(vuln.getPackageEcosystem()); + vs.setPurlType(purl.getType()); + vs.setPurlNamespace(purl.getNamespace()); + vs.setPurlName(purl.getName()); vs.setPurl(vuln.getPurl()); + vs.setVersion(vuln.getVersion()); vs.setVersionStartIncluding(versionStartIncluding); vs.setVersionEndExcluding(versionEndExcluding); + return vs; } diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java index f162cbff86..3a7da9f0f6 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -22,25 +22,29 @@ public void testTrimSummary() { String osvLongSummary = "In uvc_scan_chain_forward of uvc_driver.c, there is a possible linked list corruption due to an unusual root cause. This could lead to local escalation of privilege in the kernel with no additional execution privileges needed. User interaction is not needed for exploitation."; String trimmedSummary = parser.trimSummary(osvLongSummary); Assert.assertNotNull(trimmedSummary); - Assert.assertEquals(trimmedSummary.length(), 255); + Assert.assertEquals(255, trimmedSummary.length()); Assert.assertEquals("In uvc_scan_chain_forward of uvc_driver.c, there is a possible linked list corruption due to an unusual root cause. This could lead to local escalation of privilege in the kernel with no additional execution privileges needed. User interaction is not ne..", trimmedSummary); osvLongSummary = "I'm a short Summary"; trimmedSummary = parser.trimSummary(osvLongSummary); Assert.assertNotNull(trimmedSummary); Assert.assertEquals("I'm a short Summary", trimmedSummary); + + osvLongSummary = null; + trimmedSummary = parser.trimSummary(osvLongSummary); + Assert.assertNull(trimmedSummary); } @Test public void testVulnerabilityRangeEmpty() throws IOException { - String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; + String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json"; String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(0)); Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(osvVulnerabilityList.size(), 0); + Assert.assertEquals(1, osvVulnerabilityList.size()); } @Test @@ -52,12 +56,12 @@ public void testVulnerabilityRangeSingle() throws IOException { final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(1)); Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(osvVulnerabilityList.size(), 1); + Assert.assertEquals(1, osvVulnerabilityList.size()); OSVVulnerability vuln = osvVulnerabilityList.get(0); - Assert.assertEquals(vuln.getPurl(), "pkg:maven/org.springframework.security.oauth/spring-security-oauth"); - Assert.assertEquals(vuln.getLowerVersionRange(), "0"); - Assert.assertEquals(vuln.getUpperVersionRange(), "2.0.17"); - Assert.assertEquals(vuln.getPackageEcosystem(), "Maven"); + Assert.assertEquals("pkg:maven/org.springframework.security.oauth/spring-security-oauth", vuln.getPurl()); + Assert.assertEquals("0", vuln.getLowerVersionRange()); + Assert.assertEquals("2.0.17", vuln.getUpperVersionRange()); + Assert.assertEquals("Maven", vuln.getPackageEcosystem()); } @@ -72,22 +76,22 @@ public void testVulnerabilityRangeMultiple() throws IOException { // range test full pairs List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(2)); Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(osvVulnerabilityList.size(), 3); - Assert.assertEquals(osvVulnerabilityList.get(0).getLowerVersionRange(), "1"); - Assert.assertEquals(osvVulnerabilityList.get(0).getUpperVersionRange(), "2"); - Assert.assertEquals(osvVulnerabilityList.get(1).getLowerVersionRange(), "3"); - Assert.assertEquals(osvVulnerabilityList.get(1).getUpperVersionRange(), "4"); + Assert.assertEquals(3, osvVulnerabilityList.size()); + Assert.assertEquals("1", osvVulnerabilityList.get(0).getLowerVersionRange()); + Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRange()); + Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); + Assert.assertEquals("4", osvVulnerabilityList.get(1).getUpperVersionRange()); // range test half pairs osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(3)); Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(osvVulnerabilityList.size(), 3); - Assert.assertEquals(osvVulnerabilityList.get(0).getLowerVersionRange(), null); - Assert.assertEquals(osvVulnerabilityList.get(0).getUpperVersionRange(), "2"); - Assert.assertEquals(osvVulnerabilityList.get(1).getLowerVersionRange(), "3"); - Assert.assertEquals(osvVulnerabilityList.get(1).getUpperVersionRange(), null); - Assert.assertEquals(osvVulnerabilityList.get(2).getLowerVersionRange(), "4"); - Assert.assertEquals(osvVulnerabilityList.get(2).getUpperVersionRange(), "5"); + Assert.assertEquals(null, osvVulnerabilityList.get(0).getLowerVersionRange()); + Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRange()); + Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); + Assert.assertEquals(null, osvVulnerabilityList.get(1).getUpperVersionRange()); + Assert.assertEquals("4", osvVulnerabilityList.get(2).getLowerVersionRange()); + Assert.assertEquals("5", osvVulnerabilityList.get(2).getUpperVersionRange()); } @Test @@ -98,14 +102,27 @@ public void testParseOSVJson() throws IOException { JSONObject jsonObject = new JSONObject(jsonString); OSVAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); - Assert.assertEquals(advisory.getId(), "GHSA-77rv-6vfw-x4gc"); - Assert.assertEquals(advisory.getSeverity(), "LOW"); - Assert.assertEquals(advisory.getCweIds().size(), 1); - Assert.assertEquals(advisory.getReferences().size(), 6); - Assert.assertEquals(advisory.getCredits().size(), 2); - Assert.assertEquals(advisory.getVulnerabilities().size(), 8); - Assert.assertEquals(advisory.getCvssV3Vector(), "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H"); - Assert.assertEquals(advisory.getAliases().get(0), "CVE-2019-3778"); - Assert.assertEquals(advisory.getModified().toString(), "2022-06-09T07:01:32.587163Z"); + Assert.assertEquals("GHSA-77rv-6vfw-x4gc", advisory.getId()); + Assert.assertEquals("LOW", advisory.getSeverity()); + Assert.assertEquals(1, advisory.getCweIds().size()); + Assert.assertEquals(6, advisory.getReferences().size()); + Assert.assertEquals(2, advisory.getCredits().size()); + Assert.assertEquals(8, advisory.getVulnerabilities().size()); + Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", advisory.getCvssV3Vector()); + Assert.assertEquals("CVE-2019-3778", advisory.getAliases().get(0)); + Assert.assertEquals("2022-06-09T07:01:32.587163Z", advisory.getModified().toString()); + } + + @Test + public void testCommitHashRanges() throws IOException { + + String jsonFile = "src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); + JSONObject jsonObject = new JSONObject(jsonString); + OSVAdvisory advisory = parser.parse(jsonObject); + Assert.assertNotNull(advisory); + Assert.assertEquals("OSV-2021-1820", advisory.getId()); + Assert.assertEquals(22, advisory.getVulnerabilities().size()); + Assert.assertEquals("4.4.0", advisory.getVulnerabilities().get(0).getVersion()); } } diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index 700d132d0e..0982b01e9b 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -41,7 +41,7 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); OSVAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); - Assert.assertEquals(advisory.getVulnerabilities().size(), 8); + Assert.assertEquals(8, advisory.getVulnerabilities().size()); // pass the mapped advisory to OSV task to update the database task.updateDatasource(advisory); @@ -52,15 +52,16 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); Assert.assertNotNull(vulnerableSoftware); - Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); - Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "0"); - Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.0.17"); + Assert.assertEquals("maven", vulnerableSoftware.getPurlType()); + Assert.assertEquals("org.springframework.security.oauth", vulnerableSoftware.getPurlNamespace()); + Assert.assertEquals("spring-security-oauth", vulnerableSoftware.getPurlName()); + Assert.assertEquals("0", vulnerableSoftware.getVersionStartIncluding()); + Assert.assertEquals("2.0.17", vulnerableSoftware.getVersionEndExcluding()); vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.1.4", "2.1.0"); Assert.assertNotNull(vulnerableSoftware); - Assert.assertEquals(vulnerableSoftware.getPurlType(), "Maven"); - Assert.assertEquals(vulnerableSoftware.getVersionStartIncluding(), "2.1.0"); - Assert.assertEquals(vulnerableSoftware.getVersionEndExcluding(), "2.1.4"); + Assert.assertEquals("2.1.0", vulnerableSoftware.getVersionStartIncluding()); + Assert.assertEquals("2.1.4", vulnerableSoftware.getVersionEndExcluding()); // incoming vulnerability from osv when vulnerability already exists from github prepareJsonObject("src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json"); @@ -168,6 +169,21 @@ public void testFindExistingClashingVulnerability() throws IOException { Assert.assertNotNull(existingVuln); } + @Test + public void testCommitHashRangesAndVersions() throws IOException { + + // insert a vulnerability in database + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json"); + OSVAdvisory advisory = parser.parse(jsonObject); + task.updateDatasource(advisory); + var qm = new QueryManager(); + + Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GOOGLE", "OSV-2021-1820", true); + Assert.assertNotNull(vulnerability); + Assert.assertEquals(22, vulnerability.getVulnerableSoftware().size()); + Assert.assertEquals(Severity.MEDIUM, vulnerability.getSeverity()); + } + private void prepareJsonObject(String filePath) throws IOException { // parse OSV json file to Advisory object String jsonString = new String(Files.readAllBytes(Paths.get(filePath))); diff --git a/src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json b/src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json new file mode 100644 index 0000000000..d73696139f --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json @@ -0,0 +1,67 @@ +{ + "id": "OSV-2021-1820", + "summary": "Heap-buffer-overflow in r_str_utf8_codepoint", + "details": "OSS-Fuzz report: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48098\n\n```\nCrash type: Heap-buffer-overflow READ 1\nCrash state:\nr_str_utf8_codepoint\nr_str_char_fullwidth\nr_str_len_utf8_ansi\n```\n", + "modified": "2022-06-19T00:00:52.240913Z", + "published": "2022-06-19T00:00:52.240583Z", + "references": [ + { + "type": "REPORT", + "url": "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48098" + } + ], + "affected": [ + { + "package": { + "name": "radare2", + "ecosystem": "OSS-Fuzz", + "purl": "pkg:generic/radare2" + }, + "ranges": [ + { + "type": "GIT", + "repo": "https://github.com/radareorg/radare2", + "events": [ + { + "introduced": "775f2b3d8d6d44f3312f9911dcf70b203268f387" + }, + { + "fixed": "2cfc1d7e12adf87a28ac78abb32233cf39c6d54a" + } + ] + } + ], + "versions": [ + "4.4.0", + "4.5.1", + "5.0.0", + "5.1.0", + "5.1.1", + "5.2.0", + "5.2.1", + "5.3.0", + "5.3.1", + "5.4.0", + "5.4.0-git", + "5.4.2", + "5.5.0", + "5.5.2", + "5.5.4", + "5.6.0", + "5.6.2", + "5.6.4", + "5.6.6", + "5.6.8", + "5.7.0", + "release-5.0.0" + ], + "ecosystem_specific": { + "severity": "MEDIUM" + }, + "database_specific": { + "source": "https://github.com/google/oss-fuzz-vulns/blob/main/vulns/radare2/OSV-2021-1820.yaml" + } + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From d8f836af8d69b9776f807a4a86fef072ca2fcff0 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 1 Jul 2022 11:41:26 +0100 Subject: [PATCH 17/26] handle purl parsing Signed-off-by: Sahiba Mittal --- .../tasks/OSVDownloadTask.java | 49 +++++++++---------- .../task/OSVDownloadTaskTest.java | 13 +++++ .../unit/osv.jsons/osv-invalid-purl.json | 46 +++++++++++++++++ 3 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 src/test/resources/unit/osv.jsons/osv-invalid-purl.json diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index b0074d9eab..8dfca736c2 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -126,13 +126,9 @@ public void updateDatasource(final OSVAdvisory advisory) { } for (OSVVulnerability osvVulnerability: advisory.getVulnerabilities()) { - try { - VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); - if (vs != null) { - vsList.add(vs); - } - } catch (Exception e) { - LOGGER.error("Error while mapping vulnerable software " + osvVulnerability.getPurl(), e); + VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); + if (vs != null) { + vsList.add(vs); } } synchronizedVulnerability.setVulnerableSoftware(new ArrayList<> (vsList)); @@ -231,30 +227,33 @@ public Vulnerability.Source extractSource(String vulnId) { } } - public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) throws MalformedPackageURLException { + public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { String versionStartIncluding = vuln.getLowerVersionRange(); String versionEndExcluding = vuln.getUpperVersionRange(); - if (vuln.getPurl() == null) return null; - - PackageURL purl = new PackageURL(vuln.getPurl()); - - VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(vuln.getPurl(), versionEndExcluding, versionStartIncluding); - if (vs != null) { + try { + VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(vuln.getPurl(), versionEndExcluding, versionStartIncluding); + if (vs != null) { + return vs; + } + if (vuln.getPurl() == null) return null; + vs = new VulnerableSoftware(); + PackageURL purl = new PackageURL(vuln.getPurl()); + vs.setPurlType(purl.getType()); + vs.setPurlNamespace(purl.getNamespace()); + vs.setPurlName(purl.getName()); + vs.setVulnerable(true); + vs.setPurl(vuln.getPurl()); + vs.setVersion(vuln.getVersion()); + vs.setVersionStartIncluding(versionStartIncluding); + vs.setVersionEndExcluding(versionEndExcluding); return vs; + + } catch (Exception e) { + LOGGER.error("Error while mapping vulnerable software " + vuln.getPurl(), e); + return null; } - vs = new VulnerableSoftware(); - vs.setVulnerable(true); - vs.setPurlType(purl.getType()); - vs.setPurlNamespace(purl.getNamespace()); - vs.setPurlName(purl.getName()); - vs.setPurl(vuln.getPurl()); - vs.setVersion(vuln.getVersion()); - vs.setVersionStartIncluding(versionStartIncluding); - vs.setVersionEndExcluding(versionEndExcluding); - - return vs; } public Vulnerability findExistingClashingVulnerability(QueryManager qm, Vulnerability vulnerability, OSVAdvisory advisory) { diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java index 0982b01e9b..ea50a2b832 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java @@ -88,6 +88,19 @@ public void testParseAdvisoryToVulnerability() throws IOException { Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", vuln.getCvssV3Vector()); } + @Test + public void testParseAdvisoryToVulnerabilityWithInvalidPurl() throws IOException { + + prepareJsonObject("src/test/resources/unit/osv.jsons/osv-invalid-purl.json"); + OSVAdvisory advisory = parser.parse(jsonObject); + task.updateDatasource(advisory); + Assert.assertNotNull(advisory); + Vulnerability vuln = qm.getVulnerabilityByVulnId("GOOGLE", "OSV-2021-60", true); + Assert.assertNotNull(vuln); + Assert.assertEquals(Severity.MEDIUM, vuln.getSeverity()); + Assert.assertEquals(1, vuln.getVulnerableSoftware().size()); + } + @Test public void testWithdrawnAdvisory() throws Exception { diff --git a/src/test/resources/unit/osv.jsons/osv-invalid-purl.json b/src/test/resources/unit/osv.jsons/osv-invalid-purl.json new file mode 100644 index 0000000000..d4ab03b0ae --- /dev/null +++ b/src/test/resources/unit/osv.jsons/osv-invalid-purl.json @@ -0,0 +1,46 @@ +{ + "id": "OSV-2021-60", + "summary": "Heap-buffer-overflow in H5O__sdspace_decode", + "details": "OSS-Fuzz report: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29517\n\n```\nCrash type: Heap-buffer-overflow READ 1\nCrash state:\nH5O__sdspace_decode\nH5O_sdspace_shared_decode\nH5O_attr_decode\n```\n", + "modified": "2022-04-13T03:04:43.038723Z", + "published": "2021-01-13T00:00:18.784221Z", + "references": [ + { + "type": "REPORT", + "url": "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29517" + } + ], + "affected": [ + { + "package": { + "name": "matio", + "ecosystem": "OSS-Fuzz", + "purl": "pkg:npm/@hapi/hoek" + }, + "ranges": [], + "ecosystem_specific": { + "severity": "MEDIUM" + }, + "database_specific": { + "source": "https://github.com/google/oss-fuzz-vulns/blob/main/vulns/matio/OSV-2021-60.yaml" + }, + "versions": [] + }, + { + "package": { + "name": "matio", + "ecosystem": "OSS-Fuzz", + "purl": "pkg:npm/hapi/hoek" + }, + "ranges": [], + "ecosystem_specific": { + "severity": "MEDIUM" + }, + "database_specific": { + "source": "https://github.com/google/oss-fuzz-vulns/blob/main/vulns/matio/OSV-2021-60.yaml" + }, + "versions": [] + } + ], + "schema_version": "1.2.0" +} \ No newline at end of file From 985a58f845cc78a9349a670cf96bd5bfef5118ef Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 1 Jul 2022 13:46:37 +0100 Subject: [PATCH 18/26] handle version range types, disable default osv Signed-off-by: Sahiba Mittal --- .../model/ConfigPropertyConstants.java | 2 +- .../parser/osv/GoogleOSVAdvisoryParser.java | 23 +++++- .../parser/osv/model/OSVVulnerability.java | 20 +++-- .../tasks/OSVDownloadTask.java | 9 ++- .../osv/GoogleOSVAdvisoryParserTest.java | 38 +++++++-- .../osv-vulnerability-with-ranges.json | 78 +++++++++++++++++++ 6 files changed, 152 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java index 954cfab756..cfec529f88 100644 --- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java +++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java @@ -48,7 +48,7 @@ public enum ConfigPropertyConstants { VULNERABILITY_SOURCE_NVD_FEEDS_URL("vuln-source", "nvd.feeds.url", "https://nvd.nist.gov/feeds", PropertyType.URL, "A base URL pointing to the hostname and path of the NVD feeds"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ENABLED("vuln-source", "github.advisories.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable GitHub Advisories"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ACCESS_TOKEN("vuln-source", "github.advisories.access.token", null, PropertyType.STRING, "The access token used for GitHub API authentication"), - VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Google OSV"), + VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable Google OSV"), VULNERABILITY_SOURCE_EPSS_ENABLED("vuln-source", "epss.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Exploit Prediction Scoring System"), VULNERABILITY_SOURCE_EPSS_FEEDS_URL("vuln-source", "epss.feeds.url", "https://epss.cyentia.com", PropertyType.URL, "A base URL pointing to the hostname and path of the EPSS feeds"), ACCEPT_ARTIFACT_CYCLONEDX("artifact", "cyclonedx.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable the systems ability to accept CycloneDX uploads"), diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java index 1170841f33..79e9aec959 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java @@ -140,6 +140,7 @@ private List parseVersionRanges(JSONObject vulnerability, JSON final List osvVulnerabilityList = new ArrayList<>(); final JSONArray rangeEvents = range.optJSONArray("events"); + final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); if(rangeEvents != null) { int k = 0; while (k < rangeEvents.length()) { @@ -153,12 +154,28 @@ private List parseVersionRanges(JSONObject vulnerability, JSON } if(k < rangeEvents.length()) { event = rangeEvents.getJSONObject(k); - String upper = event.optString("fixed", null); - if(upper != null) { - osvVulnerability.setUpperVersionRange(upper); + String fixed = event.optString("fixed", null); + String lastAffected = event.optString("last_affected", null); + String limit = event.optString("limit", null); + if (fixed != null) { + osvVulnerability.setUpperVersionRangeExcluding(fixed); + k += 1; + } else if (lastAffected != null){ + osvVulnerability.setUpperVersionRangeIncluding(lastAffected); + k += 1; + } else if (limit != null) { + osvVulnerability.setUpperVersionRangeExcluding(limit); k += 1; } } + if (osvVulnerability.getUpperVersionRangeIncluding() == null + && osvVulnerability.getUpperVersionRangeExcluding() == null + && databaseSpecific != null) { + String lastAffected = databaseSpecific.optString("last_known_affected_version_range", null); + if (lastAffected != null) { + osvVulnerability.setUpperVersionRangeIncluding(lastAffected.replaceAll("[^0-9.]+", "").trim()); + } + } osvVulnerabilityList.add(osvVulnerability); } } diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java index b0946a88a8..247aa84adc 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java @@ -11,7 +11,9 @@ public class OSVVulnerability { private String lowerVersionRange; - private String upperVersionRange; + private String upperVersionRangeExcluding; + + private String upperVersionRangeIncluding; private Severity severity; @@ -49,11 +51,11 @@ public String getLowerVersionRange() { public void setLowerVersionRange(String lowerVersionRange) { this.lowerVersionRange = lowerVersionRange; } - public String getUpperVersionRange() { - return upperVersionRange; + public String getUpperVersionRangeExcluding() { + return upperVersionRangeExcluding; } - public void setUpperVersionRange(String upperVersionRange) { - this.upperVersionRange = upperVersionRange; + public void setUpperVersionRangeExcluding(String upperVersionRange) { + this.upperVersionRangeExcluding = upperVersionRange; } public Severity getSeverity() { @@ -78,4 +80,12 @@ public String getVersion() { public void setVersion(String version) { this.version = version; } + + public String getUpperVersionRangeIncluding() { + return upperVersionRangeIncluding; + } + + public void setUpperVersionRangeIncluding(String upperVersionRangeIncluding) { + this.upperVersionRangeIncluding = upperVersionRangeIncluding; + } } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 8dfca736c2..815685fe38 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -230,16 +230,18 @@ public Vulnerability.Source extractSource(String vulnId) { public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { String versionStartIncluding = vuln.getLowerVersionRange(); - String versionEndExcluding = vuln.getUpperVersionRange(); + String versionEndExcluding = vuln.getUpperVersionRangeExcluding(); + String versionEndIncluding = vuln.getUpperVersionRangeIncluding(); try { - VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(vuln.getPurl(), versionEndExcluding, versionStartIncluding); + PackageURL purl = new PackageURL(vuln.getPurl()); + VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(purl.getType(), purl.getNamespace(), purl.getName(), + versionEndExcluding, versionEndIncluding, null, versionStartIncluding); if (vs != null) { return vs; } if (vuln.getPurl() == null) return null; vs = new VulnerableSoftware(); - PackageURL purl = new PackageURL(vuln.getPurl()); vs.setPurlType(purl.getType()); vs.setPurlNamespace(purl.getNamespace()); vs.setPurlName(purl.getName()); @@ -248,6 +250,7 @@ public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManage vs.setVersion(vuln.getVersion()); vs.setVersionStartIncluding(versionStartIncluding); vs.setVersionEndExcluding(versionEndExcluding); + vs.setVersionEndIncluding(versionEndIncluding); return vs; } catch (Exception e) { diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java index 3a7da9f0f6..54b11080fb 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -60,7 +60,7 @@ public void testVulnerabilityRangeSingle() throws IOException { OSVVulnerability vuln = osvVulnerabilityList.get(0); Assert.assertEquals("pkg:maven/org.springframework.security.oauth/spring-security-oauth", vuln.getPurl()); Assert.assertEquals("0", vuln.getLowerVersionRange()); - Assert.assertEquals("2.0.17", vuln.getUpperVersionRange()); + Assert.assertEquals("2.0.17", vuln.getUpperVersionRangeExcluding()); Assert.assertEquals("Maven", vuln.getPackageEcosystem()); } @@ -78,20 +78,46 @@ public void testVulnerabilityRangeMultiple() throws IOException { Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(3, osvVulnerabilityList.size()); Assert.assertEquals("1", osvVulnerabilityList.get(0).getLowerVersionRange()); - Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRange()); + Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); - Assert.assertEquals("4", osvVulnerabilityList.get(1).getUpperVersionRange()); + Assert.assertEquals("4", osvVulnerabilityList.get(1).getUpperVersionRangeExcluding()); // range test half pairs osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(3)); Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(osvVulnerabilityList.size(), 3); Assert.assertEquals(null, osvVulnerabilityList.get(0).getLowerVersionRange()); - Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRange()); + Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); - Assert.assertEquals(null, osvVulnerabilityList.get(1).getUpperVersionRange()); + Assert.assertEquals(null, osvVulnerabilityList.get(1).getUpperVersionRangeExcluding()); Assert.assertEquals("4", osvVulnerabilityList.get(2).getLowerVersionRange()); - Assert.assertEquals("5", osvVulnerabilityList.get(2).getUpperVersionRange()); + Assert.assertEquals("5", osvVulnerabilityList.get(2).getUpperVersionRangeExcluding()); + } + + @Test + public void testVulnerabilityRangeTypes() throws IOException { + + String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; + String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); + JSONObject jsonObject = new JSONObject(jsonString); + final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); + + // type last_affected + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(4)); + Assert.assertNotNull(osvVulnerabilityList); + Assert.assertEquals(1, osvVulnerabilityList.size()); + Assert.assertEquals("10", osvVulnerabilityList.get(0).getLowerVersionRange()); + Assert.assertEquals(null, osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); + Assert.assertEquals("11", osvVulnerabilityList.get(0).getUpperVersionRangeIncluding()); + + // type last_affected + osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(6)); + Assert.assertNotNull(osvVulnerabilityList); + Assert.assertEquals(1, osvVulnerabilityList.size()); + Assert.assertEquals("10", osvVulnerabilityList.get(0).getLowerVersionRange()); + Assert.assertEquals(null, osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); + Assert.assertEquals("29.0", osvVulnerabilityList.get(0).getUpperVersionRangeIncluding()); + } @Test diff --git a/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json b/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json index 6820d0db6f..fc0f11e16b 100644 --- a/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json +++ b/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json @@ -118,6 +118,84 @@ "database_specific": { "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "10" + }, + { + "last_affected": "11" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "10" + }, + { + "limit": "13" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "source": "https://github.com/oliverchang/advisory-database/blob/main/advisories/github-reviewed/2019/03/GHSA-77rv-6vfw-x4gc/GHSA-77rv-6vfw-x4gc.json" + } + }, + { + "package": { + "name": "org.springframework.security.oauth:spring-security-oauth", + "ecosystem": "Maven", + "purl": "pkg:maven/org.springframework.security.oauth/spring-security-oauth" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "10" + } + ] + } + ], + "versions": [ + "1.0.0.RELEASE", + "2.0.9.RELEASE" + ], + "database_specific": { + "last_known_affected_version_range": "<= 29.0" + } } ] } \ No newline at end of file From 94072d539363476ba057ef9cc52f9e027e4582f7 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 1 Jul 2022 14:58:42 +0100 Subject: [PATCH 19/26] fix de duplication of vulnerable softwares Signed-off-by: Sahiba Mittal --- .../org/dependencytrack/tasks/OSVDownloadTask.java | 14 +++++++------- .../parser/osv/GoogleOSVAdvisoryParserTest.java | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 815685fe38..8915eccbb5 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -4,7 +4,6 @@ import alpine.event.framework.Event; import alpine.event.framework.LoggableSubscriber; import alpine.model.ConfigProperty; -import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; import org.apache.http.StatusLine; @@ -36,8 +35,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Set; -import java.util.HashSet; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -112,15 +109,14 @@ public void updateDatasource(final OSVAdvisory advisory) { try (QueryManager qm = new QueryManager()) { LOGGER.debug("Synchronizing Google OSV advisory: " + advisory.getId()); - // Set to avoid duplicates - Set vsList = new HashSet<>(); + final List vsList = new ArrayList<>(); Vulnerability synchronizedVulnerability; Vulnerability vulnerability = mapAdvisoryToVulnerability(qm, advisory); Vulnerability existingVuln = findExistingClashingVulnerability(qm, vulnerability, advisory); if (existingVuln != null) { synchronizedVulnerability = existingVuln; - vsList = new HashSet<>(existingVuln.getVulnerableSoftware()); + vsList.addAll(existingVuln.getVulnerableSoftware()); } else { synchronizedVulnerability = qm.synchronizeVulnerability(vulnerability, false); } @@ -128,7 +124,11 @@ public void updateDatasource(final OSVAdvisory advisory) { for (OSVVulnerability osvVulnerability: advisory.getVulnerabilities()) { VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); if (vs != null) { - vsList.add(vs); + // check if it already exists or not + VulnerableSoftware existingVulnSoftware = qm.getVulnerableSoftwareByPurl(vs.getPurlType(), vs.getPurlNamespace(), vs.getPurlName(), vs.getVersionEndExcluding(), vs.getVersionEndIncluding(), vs.getVersionStartExcluding(), vs.getVersionStartIncluding()); + if(existingVulnSoftware == null) { + vsList.add(vs); + } } } synchronizedVulnerability.setVulnerableSoftware(new ArrayList<> (vsList)); diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java index 54b11080fb..cbb365948f 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java @@ -85,7 +85,7 @@ public void testVulnerabilityRangeMultiple() throws IOException { // range test half pairs osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(3)); Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(osvVulnerabilityList.size(), 3); + Assert.assertEquals(3, osvVulnerabilityList.size()); Assert.assertEquals(null, osvVulnerabilityList.get(0).getLowerVersionRange()); Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); From 7b0afeee5332b329f8beaa273c6934878deff133 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 1 Jul 2022 15:01:10 +0100 Subject: [PATCH 20/26] small test fix Signed-off-by: Sahiba Mittal --- .../dependencytrack/persistence/DefaultObjectGeneratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java b/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java index e378971287..b4c4a8e478 100644 --- a/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java +++ b/src/test/java/org/dependencytrack/persistence/DefaultObjectGeneratorTest.java @@ -78,7 +78,7 @@ public void testLoadDefaultConfigProperties() throws Exception { Method method = generator.getClass().getDeclaredMethod("loadDefaultConfigProperties"); method.setAccessible(true); method.invoke(generator); - Assert.assertEquals(43, qm.getConfigProperties().size()); + Assert.assertEquals(44, qm.getConfigProperties().size()); } @Test From 0477ecde62dcbde98ac0c300d1f45ffb9bcbc150 Mon Sep 17 00:00:00 2001 From: nscuro Date: Sat, 2 Jul 2022 17:12:52 +0200 Subject: [PATCH 21/26] Perform `null` check before parsing PURLs Signed-off-by: nscuro --- .../tasks/OSVDownloadTask.java | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java index 8915eccbb5..457d78b927 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java @@ -4,6 +4,7 @@ import alpine.event.framework.Event; import alpine.event.framework.LoggableSubscriber; import alpine.model.ConfigProperty; +import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; import org.apache.http.StatusLine; @@ -228,35 +229,39 @@ public Vulnerability.Source extractSource(String vulnId) { } public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { + if (vuln.getPurl() == null) { + LOGGER.debug("No PURL provided for affected package " + vuln.getPackageName() + " - skipping"); + return null; + } - String versionStartIncluding = vuln.getLowerVersionRange(); - String versionEndExcluding = vuln.getUpperVersionRangeExcluding(); - String versionEndIncluding = vuln.getUpperVersionRangeIncluding(); - + final PackageURL purl; try { - PackageURL purl = new PackageURL(vuln.getPurl()); - VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(purl.getType(), purl.getNamespace(), purl.getName(), - versionEndExcluding, versionEndIncluding, null, versionStartIncluding); - if (vs != null) { - return vs; - } - if (vuln.getPurl() == null) return null; - vs = new VulnerableSoftware(); - vs.setPurlType(purl.getType()); - vs.setPurlNamespace(purl.getNamespace()); - vs.setPurlName(purl.getName()); - vs.setVulnerable(true); - vs.setPurl(vuln.getPurl()); - vs.setVersion(vuln.getVersion()); - vs.setVersionStartIncluding(versionStartIncluding); - vs.setVersionEndExcluding(versionEndExcluding); - vs.setVersionEndIncluding(versionEndIncluding); - return vs; - - } catch (Exception e) { - LOGGER.error("Error while mapping vulnerable software " + vuln.getPurl(), e); + purl = new PackageURL(vuln.getPurl()); + } catch (MalformedPackageURLException e) { + LOGGER.debug("Invalid PURL provided for affected package " + vuln.getPackageName() + " - skipping", e); return null; } + + final String versionStartIncluding = vuln.getLowerVersionRange(); + final String versionEndExcluding = vuln.getUpperVersionRangeExcluding(); + final String versionEndIncluding = vuln.getUpperVersionRangeIncluding(); + + VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(purl.getType(), purl.getNamespace(), purl.getName(), + versionEndExcluding, versionEndIncluding, null, versionStartIncluding); + if (vs != null) { + return vs; + } + + vs = new VulnerableSoftware(); + vs.setPurlType(purl.getType()); + vs.setPurlNamespace(purl.getNamespace()); + vs.setPurlName(purl.getName()); + vs.setVulnerable(true); + vs.setVersion(vuln.getVersion()); + vs.setVersionStartIncluding(versionStartIncluding); + vs.setVersionEndExcluding(versionEndExcluding); + vs.setVersionEndIncluding(versionEndIncluding); + return vs; } public Vulnerability findExistingClashingVulnerability(QueryManager qm, Vulnerability vulnerability, OSVAdvisory advisory) { From a8aba4583fbb955114045e383c710bb78e5a28bf Mon Sep 17 00:00:00 2001 From: nscuro Date: Sat, 2 Jul 2022 17:52:09 +0200 Subject: [PATCH 22/26] Adjust class names to rest of the code base Signed-off-by: nscuro --- .../event/EventSubsystemInitializer.java | 6 ++-- ...SVMirrorEvent.java => OsvMirrorEvent.java} | 2 +- ...soryParser.java => OsvAdvisoryParser.java} | 35 +++++++++---------- .../{OSVAdvisory.java => OsvAdvisory.java} | 8 ++--- ...lnerability.java => OsvVulnerability.java} | 2 +- ...DownloadTask.java => OsvDownloadTask.java} | 34 +++++++++--------- .../dependencytrack/tasks/TaskScheduler.java | 4 +-- ...erTest.java => OsvAdvisoryParserTest.java} | 22 ++++++------ ...TaskTest.java => OsvDownloadTaskTest.java} | 26 +++++++------- 9 files changed, 69 insertions(+), 70 deletions(-) rename src/main/java/org/dependencytrack/event/{GoogleOSVMirrorEvent.java => OsvMirrorEvent.java} (72%) rename src/main/java/org/dependencytrack/parser/osv/{GoogleOSVAdvisoryParser.java => OsvAdvisoryParser.java} (90%) rename src/main/java/org/dependencytrack/parser/osv/model/{OSVAdvisory.java => OsvAdvisory.java} (94%) rename src/main/java/org/dependencytrack/parser/osv/model/{OSVVulnerability.java => OsvVulnerability.java} (98%) rename src/main/java/org/dependencytrack/tasks/{OSVDownloadTask.java => OsvDownloadTask.java} (92%) rename src/test/java/org/dependencytrack/parser/osv/{GoogleOSVAdvisoryParserTest.java => OsvAdvisoryParserTest.java} (92%) rename src/test/java/org/dependencytrack/task/{OSVDownloadTaskTest.java => OsvDownloadTaskTest.java} (91%) diff --git a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java index 809a651a0b..98a8b814ce 100644 --- a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java +++ b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java @@ -31,7 +31,7 @@ import org.dependencytrack.tasks.EpssMirrorTask; import org.dependencytrack.tasks.FortifySscUploadTask; import org.dependencytrack.tasks.GitHubAdvisoryMirrorTask; -import org.dependencytrack.tasks.OSVDownloadTask; +import org.dependencytrack.tasks.OsvDownloadTask; import org.dependencytrack.tasks.IndexTask; import org.dependencytrack.tasks.InternalComponentIdentificationTask; import org.dependencytrack.tasks.KennaSecurityUploadTask; @@ -81,7 +81,7 @@ public void contextInitialized(final ServletContextEvent event) { EVENT_SERVICE.subscribe(InternalAnalysisEvent.class, InternalAnalysisTask.class); EVENT_SERVICE.subscribe(OssIndexAnalysisEvent.class, OssIndexAnalysisTask.class); EVENT_SERVICE.subscribe(GitHubAdvisoryMirrorEvent.class, GitHubAdvisoryMirrorTask.class); - EVENT_SERVICE.subscribe(GoogleOSVMirrorEvent.class, OSVDownloadTask.class); + EVENT_SERVICE.subscribe(OsvMirrorEvent.class, OsvDownloadTask.class); EVENT_SERVICE.subscribe(VulnDbSyncEvent.class, VulnDbSyncTask.class); EVENT_SERVICE.subscribe(VulnDbAnalysisEvent.class, VulnDbAnalysisTask.class); EVENT_SERVICE.subscribe(VulnerabilityAnalysisEvent.class, VulnerabilityAnalysisTask.class); @@ -116,7 +116,7 @@ public void contextDestroyed(final ServletContextEvent event) { EVENT_SERVICE.unsubscribe(InternalAnalysisTask.class); EVENT_SERVICE.unsubscribe(OssIndexAnalysisTask.class); EVENT_SERVICE.unsubscribe(GitHubAdvisoryMirrorTask.class); - EVENT_SERVICE.unsubscribe(OSVDownloadTask.class); + EVENT_SERVICE.unsubscribe(OsvDownloadTask.class); EVENT_SERVICE.unsubscribe(VulnDbSyncTask.class); EVENT_SERVICE.unsubscribe(VulnDbAnalysisTask.class); EVENT_SERVICE.unsubscribe(VulnerabilityAnalysisTask.class); diff --git a/src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java b/src/main/java/org/dependencytrack/event/OsvMirrorEvent.java similarity index 72% rename from src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java rename to src/main/java/org/dependencytrack/event/OsvMirrorEvent.java index bb3c347fb3..67a80c7fd0 100644 --- a/src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java +++ b/src/main/java/org/dependencytrack/event/OsvMirrorEvent.java @@ -5,6 +5,6 @@ /** * Defines an event used to start a mirror of Google OSV. */ -public class GoogleOSVMirrorEvent implements Event { +public class OsvMirrorEvent implements Event { } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java similarity index 90% rename from src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java rename to src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java index 79e9aec959..9929bb445b 100644 --- a/src/main/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java @@ -4,9 +4,8 @@ import kong.unirest.json.JSONObject; import org.apache.commons.lang3.StringUtils; import org.dependencytrack.model.Severity; -import org.dependencytrack.model.Vulnerability; -import org.dependencytrack.parser.osv.model.OSVAdvisory; -import org.dependencytrack.parser.osv.model.OSVVulnerability; +import org.dependencytrack.parser.osv.model.OsvAdvisory; +import org.dependencytrack.parser.osv.model.OsvVulnerability; import us.springett.cvss.Cvss; import us.springett.cvss.Score; @@ -19,18 +18,18 @@ /* Parser for Google OSV, an aggregator of vulnerability databases including GitHub Security Advisories, PyPA, RustSec, and Global Security Database, and more. */ -public class GoogleOSVAdvisoryParser { +public class OsvAdvisoryParser { - public OSVAdvisory parse(final JSONObject object) { + public OsvAdvisory parse(final JSONObject object) { - OSVAdvisory advisory = null; + OsvAdvisory advisory = null; // initial check if advisory is valid or withdrawn String withdrawn = object.optString("withdrawn", null); if(object != null && withdrawn == null) { - advisory = new OSVAdvisory(); + advisory = new OsvAdvisory(); advisory.setId(object.optString("id", null)); advisory.setSummary(trimSummary(object.optString("summary", null))); advisory.setDetails(object.optString("details", null)); @@ -88,15 +87,15 @@ public OSVAdvisory parse(final JSONObject object) { } } - final List vulnerabilities = parseVulnerabilities(object); + final List vulnerabilities = parseVulnerabilities(object); advisory.setVulnerabilities(vulnerabilities); } return advisory; } - private List parseVulnerabilities(JSONObject object) { + private List parseVulnerabilities(JSONObject object) { - List osvVulnerabilityList = new ArrayList<>(); + List osvVulnerabilityList = new ArrayList<>(); final JSONArray vulnerabilities = object.optJSONArray("affected"); if (vulnerabilities != null) { for(int i=0; i parseVulnerabilities(JSONObject object) { return osvVulnerabilityList; } - public List parseVulnerabilityRange(JSONObject vulnerability) { + public List parseVulnerabilityRange(JSONObject vulnerability) { - List osvVulnerabilityList = new ArrayList<>(); + List osvVulnerabilityList = new ArrayList<>(); final JSONArray ranges = vulnerability.optJSONArray("ranges"); final JSONArray versions = vulnerability.optJSONArray("versions"); if (ranges != null) { @@ -124,7 +123,7 @@ public List parseVulnerabilityRange(JSONObject vulnerability) // if ranges are not available or only commit hash range is available, look for versions if (osvVulnerabilityList.size() == 0 && versions != null && versions.length() > 0) { for (int j=0; j parseVersionRanges(JSONObject vulnerability, JSONObject range) { + private List parseVersionRanges(JSONObject vulnerability, JSONObject range) { - final List osvVulnerabilityList = new ArrayList<>(); + final List osvVulnerabilityList = new ArrayList<>(); final JSONArray rangeEvents = range.optJSONArray("events"); final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); if(rangeEvents != null) { int k = 0; while (k < rangeEvents.length()) { - OSVVulnerability osvVulnerability = createOSVVulnerability(vulnerability); + OsvVulnerability osvVulnerability = createOSVVulnerability(vulnerability); JSONObject event = rangeEvents.getJSONObject(k); String lower = event.optString("introduced", null); if(lower != null) { @@ -182,9 +181,9 @@ private List parseVersionRanges(JSONObject vulnerability, JSON return osvVulnerabilityList; } - private OSVVulnerability createOSVVulnerability(JSONObject vulnerability) { + private OsvVulnerability createOSVVulnerability(JSONObject vulnerability) { - OSVVulnerability osvVulnerability = new OSVVulnerability(); + OsvVulnerability osvVulnerability = new OsvVulnerability(); final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); final JSONObject ecosystemSpecific = vulnerability.optJSONObject("ecosystem_specific"); final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java b/src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java similarity index 94% rename from src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java rename to src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java index 590bbc48c5..76cda4537a 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVAdvisory.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.List; -public class OSVAdvisory { +public class OsvAdvisory { private String id; @@ -30,7 +30,7 @@ public class OSVAdvisory { private String schema_version; - private List vulnerabilities; + private List vulnerabilities; private String cvssV2Vector; @@ -129,11 +129,11 @@ public void setSchema_version(String schema_version) { this.schema_version = schema_version; } - public List getVulnerabilities() { + public List getVulnerabilities() { return vulnerabilities; } - public void setVulnerabilities(List vulnerabilities) { + public void setVulnerabilities(List vulnerabilities) { this.vulnerabilities = vulnerabilities; } diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OsvVulnerability.java similarity index 98% rename from src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java rename to src/main/java/org/dependencytrack/parser/osv/model/OsvVulnerability.java index 247aa84adc..bc5a6bb822 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OSVVulnerability.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OsvVulnerability.java @@ -2,7 +2,7 @@ import org.dependencytrack.model.Severity; -public class OSVVulnerability { +public class OsvVulnerability { private String packageName; private String packageEcosystem; diff --git a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java similarity index 92% rename from src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java rename to src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index 457d78b927..c40641edee 100644 --- a/src/main/java/org/dependencytrack/tasks/OSVDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -12,17 +12,17 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.dependencytrack.common.HttpClientPool; -import org.dependencytrack.event.GoogleOSVMirrorEvent; +import org.dependencytrack.event.OsvMirrorEvent; import org.dependencytrack.event.IndexEvent; import org.dependencytrack.model.Cwe; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.parser.common.resolver.CweResolver; -import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; +import org.dependencytrack.parser.osv.OsvAdvisoryParser; import org.dependencytrack.parser.osv.model.Ecosystem; -import org.dependencytrack.parser.osv.model.OSVAdvisory; -import org.dependencytrack.parser.osv.model.OSVVulnerability; +import org.dependencytrack.parser.osv.model.OsvAdvisory; +import org.dependencytrack.parser.osv.model.OsvVulnerability; import org.dependencytrack.persistence.QueryManager; import us.springett.cvss.Cvss; @@ -43,13 +43,13 @@ import static org.dependencytrack.model.Severity.getSeverityByLevel; import static org.dependencytrack.util.VulnerabilityUtil.*; -public class OSVDownloadTask implements LoggableSubscriber { +public class OsvDownloadTask implements LoggableSubscriber { - private static final Logger LOGGER = Logger.getLogger(OSVDownloadTask.class); + private static final Logger LOGGER = Logger.getLogger(OsvDownloadTask.class); private final boolean isEnabled; private HttpUriRequest request; - public OSVDownloadTask() { + public OsvDownloadTask() { try (final QueryManager qm = new QueryManager()) { final ConfigProperty enabled = qm.getConfigProperty(VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getGroupName(), VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getPropertyName()); this.isEnabled = enabled != null && Boolean.valueOf(enabled.getPropertyValue()); @@ -59,7 +59,7 @@ public OSVDownloadTask() { @Override public void inform(Event e) { - if (e instanceof GoogleOSVMirrorEvent && this.isEnabled) { + if (e instanceof OsvMirrorEvent && this.isEnabled) { for (Ecosystem ecosystem : Ecosystem.values()) { LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); @@ -85,7 +85,7 @@ public void inform(Event e) { private void unzipFolder(ZipInputStream zipIn) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(zipIn)); - GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + OsvAdvisoryParser parser = new OsvAdvisoryParser(); ZipEntry zipEntry = zipIn.getNextEntry(); while (zipEntry != null) { @@ -95,7 +95,7 @@ private void unzipFolder(ZipInputStream zipIn) throws IOException { out.append(line); } JSONObject json = new JSONObject(out.toString()); - final OSVAdvisory osvAdvisory = parser.parse(json); + final OsvAdvisory osvAdvisory = parser.parse(json); if (osvAdvisory != null) { updateDatasource(osvAdvisory); } @@ -105,7 +105,7 @@ private void unzipFolder(ZipInputStream zipIn) throws IOException { reader.close(); } - public void updateDatasource(final OSVAdvisory advisory) { + public void updateDatasource(final OsvAdvisory advisory) { try (QueryManager qm = new QueryManager()) { @@ -122,7 +122,7 @@ public void updateDatasource(final OSVAdvisory advisory) { synchronizedVulnerability = qm.synchronizeVulnerability(vulnerability, false); } - for (OSVVulnerability osvVulnerability: advisory.getVulnerabilities()) { + for (OsvVulnerability osvVulnerability: advisory.getVulnerabilities()) { VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); if (vs != null) { // check if it already exists or not @@ -140,7 +140,7 @@ public void updateDatasource(final OSVAdvisory advisory) { Event.dispatch(new IndexEvent(IndexEvent.Action.COMMIT, Vulnerability.class)); } - public Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSVAdvisory advisory) { + public Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OsvAdvisory advisory) { final Vulnerability vuln = new Vulnerability(); if(advisory.getId() != null) { @@ -179,7 +179,7 @@ public Vulnerability mapAdvisoryToVulnerability(final QueryManager qm, final OSV } // calculate severity of vulnerability on priority-basis (database, ecosystem) - public Severity calculateOSVSeverity(OSVAdvisory advisory) { + public Severity calculateOSVSeverity(OsvAdvisory advisory) { // derive from database_specific cvss v3 vector if available if(advisory.getCvssV3Vector() != null) { @@ -208,7 +208,7 @@ public Severity calculateOSVSeverity(OSVAdvisory advisory) { // get largest ecosystem_specific severity from its affected packages if (advisory.getVulnerabilities() != null) { List severityLevels = new ArrayList<>(); - for (OSVVulnerability vuln : advisory.getVulnerabilities()) { + for (OsvVulnerability vuln : advisory.getVulnerabilities()) { severityLevels.add(vuln.getSeverity().getLevel()); } Collections.sort(severityLevels); @@ -228,7 +228,7 @@ public Vulnerability.Source extractSource(String vulnId) { } } - public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OSVVulnerability vuln) { + public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OsvVulnerability vuln) { if (vuln.getPurl() == null) { LOGGER.debug("No PURL provided for affected package " + vuln.getPackageName() + " - skipping"); return null; @@ -264,7 +264,7 @@ public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManage return vs; } - public Vulnerability findExistingClashingVulnerability(QueryManager qm, Vulnerability vulnerability, OSVAdvisory advisory) { + public Vulnerability findExistingClashingVulnerability(QueryManager qm, Vulnerability vulnerability, OsvAdvisory advisory) { Vulnerability existing = null; if (isVulnerabilitySourceClashingWithGithubOrNvd(vulnerability.getSource())) { diff --git a/src/main/java/org/dependencytrack/tasks/TaskScheduler.java b/src/main/java/org/dependencytrack/tasks/TaskScheduler.java index c55bf2feef..67b95e2cb4 100644 --- a/src/main/java/org/dependencytrack/tasks/TaskScheduler.java +++ b/src/main/java/org/dependencytrack/tasks/TaskScheduler.java @@ -27,7 +27,7 @@ import org.dependencytrack.event.DefectDojoUploadEventAbstract; import org.dependencytrack.event.FortifySscUploadEventAbstract; import org.dependencytrack.event.GitHubAdvisoryMirrorEvent; -import org.dependencytrack.event.GoogleOSVMirrorEvent; +import org.dependencytrack.event.OsvMirrorEvent; import org.dependencytrack.event.InternalComponentIdentificationEvent; import org.dependencytrack.event.KennaSecurityUploadEventAbstract; import org.dependencytrack.event.MetricsUpdateEvent; @@ -63,7 +63,7 @@ private TaskScheduler() { scheduleEvent(new GitHubAdvisoryMirrorEvent(), 10000, 86400000); // Creates a new event that executes every 24 hours (86400000) after an initial 10 second (10000) delay - scheduleEvent(new GoogleOSVMirrorEvent(), 10000, 86400000); + scheduleEvent(new OsvMirrorEvent(), 10000, 86400000); // Creates a new event that executes every 24 hours (86400000) after an initial 1 minute (60000) delay scheduleEvent(new NistMirrorEvent(), 60000, 86400000); diff --git a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java similarity index 92% rename from src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java rename to src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java index cbb365948f..fffe77469b 100644 --- a/src/test/java/org/dependencytrack/parser/osv/GoogleOSVAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java @@ -2,8 +2,8 @@ import kong.unirest.json.JSONArray; import kong.unirest.json.JSONObject; -import org.dependencytrack.parser.osv.model.OSVAdvisory; -import org.dependencytrack.parser.osv.model.OSVVulnerability; +import org.dependencytrack.parser.osv.model.OsvAdvisory; +import org.dependencytrack.parser.osv.model.OsvVulnerability; import org.junit.Assert; import org.junit.Test; @@ -12,9 +12,9 @@ import java.nio.file.Paths; import java.util.List; -public class GoogleOSVAdvisoryParserTest { +public class OsvAdvisoryParserTest { - GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); + OsvAdvisoryParser parser = new OsvAdvisoryParser(); @Test public void testTrimSummary() { @@ -42,7 +42,7 @@ public void testVulnerabilityRangeEmpty() throws IOException { String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(0)); + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(0)); Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(1, osvVulnerabilityList.size()); } @@ -54,10 +54,10 @@ public void testVulnerabilityRangeSingle() throws IOException { String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(1)); + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(1)); Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(1, osvVulnerabilityList.size()); - OSVVulnerability vuln = osvVulnerabilityList.get(0); + OsvVulnerability vuln = osvVulnerabilityList.get(0); Assert.assertEquals("pkg:maven/org.springframework.security.oauth/spring-security-oauth", vuln.getPurl()); Assert.assertEquals("0", vuln.getLowerVersionRange()); Assert.assertEquals("2.0.17", vuln.getUpperVersionRangeExcluding()); @@ -74,7 +74,7 @@ public void testVulnerabilityRangeMultiple() throws IOException { final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); // range test full pairs - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(2)); + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(2)); Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(3, osvVulnerabilityList.size()); Assert.assertEquals("1", osvVulnerabilityList.get(0).getLowerVersionRange()); @@ -103,7 +103,7 @@ public void testVulnerabilityRangeTypes() throws IOException { final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); // type last_affected - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(4)); + List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(4)); Assert.assertNotNull(osvVulnerabilityList); Assert.assertEquals(1, osvVulnerabilityList.size()); Assert.assertEquals("10", osvVulnerabilityList.get(0).getLowerVersionRange()); @@ -126,7 +126,7 @@ public void testParseOSVJson() throws IOException { String jsonFile = "src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"; String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Assert.assertEquals("GHSA-77rv-6vfw-x4gc", advisory.getId()); Assert.assertEquals("LOW", advisory.getSeverity()); @@ -145,7 +145,7 @@ public void testCommitHashRanges() throws IOException { String jsonFile = "src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json"; String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Assert.assertEquals("OSV-2021-1820", advisory.getId()); Assert.assertEquals(22, advisory.getVulnerabilities().size()); diff --git a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java similarity index 91% rename from src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java rename to src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java index ea50a2b832..b25359d208 100644 --- a/src/test/java/org/dependencytrack/task/OSVDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java @@ -19,10 +19,10 @@ import org.dependencytrack.PersistenceCapableTest; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; -import org.dependencytrack.parser.osv.GoogleOSVAdvisoryParser; -import org.dependencytrack.parser.osv.model.OSVAdvisory; +import org.dependencytrack.parser.osv.OsvAdvisoryParser; +import org.dependencytrack.parser.osv.model.OsvAdvisory; import org.dependencytrack.persistence.QueryManager; -import org.dependencytrack.tasks.OSVDownloadTask; +import org.dependencytrack.tasks.OsvDownloadTask; import org.junit.Assert; import org.junit.Test; @@ -30,16 +30,16 @@ import java.nio.file.Files; import java.nio.file.Paths; -public class OSVDownloadTaskTest extends PersistenceCapableTest { +public class OsvDownloadTaskTest extends PersistenceCapableTest { private JSONObject jsonObject; - private final GoogleOSVAdvisoryParser parser = new GoogleOSVAdvisoryParser(); - private final OSVDownloadTask task = new OSVDownloadTask(); + private final OsvAdvisoryParser parser = new OsvAdvisoryParser(); + private final OsvDownloadTask task = new OsvDownloadTask(); @Test public void testParseOSVJsonToAdvisoryAndSave() throws Exception { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Assert.assertEquals(8, advisory.getVulnerabilities().size()); @@ -78,7 +78,7 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { public void testParseAdvisoryToVulnerability() throws IOException { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Vulnerability vuln = task.mapAdvisoryToVulnerability(new QueryManager(), advisory); Assert.assertNotNull(vuln); @@ -92,7 +92,7 @@ public void testParseAdvisoryToVulnerability() throws IOException { public void testParseAdvisoryToVulnerabilityWithInvalidPurl() throws IOException { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-invalid-purl.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); Assert.assertNotNull(advisory); Vulnerability vuln = qm.getVulnerabilityByVulnId("GOOGLE", "OSV-2021-60", true); @@ -105,7 +105,7 @@ public void testParseAdvisoryToVulnerabilityWithInvalidPurl() throws IOException public void testWithdrawnAdvisory() throws Exception { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-withdrawn.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNull(advisory); } @@ -132,7 +132,7 @@ public void testSourceOfVulnerability() { public void testCalculateOSVSeverity() throws IOException { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Severity severity = task.calculateOSVSeverity(advisory); Assert.assertEquals(Severity.CRITICAL, severity); @@ -159,7 +159,7 @@ public void testFindExistingClashingVulnerability() throws IOException { // insert a vulnerability in database prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); var qm = new QueryManager(); @@ -187,7 +187,7 @@ public void testCommitHashRangesAndVersions() throws IOException { // insert a vulnerability in database prepareJsonObject("src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json"); - OSVAdvisory advisory = parser.parse(jsonObject); + OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); var qm = new QueryManager(); From da1d059ffcecaaa0e9bb31b70656636bbd0f08f5 Mon Sep 17 00:00:00 2001 From: nscuro Date: Sat, 2 Jul 2022 18:51:19 +0200 Subject: [PATCH 23/26] Remove redundant QueryManager method; Test more mapped vulnerability fields Signed-off-by: nscuro --- .../persistence/QueryManager.java | 3 - .../VulnerableSoftwareQueryManager.java | 10 --- .../task/OsvDownloadTaskTest.java | 66 +++++++++++++------ 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 4a4aefd981..123d2c7834 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -1106,7 +1106,4 @@ public PaginatedResult getTags(String policyUuid) { return getTagQueryManager().getTags(policyUuid); } - public VulnerableSoftware getVulnerableSoftwareByPurl(String purl, String versionEndExcluding, String versionStartIncluding) { - return getVulnerableSoftwareQueryManager().getVulnerableSoftwareByPurl(purl, versionEndExcluding, versionStartIncluding); - } } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java index 3de8fa06f0..a93edbaa5c 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java @@ -168,16 +168,6 @@ public VulnerableSoftware getVulnerableSoftwareByPurl(String purlType, String pu return singleResult(query.executeWithArray(purlType, purlNamespace, purlName, versionEndExcluding, versionEndIncluding, versionStartExcluding, versionStartIncluding)); } - /** - * Returns a List of all VulnerableSoftware objects that match the specified PackageURL - * @return a List of matching VulnerableSoftware objects - */ - @SuppressWarnings("unchecked") - public VulnerableSoftware getVulnerableSoftwareByPurl(String purl, String versionEndExcluding, String versionStartIncluding) { - final Query query = pm.newQuery(VulnerableSoftware.class, "purl == :purl && versionEndExcluding == :versionEndExcluding && versionStartIncluding == :versionStartIncluding"); - return singleResult(query.executeWithArray(purl, versionEndExcluding, versionStartIncluding)); - } - /** * Returns a List of all VulnerableSoftware objects that match the specified PackageURL * @return a List of matching VulnerableSoftware objects diff --git a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java index b25359d208..2c7ca3c8a9 100644 --- a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java @@ -15,13 +15,16 @@ */ package org.dependencytrack.task; +import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; +import org.apache.commons.lang3.StringUtils; import org.dependencytrack.PersistenceCapableTest; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; +import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.parser.osv.OsvAdvisoryParser; import org.dependencytrack.parser.osv.model.OsvAdvisory; -import org.dependencytrack.persistence.QueryManager; +import org.dependencytrack.persistence.CweImporter; import org.dependencytrack.tasks.OsvDownloadTask; import org.junit.Assert; import org.junit.Test; @@ -29,6 +32,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.function.Consumer; public class OsvDownloadTaskTest extends PersistenceCapableTest { private JSONObject jsonObject; @@ -37,6 +44,7 @@ public class OsvDownloadTaskTest extends PersistenceCapableTest { @Test public void testParseOSVJsonToAdvisoryAndSave() throws Exception { + new CweImporter().processCweDefinitions(); // Necessary for resolving CWEs prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); OsvAdvisory advisory = parser.parse(jsonObject); @@ -45,23 +53,41 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { // pass the mapped advisory to OSV task to update the database task.updateDatasource(advisory); - var qm = new QueryManager(); - Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); - Assert.assertNotNull(vulnerability); + final Consumer assertVulnerability = (vulnerability) -> { + Assert.assertNotNull(vulnerability); + Assert.assertFalse(StringUtils.isEmpty(vulnerability.getTitle())); + Assert.assertFalse(StringUtils.isEmpty(vulnerability.getDescription())); + Assert.assertNotNull(vulnerability.getCwes()); + Assert.assertEquals(1, vulnerability.getCwes().size()); + Assert.assertEquals(601, vulnerability.getCwes().get(0).intValue()); + Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", vulnerability.getCvssV3Vector()); + Assert.assertEquals(Severity.CRITICAL, vulnerability.getSeverity()); + Assert.assertNull(vulnerability.getCreated()); + Assert.assertNotNull(vulnerability.getPublished()); + Assert.assertEquals(LocalDateTime.of(2019, 3, 14, 15, 39, 30).toInstant(ZoneOffset.UTC), vulnerability.getPublished().toInstant()); + Assert.assertNotNull(vulnerability.getUpdated()); + Assert.assertEquals(LocalDateTime.of(2022, 6, 9, 7, 1, 32, 587000000).toInstant(ZoneOffset.UTC), vulnerability.getUpdated().toInstant()); + Assert.assertEquals("Skywalker, Solo", vulnerability.getCredits()); + }; - var vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.0.17", "0"); - Assert.assertNotNull(vulnerableSoftware); - Assert.assertEquals("maven", vulnerableSoftware.getPurlType()); - Assert.assertEquals("org.springframework.security.oauth", vulnerableSoftware.getPurlNamespace()); - Assert.assertEquals("spring-security-oauth", vulnerableSoftware.getPurlName()); - Assert.assertEquals("0", vulnerableSoftware.getVersionStartIncluding()); - Assert.assertEquals("2.0.17", vulnerableSoftware.getVersionEndExcluding()); - - vulnerableSoftware = qm.getVulnerableSoftwareByPurl("pkg:maven/org.springframework.security.oauth/spring-security-oauth", "2.1.4", "2.1.0"); - Assert.assertNotNull(vulnerableSoftware); - Assert.assertEquals("2.1.0", vulnerableSoftware.getVersionStartIncluding()); - Assert.assertEquals("2.1.4", vulnerableSoftware.getVersionEndExcluding()); + Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); + assertVulnerability.accept(vulnerability); + + List vulnerableSoftware = qm.getAllVulnerableSoftwareByPurl(new PackageURL("pkg:maven/org.springframework.security.oauth/spring-security-oauth")); + Assert.assertEquals(4, vulnerableSoftware.size()); + Assert.assertEquals("0", vulnerableSoftware.get(0).getVersionStartIncluding()); + Assert.assertEquals("2.0.17", vulnerableSoftware.get(0).getVersionEndExcluding()); + Assert.assertEquals("2.1.0", vulnerableSoftware.get(1).getVersionStartIncluding()); + Assert.assertEquals("2.1.4", vulnerableSoftware.get(1).getVersionEndExcluding()); + Assert.assertEquals("2.2.0", vulnerableSoftware.get(2).getVersionStartIncluding()); + Assert.assertEquals("2.2.4", vulnerableSoftware.get(2).getVersionEndExcluding()); + Assert.assertEquals("2.3.0", vulnerableSoftware.get(3).getVersionStartIncluding()); + Assert.assertEquals("2.3.5", vulnerableSoftware.get(3).getVersionEndExcluding()); + + // The advisory reports both spring-security-oauth and spring-security-oauth2 as affected + vulnerableSoftware = qm.getAllVulnerableSoftwareByPurl(new PackageURL("pkg:maven/org.springframework.security.oauth/spring-security-oauth2")); + Assert.assertEquals(4, vulnerableSoftware.size()); // incoming vulnerability from osv when vulnerability already exists from github prepareJsonObject("src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json"); @@ -70,8 +96,10 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { task.updateDatasource(advisory); vulnerability = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-77rv-6vfw-x4gc", true); Assert.assertNotNull(vulnerability); + assertVulnerability.accept(vulnerability); // Ensure that the vulnerability was not modified Assert.assertEquals(9, vulnerability.getVulnerableSoftware().size()); - Assert.assertEquals(Severity.CRITICAL, vulnerability.getSeverity()); + Assert.assertEquals("3.1.0", vulnerability.getVulnerableSoftware().get(8).getVersionStartIncluding()); + Assert.assertEquals("3.3.0", vulnerability.getVulnerableSoftware().get(8).getVersionEndExcluding()); } @Test @@ -80,7 +108,7 @@ public void testParseAdvisoryToVulnerability() throws IOException { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); - Vulnerability vuln = task.mapAdvisoryToVulnerability(new QueryManager(), advisory); + Vulnerability vuln = task.mapAdvisoryToVulnerability(qm, advisory); Assert.assertNotNull(vuln); Assert.assertEquals("Skywalker, Solo", vuln.getCredits()); Assert.assertEquals("GITHUB", vuln.getSource()); @@ -161,7 +189,6 @@ public void testFindExistingClashingVulnerability() throws IOException { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); - var qm = new QueryManager(); // tests for incoming vulnerabilities if it or its alias already exists prepareJsonObject("src/test/resources/unit/osv.jsons/new-GHSA-77rv-6vfw-x4gc.json"); @@ -189,7 +216,6 @@ public void testCommitHashRangesAndVersions() throws IOException { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-git-commit-hash-ranges.json"); OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); - var qm = new QueryManager(); Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GOOGLE", "OSV-2021-1820", true); Assert.assertNotNull(vulnerability); From bb5760083a85842f025c763a57463ebff0485e68 Mon Sep 17 00:00:00 2001 From: nscuro Date: Sun, 3 Jul 2022 00:01:34 +0200 Subject: [PATCH 24/26] Refactor OSV range parsing to avoid infinite loops Additional changes: * Rename `OsvVulnerability` to `OsvAffectedPackage` to avoid confusion * Be more strict about ordering of range events Signed-off-by: nscuro --- .../parser/osv/OsvAdvisoryParser.java | 165 ++++++++++-------- .../parser/osv/model/OsvAdvisory.java | 10 +- ...erability.java => OsvAffectedPackage.java} | 2 +- .../tasks/OsvDownloadTask.java | 28 +-- .../parser/osv/OsvAdvisoryParserTest.java | 89 +++++----- .../task/OsvDownloadTaskTest.java | 2 +- .../osv-vulnerability-with-ranges.json | 2 +- 7 files changed, 161 insertions(+), 137 deletions(-) rename src/main/java/org/dependencytrack/parser/osv/model/{OsvVulnerability.java => OsvAffectedPackage.java} (98%) diff --git a/src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java b/src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java index 9929bb445b..dc73b2aa75 100644 --- a/src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java +++ b/src/main/java/org/dependencytrack/parser/osv/OsvAdvisoryParser.java @@ -5,7 +5,7 @@ import org.apache.commons.lang3.StringUtils; import org.dependencytrack.model.Severity; import org.dependencytrack.parser.osv.model.OsvAdvisory; -import org.dependencytrack.parser.osv.model.OsvVulnerability; +import org.dependencytrack.parser.osv.model.OsvAffectedPackage; import us.springett.cvss.Cvss; import us.springett.cvss.Score; @@ -87,112 +87,139 @@ public OsvAdvisory parse(final JSONObject object) { } } - final List vulnerabilities = parseVulnerabilities(object); - advisory.setVulnerabilities(vulnerabilities); + final List affectedPackages = parseAffectedPackages(object); + advisory.setAffectedPackages(affectedPackages); } return advisory; } - private List parseVulnerabilities(JSONObject object) { + private List parseAffectedPackages(final JSONObject advisory) { - List osvVulnerabilityList = new ArrayList<>(); - final JSONArray vulnerabilities = object.optJSONArray("affected"); - if (vulnerabilities != null) { - for(int i=0; i affectedPackages = new ArrayList<>(); + final JSONArray affected = advisory.optJSONArray("affected"); + if (affected != null) { + for(int i=0; i parseVulnerabilityRange(JSONObject vulnerability) { + public List parseAffectedPackageRange(final JSONObject affected) { - List osvVulnerabilityList = new ArrayList<>(); - final JSONArray ranges = vulnerability.optJSONArray("ranges"); - final JSONArray versions = vulnerability.optJSONArray("versions"); + List osvAffectedPackageList = new ArrayList<>(); + final JSONArray ranges = affected.optJSONArray("ranges"); + final JSONArray versions = affected.optJSONArray("versions"); if (ranges != null) { for (int j=0; j 0) { + if (osvAffectedPackageList.size() == 0 && versions != null && versions.length() > 0) { for (int j=0; j parseVersionRanges(JSONObject vulnerability, JSONObject range) { + private List parseVersionRanges(JSONObject vulnerability, JSONObject range) { + final String rangeType = range.optString("type"); + if (!"ECOSYSTEM".equalsIgnoreCase(rangeType) && !"SEMVER".equalsIgnoreCase(rangeType)) { + // We can't support ranges of type GIT for now, as evaluating them requires knowledge of + // the entire Git history of a package. We don't have that, so there's no point in + // ingesting this data. + // + // We're also implicitly excluding ranges of types that we don't yet know of. + // This is a tradeoff of potentially missing new data vs. flooding our users' + // database with junk data. + return List.of(); + } - final List osvVulnerabilityList = new ArrayList<>(); final JSONArray rangeEvents = range.optJSONArray("events"); - final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); - if(rangeEvents != null) { - int k = 0; - while (k < rangeEvents.length()) { - - OsvVulnerability osvVulnerability = createOSVVulnerability(vulnerability); - JSONObject event = rangeEvents.getJSONObject(k); - String lower = event.optString("introduced", null); - if(lower != null) { - osvVulnerability.setLowerVersionRange(lower); - k += 1; - } - if(k < rangeEvents.length()) { - event = rangeEvents.getJSONObject(k); - String fixed = event.optString("fixed", null); - String lastAffected = event.optString("last_affected", null); - String limit = event.optString("limit", null); - if (fixed != null) { - osvVulnerability.setUpperVersionRangeExcluding(fixed); - k += 1; - } else if (lastAffected != null){ - osvVulnerability.setUpperVersionRangeIncluding(lastAffected); - k += 1; - } else if (limit != null) { - osvVulnerability.setUpperVersionRangeExcluding(limit); - k += 1; - } + if (rangeEvents == null) { + return List.of(); + } + + final List affectedPackages = new ArrayList<>(); + + for (int i = 0; i < rangeEvents.length(); i++) { + JSONObject event = rangeEvents.getJSONObject(i); + + final String introduced = event.optString("introduced", null); + if (introduced == null) { + // "introduced" is required for every range. But events are not guaranteed to be sorted, + // it's merely a recommendation by the OSV specification. + // + // If events are not sorted, we have no way to tell what the correct order should be. + // We make a tradeoff by assuming that ranges are sorted, and potentially skip ranges + // that aren't. + continue; + } + + final OsvAffectedPackage affectedPackage = createAffectedPackage(vulnerability); + affectedPackage.setLowerVersionRange(introduced); + + if (i + 1 < rangeEvents.length()) { + event = rangeEvents.getJSONObject(i + 1); + final String fixed = event.optString("fixed", null); + final String lastAffected = event.optString("last_affected", null); + final String limit = event.optString("limit", null); + + if (fixed != null) { + affectedPackage.setUpperVersionRangeExcluding(fixed); + i++; + } else if (lastAffected != null) { + affectedPackage.setUpperVersionRangeIncluding(lastAffected); + i++; + } else if (limit != null) { + affectedPackage.setUpperVersionRangeExcluding(limit); + i++; } - if (osvVulnerability.getUpperVersionRangeIncluding() == null - && osvVulnerability.getUpperVersionRangeExcluding() == null - && databaseSpecific != null) { - String lastAffected = databaseSpecific.optString("last_known_affected_version_range", null); - if (lastAffected != null) { - osvVulnerability.setUpperVersionRangeIncluding(lastAffected.replaceAll("[^0-9.]+", "").trim()); + } + + // Special treatment for GitHub: https://github.com/github/advisory-database/issues/470 + final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); + if (databaseSpecific != null + && affectedPackage.getUpperVersionRangeIncluding() == null + && affectedPackage.getUpperVersionRangeExcluding() == null) { + final String lastAffectedRange = databaseSpecific.optString("last_known_affected_version_range", null); + if (lastAffectedRange != null) { + if (lastAffectedRange.startsWith("<=")) { + affectedPackage.setUpperVersionRangeIncluding(lastAffectedRange.replaceFirst("<=", "").trim()); + } else if (lastAffectedRange.startsWith("<")) { + affectedPackage.setUpperVersionRangeExcluding(lastAffectedRange.replaceAll("<", "").trim()); } } - osvVulnerabilityList.add(osvVulnerability); } + + affectedPackages.add(affectedPackage); } - return osvVulnerabilityList; + + return affectedPackages; } - private OsvVulnerability createOSVVulnerability(JSONObject vulnerability) { + private OsvAffectedPackage createAffectedPackage(JSONObject vulnerability) { - OsvVulnerability osvVulnerability = new OsvVulnerability(); + OsvAffectedPackage osvAffectedPackage = new OsvAffectedPackage(); final JSONObject affectedPackageJson = vulnerability.optJSONObject("package"); final JSONObject ecosystemSpecific = vulnerability.optJSONObject("ecosystem_specific"); final JSONObject databaseSpecific = vulnerability.optJSONObject("database_specific"); Severity ecosystemSeverity = parseEcosystemSeverity(ecosystemSpecific, databaseSpecific); - osvVulnerability.setPackageName(affectedPackageJson.optString("name", null)); - osvVulnerability.setPackageEcosystem(affectedPackageJson.optString("ecosystem", null)); - osvVulnerability.setPurl(affectedPackageJson.optString("purl", null)); - osvVulnerability.setSeverity(ecosystemSeverity); - return osvVulnerability; + osvAffectedPackage.setPackageName(affectedPackageJson.optString("name", null)); + osvAffectedPackage.setPackageEcosystem(affectedPackageJson.optString("ecosystem", null)); + osvAffectedPackage.setPurl(affectedPackageJson.optString("purl", null)); + osvAffectedPackage.setSeverity(ecosystemSeverity); + return osvAffectedPackage; } private Severity parseEcosystemSeverity(JSONObject ecosystemSpecific, JSONObject databaseSpecific) { diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java b/src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java index 76cda4537a..521f62d6ca 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OsvAdvisory.java @@ -30,7 +30,7 @@ public class OsvAdvisory { private String schema_version; - private List vulnerabilities; + private List affectedPackages; private String cvssV2Vector; @@ -129,12 +129,12 @@ public void setSchema_version(String schema_version) { this.schema_version = schema_version; } - public List getVulnerabilities() { - return vulnerabilities; + public List getAffectedPackages() { + return affectedPackages; } - public void setVulnerabilities(List vulnerabilities) { - this.vulnerabilities = vulnerabilities; + public void setAffectedPackages(List affectedPackages) { + this.affectedPackages = affectedPackages; } public String getSeverity() { diff --git a/src/main/java/org/dependencytrack/parser/osv/model/OsvVulnerability.java b/src/main/java/org/dependencytrack/parser/osv/model/OsvAffectedPackage.java similarity index 98% rename from src/main/java/org/dependencytrack/parser/osv/model/OsvVulnerability.java rename to src/main/java/org/dependencytrack/parser/osv/model/OsvAffectedPackage.java index bc5a6bb822..7ac41e3822 100644 --- a/src/main/java/org/dependencytrack/parser/osv/model/OsvVulnerability.java +++ b/src/main/java/org/dependencytrack/parser/osv/model/OsvAffectedPackage.java @@ -2,7 +2,7 @@ import org.dependencytrack.model.Severity; -public class OsvVulnerability { +public class OsvAffectedPackage { private String packageName; private String packageEcosystem; diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index c40641edee..a241d963d8 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -22,7 +22,7 @@ import org.dependencytrack.parser.osv.OsvAdvisoryParser; import org.dependencytrack.parser.osv.model.Ecosystem; import org.dependencytrack.parser.osv.model.OsvAdvisory; -import org.dependencytrack.parser.osv.model.OsvVulnerability; +import org.dependencytrack.parser.osv.model.OsvAffectedPackage; import org.dependencytrack.persistence.QueryManager; import us.springett.cvss.Cvss; @@ -122,8 +122,8 @@ public void updateDatasource(final OsvAdvisory advisory) { synchronizedVulnerability = qm.synchronizeVulnerability(vulnerability, false); } - for (OsvVulnerability osvVulnerability: advisory.getVulnerabilities()) { - VulnerableSoftware vs = mapVulnerabilityToVulnerableSoftware(qm, osvVulnerability); + for (OsvAffectedPackage osvAffectedPackage : advisory.getAffectedPackages()) { + VulnerableSoftware vs = mapAffectedPackageToVulnerableSoftware(qm, osvAffectedPackage); if (vs != null) { // check if it already exists or not VulnerableSoftware existingVulnSoftware = qm.getVulnerableSoftwareByPurl(vs.getPurlType(), vs.getPurlNamespace(), vs.getPurlName(), vs.getVersionEndExcluding(), vs.getVersionEndIncluding(), vs.getVersionStartExcluding(), vs.getVersionStartIncluding()); @@ -206,9 +206,9 @@ public Severity calculateOSVSeverity(OsvAdvisory advisory) { } } // get largest ecosystem_specific severity from its affected packages - if (advisory.getVulnerabilities() != null) { + if (advisory.getAffectedPackages() != null) { List severityLevels = new ArrayList<>(); - for (OsvVulnerability vuln : advisory.getVulnerabilities()) { + for (OsvAffectedPackage vuln : advisory.getAffectedPackages()) { severityLevels.add(vuln.getSeverity().getLevel()); } Collections.sort(severityLevels); @@ -228,23 +228,23 @@ public Vulnerability.Source extractSource(String vulnId) { } } - public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManager qm, final OsvVulnerability vuln) { - if (vuln.getPurl() == null) { - LOGGER.debug("No PURL provided for affected package " + vuln.getPackageName() + " - skipping"); + public VulnerableSoftware mapAffectedPackageToVulnerableSoftware(final QueryManager qm, final OsvAffectedPackage affectedPackage) { + if (affectedPackage.getPurl() == null) { + LOGGER.debug("No PURL provided for affected package " + affectedPackage.getPackageName() + " - skipping"); return null; } final PackageURL purl; try { - purl = new PackageURL(vuln.getPurl()); + purl = new PackageURL(affectedPackage.getPurl()); } catch (MalformedPackageURLException e) { - LOGGER.debug("Invalid PURL provided for affected package " + vuln.getPackageName() + " - skipping", e); + LOGGER.debug("Invalid PURL provided for affected package " + affectedPackage.getPackageName() + " - skipping", e); return null; } - final String versionStartIncluding = vuln.getLowerVersionRange(); - final String versionEndExcluding = vuln.getUpperVersionRangeExcluding(); - final String versionEndIncluding = vuln.getUpperVersionRangeIncluding(); + final String versionStartIncluding = affectedPackage.getLowerVersionRange(); + final String versionEndExcluding = affectedPackage.getUpperVersionRangeExcluding(); + final String versionEndIncluding = affectedPackage.getUpperVersionRangeIncluding(); VulnerableSoftware vs = qm.getVulnerableSoftwareByPurl(purl.getType(), purl.getNamespace(), purl.getName(), versionEndExcluding, versionEndIncluding, null, versionStartIncluding); @@ -257,7 +257,7 @@ public VulnerableSoftware mapVulnerabilityToVulnerableSoftware(final QueryManage vs.setPurlNamespace(purl.getNamespace()); vs.setPurlName(purl.getName()); vs.setVulnerable(true); - vs.setVersion(vuln.getVersion()); + vs.setVersion(affectedPackage.getVersion()); vs.setVersionStartIncluding(versionStartIncluding); vs.setVersionEndExcluding(versionEndExcluding); vs.setVersionEndIncluding(versionEndIncluding); diff --git a/src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java b/src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java index fffe77469b..6a09200c80 100644 --- a/src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java +++ b/src/test/java/org/dependencytrack/parser/osv/OsvAdvisoryParserTest.java @@ -3,7 +3,7 @@ import kong.unirest.json.JSONArray; import kong.unirest.json.JSONObject; import org.dependencytrack.parser.osv.model.OsvAdvisory; -import org.dependencytrack.parser.osv.model.OsvVulnerability; +import org.dependencytrack.parser.osv.model.OsvAffectedPackage; import org.junit.Assert; import org.junit.Test; @@ -41,10 +41,10 @@ public void testVulnerabilityRangeEmpty() throws IOException { String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-no-range.json"; String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); - final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(0)); - Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(1, osvVulnerabilityList.size()); + final JSONArray affected = jsonObject.optJSONArray("affected"); + List affectedPackages = parser.parseAffectedPackageRange(affected.getJSONObject(0)); + Assert.assertNotNull(affectedPackages); + Assert.assertEquals(1, affectedPackages.size()); } @Test @@ -53,15 +53,15 @@ public void testVulnerabilityRangeSingle() throws IOException { String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); - final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(1)); - Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(1, osvVulnerabilityList.size()); - OsvVulnerability vuln = osvVulnerabilityList.get(0); - Assert.assertEquals("pkg:maven/org.springframework.security.oauth/spring-security-oauth", vuln.getPurl()); - Assert.assertEquals("0", vuln.getLowerVersionRange()); - Assert.assertEquals("2.0.17", vuln.getUpperVersionRangeExcluding()); - Assert.assertEquals("Maven", vuln.getPackageEcosystem()); + final JSONArray affected = jsonObject.optJSONArray("affected"); + List affectedPackages = parser.parseAffectedPackageRange(affected.getJSONObject(1)); + Assert.assertNotNull(affectedPackages); + Assert.assertEquals(1, affectedPackages.size()); + OsvAffectedPackage affectedPackage = affectedPackages.get(0); + Assert.assertEquals("pkg:maven/org.springframework.security.oauth/spring-security-oauth", affectedPackage.getPurl()); + Assert.assertEquals("0", affectedPackage.getLowerVersionRange()); + Assert.assertEquals("2.0.17", affectedPackage.getUpperVersionRangeExcluding()); + Assert.assertEquals("Maven", affectedPackage.getPackageEcosystem()); } @@ -71,27 +71,24 @@ public void testVulnerabilityRangeMultiple() throws IOException { String jsonFile = "src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json"; String jsonString = new String(Files.readAllBytes(Paths.get(jsonFile))); JSONObject jsonObject = new JSONObject(jsonString); - final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); + final JSONArray affected = jsonObject.optJSONArray("affected"); // range test full pairs - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(2)); - Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(3, osvVulnerabilityList.size()); - Assert.assertEquals("1", osvVulnerabilityList.get(0).getLowerVersionRange()); - Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); - Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); - Assert.assertEquals("4", osvVulnerabilityList.get(1).getUpperVersionRangeExcluding()); + List affectedPackages = parser.parseAffectedPackageRange(affected.getJSONObject(2)); + Assert.assertNotNull(affectedPackages); + Assert.assertEquals(3, affectedPackages.size()); + Assert.assertEquals("1", affectedPackages.get(0).getLowerVersionRange()); + Assert.assertEquals("2", affectedPackages.get(0).getUpperVersionRangeExcluding()); + Assert.assertEquals("3", affectedPackages.get(1).getLowerVersionRange()); + Assert.assertEquals("4", affectedPackages.get(1).getUpperVersionRangeExcluding()); // range test half pairs - osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(3)); - Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(3, osvVulnerabilityList.size()); - Assert.assertEquals(null, osvVulnerabilityList.get(0).getLowerVersionRange()); - Assert.assertEquals("2", osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); - Assert.assertEquals("3", osvVulnerabilityList.get(1).getLowerVersionRange()); - Assert.assertEquals(null, osvVulnerabilityList.get(1).getUpperVersionRangeExcluding()); - Assert.assertEquals("4", osvVulnerabilityList.get(2).getLowerVersionRange()); - Assert.assertEquals("5", osvVulnerabilityList.get(2).getUpperVersionRangeExcluding()); + affectedPackages = parser.parseAffectedPackageRange(affected.getJSONObject(3)); + Assert.assertNotNull(affectedPackages); + Assert.assertEquals(2, affectedPackages.size()); + Assert.assertEquals("3", affectedPackages.get(0).getLowerVersionRange()); + Assert.assertEquals("4", affectedPackages.get(1).getLowerVersionRange()); + Assert.assertEquals("5", affectedPackages.get(1).getUpperVersionRangeExcluding()); } @Test @@ -103,20 +100,20 @@ public void testVulnerabilityRangeTypes() throws IOException { final JSONArray vulnerabilities = jsonObject.optJSONArray("affected"); // type last_affected - List osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(4)); - Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(1, osvVulnerabilityList.size()); - Assert.assertEquals("10", osvVulnerabilityList.get(0).getLowerVersionRange()); - Assert.assertEquals(null, osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); - Assert.assertEquals("11", osvVulnerabilityList.get(0).getUpperVersionRangeIncluding()); + List affectedPackages = parser.parseAffectedPackageRange(vulnerabilities.getJSONObject(4)); + Assert.assertNotNull(affectedPackages); + Assert.assertEquals(1, affectedPackages.size()); + Assert.assertEquals("10", affectedPackages.get(0).getLowerVersionRange()); + Assert.assertEquals(null, affectedPackages.get(0).getUpperVersionRangeExcluding()); + Assert.assertEquals("11", affectedPackages.get(0).getUpperVersionRangeIncluding()); // type last_affected - osvVulnerabilityList = parser.parseVulnerabilityRange(vulnerabilities.getJSONObject(6)); - Assert.assertNotNull(osvVulnerabilityList); - Assert.assertEquals(1, osvVulnerabilityList.size()); - Assert.assertEquals("10", osvVulnerabilityList.get(0).getLowerVersionRange()); - Assert.assertEquals(null, osvVulnerabilityList.get(0).getUpperVersionRangeExcluding()); - Assert.assertEquals("29.0", osvVulnerabilityList.get(0).getUpperVersionRangeIncluding()); + affectedPackages = parser.parseAffectedPackageRange(vulnerabilities.getJSONObject(6)); + Assert.assertNotNull(affectedPackages); + Assert.assertEquals(1, affectedPackages.size()); + Assert.assertEquals("10", affectedPackages.get(0).getLowerVersionRange()); + Assert.assertEquals(null, affectedPackages.get(0).getUpperVersionRangeExcluding()); + Assert.assertEquals("29.0", affectedPackages.get(0).getUpperVersionRangeIncluding()); } @@ -133,7 +130,7 @@ public void testParseOSVJson() throws IOException { Assert.assertEquals(1, advisory.getCweIds().size()); Assert.assertEquals(6, advisory.getReferences().size()); Assert.assertEquals(2, advisory.getCredits().size()); - Assert.assertEquals(8, advisory.getVulnerabilities().size()); + Assert.assertEquals(8, advisory.getAffectedPackages().size()); Assert.assertEquals("CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", advisory.getCvssV3Vector()); Assert.assertEquals("CVE-2019-3778", advisory.getAliases().get(0)); Assert.assertEquals("2022-06-09T07:01:32.587163Z", advisory.getModified().toString()); @@ -148,7 +145,7 @@ public void testCommitHashRanges() throws IOException { OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); Assert.assertEquals("OSV-2021-1820", advisory.getId()); - Assert.assertEquals(22, advisory.getVulnerabilities().size()); - Assert.assertEquals("4.4.0", advisory.getVulnerabilities().get(0).getVersion()); + Assert.assertEquals(22, advisory.getAffectedPackages().size()); + Assert.assertEquals("4.4.0", advisory.getAffectedPackages().get(0).getVersion()); } } diff --git a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java index 2c7ca3c8a9..b4b82e7ce3 100644 --- a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java @@ -49,7 +49,7 @@ public void testParseOSVJsonToAdvisoryAndSave() throws Exception { prepareJsonObject("src/test/resources/unit/osv.jsons/osv-GHSA-77rv-6vfw-x4gc.json"); OsvAdvisory advisory = parser.parse(jsonObject); Assert.assertNotNull(advisory); - Assert.assertEquals(8, advisory.getVulnerabilities().size()); + Assert.assertEquals(8, advisory.getAffectedPackages().size()); // pass the mapped advisory to OSV task to update the database task.updateDatasource(advisory); diff --git a/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json b/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json index fc0f11e16b..184d17a91c 100644 --- a/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json +++ b/src/test/resources/unit/osv.jsons/osv-vulnerability-with-ranges.json @@ -67,7 +67,7 @@ ] }, { - "type": "SERVER", + "type": "SEMVER", "events": [ { "introduced": "0" From c6c687e1b102f4cae6678dbc726c2e7e7474774f Mon Sep 17 00:00:00 2001 From: nscuro Date: Sun, 3 Jul 2022 19:30:39 +0200 Subject: [PATCH 25/26] Fetch `Vulnerability#vulnerableSoftware` lazily For some odd reason, the query generated by DataNucleus for fetching `VulnerableSoftware` is drastically less efficient when using the `VULNERABLESOFTWARE` `@FetchGroup` over lazy fetching via `Vulnerability#getVulnerableSoftware()`. Query generated by fetch group: ``` SELECT 'org.dependencytrack.model.VulnerableSoftware' AS DN_TYPE,A1.CPE22,A1.CPE23,A1.EDITION,A1.ID AS NUCORDER0,A1."LANGUAGE",A1.OTHER,A1.PART,A1.PRODUCT,A1.PURL,A1.PURL_NAME,A1.PURL_NAMESPACE,A1.PURL_QUALIFIERS,A1.PURL_SUBPATH,A1.PURL_TYPE,A1.PURL_VERSION,A1.SWEDITION,A1.TARGETHW,A1.TARGETSW,A1."UPDATE",A1.UUID,A1.VENDOR,A1.VERSION,A1.VERSIONENDEXCLUDING,A1.VERSIONENDINCLUDING,A1.VERSIONSTARTEXCLUDING,A1.VERSIONSTARTINCLUDING,A1.VULNERABLE,A0.VULNERABILITY_ID FROM VULNERABLESOFTWARE_VULNERABILITIES A0 INNER JOIN VULNERABLESOFTWARE A1 ON A0.VULNERABLESOFTWARE_ID = A1.ID WHERE EXISTS (SELECT 'org.dependencytrack.model.Vulnerability' AS DN_TYPE,A0_SUB.ID AS DN_APPID FROM VULNERABILITY A0_SUB WHERE A0_SUB.SOURCE = 'NVD' AND A0_SUB.VULNID = 'CVE-2020-0404' AND A0.VULNERABILITY_ID = A0_SUB.ID) ORDER BY NUCORDER0 ``` Query generated by `getVulnerableSoftware()`: ``` SELECT 'org.dependencytrack.model.VulnerableSoftware' AS DN_TYPE,A1.CPE22,A1.CPE23,A1.EDITION,A1.ID AS NUCORDER0,A1."LANGUAGE",A1.OTHER,A1.PART,A1.PRODUCT,A1.PURL,A1.PURL_NAME,A1.PURL_NAMESPACE,A1.PURL_QUALIFIERS,A1.PURL_SUBPATH,A1.PURL_TYPE,A1.PURL_VERSION,A1.SWEDITION,A1.TARGETHW,A1.TARGETSW,A1."UPDATE",A1.UUID,A1.VENDOR,A1.VERSION,A1.VERSIONENDEXCLUDING,A1.VERSIONENDINCLUDING,A1.VERSIONSTARTEXCLUDING,A1.VERSIONSTARTINCLUDING,A1.VULNERABLE FROM VULNERABLESOFTWARE_VULNERABILITIES A0 INNER JOIN VULNERABLESOFTWARE A1 ON A0.VULNERABLESOFTWARE_ID = A1.ID WHERE A0.VULNERABILITY_ID = ? ORDER BY NUCORDER0 ``` Signed-off-by: nscuro --- src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index a241d963d8..6dde366373 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -268,12 +268,12 @@ public Vulnerability findExistingClashingVulnerability(QueryManager qm, Vulnerab Vulnerability existing = null; if (isVulnerabilitySourceClashingWithGithubOrNvd(vulnerability.getSource())) { - existing = qm.getVulnerabilityByVulnId(vulnerability.getSource(), vulnerability.getVulnId(), true); + existing = qm.getVulnerabilityByVulnId(vulnerability.getSource(), vulnerability.getVulnId()); } else if (advisory.getAliases() != null) { for(String alias : advisory.getAliases()) { String sourceOfAlias = extractSource(alias).toString(); if(isVulnerabilitySourceClashingWithGithubOrNvd(sourceOfAlias)) { - existing = qm.getVulnerabilityByVulnId(sourceOfAlias, alias, true); + existing = qm.getVulnerabilityByVulnId(sourceOfAlias, alias); if (existing != null) break; } } From d855040cfc5220e1a88036822aef3dfd1fd77aa4 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 21 Jul 2022 13:17:19 +0100 Subject: [PATCH 26/26] change OSV label from Google Signed-off-by: Sahiba Mittal --- src/main/java/org/dependencytrack/model/Vulnerability.java | 2 +- .../java/org/dependencytrack/tasks/OsvDownloadTask.java | 2 +- .../java/org/dependencytrack/task/OsvDownloadTaskTest.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java index f0f6be7121..52c0a61e74 100644 --- a/src/main/java/org/dependencytrack/model/Vulnerability.java +++ b/src/main/java/org/dependencytrack/model/Vulnerability.java @@ -96,7 +96,7 @@ public enum Source { OSSINDEX, // Sonatype OSS Index RETIREJS, // Retire.js INTERNAL, // Internally-managed (and manually entered) vulnerability - GOOGLE // Google OSV Advisories + OSV // Google OSV Advisories } @PrimaryKey diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index 6dde366373..cf6714a43e 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -224,7 +224,7 @@ public Vulnerability.Source extractSource(String vulnId) { switch (sourceId) { case "GHSA": return Vulnerability.Source.GITHUB; case "CVE": return Vulnerability.Source.NVD; - default: return Vulnerability.Source.GOOGLE; + default: return Vulnerability.Source.OSV; } } diff --git a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java index b4b82e7ce3..b0f0a2257d 100644 --- a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java @@ -123,7 +123,7 @@ public void testParseAdvisoryToVulnerabilityWithInvalidPurl() throws IOException OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); Assert.assertNotNull(advisory); - Vulnerability vuln = qm.getVulnerabilityByVulnId("GOOGLE", "OSV-2021-60", true); + Vulnerability vuln = qm.getVulnerabilityByVulnId("OSV", "OSV-2021-60", true); Assert.assertNotNull(vuln); Assert.assertEquals(Severity.MEDIUM, vuln.getSeverity()); Assert.assertEquals(1, vuln.getVulnerableSoftware().size()); @@ -153,7 +153,7 @@ public void testSourceOfVulnerability() { sourceTestId = "anyOther-2022-tyhg"; source = task.extractSource(sourceTestId); Assert.assertNotNull(source); - Assert.assertEquals(Vulnerability.Source.GOOGLE, source); + Assert.assertEquals(Vulnerability.Source.OSV, source); } @Test @@ -217,7 +217,7 @@ public void testCommitHashRangesAndVersions() throws IOException { OsvAdvisory advisory = parser.parse(jsonObject); task.updateDatasource(advisory); - Vulnerability vulnerability = qm.getVulnerabilityByVulnId("GOOGLE", "OSV-2021-1820", true); + Vulnerability vulnerability = qm.getVulnerabilityByVulnId("OSV", "OSV-2021-1820", true); Assert.assertNotNull(vulnerability); Assert.assertEquals(22, vulnerability.getVulnerableSoftware().size()); Assert.assertEquals(Severity.MEDIUM, vulnerability.getSeverity());