Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Issue #931 : Support for Google OSV #1703

Merged
merged 28 commits into from
Jul 24, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bcbddff
draft for using google OSV
sahibamittal Jun 10, 2022
ee624bc
ConfigProperties test fix
sahibamittal Jun 10, 2022
594af37
Vulnerability mapping done
sahibamittal Jun 14, 2022
7b6ea43
unit test for osv task
sahibamittal Jun 15, 2022
a9a0783
osv enabled default to true
sahibamittal Jun 16, 2022
a8adf36
fixes and tests
sahibamittal Jun 17, 2022
eeb1372
fix http client
sahibamittal Jun 17, 2022
dba49b5
update source of vulnerability
sahibamittal Jun 23, 2022
0fb6ad1
map credits
sahibamittal Jun 23, 2022
6f725af
minor changes
sahibamittal Jun 24, 2022
b987030
close reader
sahibamittal Jun 24, 2022
cca6c9f
update severity calculation and prioritize
sahibamittal Jun 28, 2022
cdf1e90
handle vulnerability mapping to avoid whole task
sahibamittal Jun 28, 2022
7fe4c5d
fix out of bound exception
sahibamittal Jun 28, 2022
2edf945
changes to avoid clashing with github or nvd
sahibamittal Jun 29, 2022
7fb3b42
fix for commit hash ranges and small changes requested
sahibamittal Jun 30, 2022
d8f836a
handle purl parsing
sahibamittal Jul 1, 2022
985a58f
handle version range types, disable default osv
sahibamittal Jul 1, 2022
94072d5
fix de duplication of vulnerable softwares
sahibamittal Jul 1, 2022
7b0afee
small test fix
sahibamittal Jul 1, 2022
d66e211
Merge branch 'master' into google-osv-support
sahibamittal Jul 1, 2022
0477ecd
Perform `null` check before parsing PURLs
nscuro Jul 2, 2022
a8aba45
Adjust class names to rest of the code base
nscuro Jul 2, 2022
da1d059
Remove redundant QueryManager method; Test more mapped vulnerability …
nscuro Jul 2, 2022
bb57600
Refactor OSV range parsing to avoid infinite loops
nscuro Jul 2, 2022
c6c687e
Fetch `Vulnerability#vulnerableSoftware` lazily
nscuro Jul 3, 2022
d855040
change OSV label from Google
sahibamittal Jul 21, 2022
0c23fac
Merge remote-tracking branch 'upstream/master' into google-osv-support
sahibamittal Jul 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -114,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);
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/dependencytrack/event/GoogleOSVMirrorEvent.java
Original file line number Diff line number Diff line change
@@ -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 {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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", "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"),
Expand Down
28 changes: 22 additions & 6 deletions src/main/java/org/dependencytrack/model/Severity.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,33 @@
*/
package org.dependencytrack.model;

import java.util.Arrays;

/**
* Defines internal severity labels.
*
* @author Steve Springett
* @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);
}
}
3 changes: 2 additions & 1 deletion src/main/java/org/dependencytrack/model/Vulnerability.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -102,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");
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package org.dependencytrack.parser.osv;

import kong.unirest.json.JSONArray;
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;
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.
*/
public class GoogleOSVAdvisoryParser {

public OSVAdvisory parse(final JSONObject object) {

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(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)));
advisory.setSchema_version(object.optString("schema_version", null));

final JSONArray references = object.optJSONArray("references");
if (references != null) {
for (int i=0; i<references.length(); i++) {
final JSONObject reference = references.getJSONObject(i);
final String url = reference.optString("url", null);
advisory.addReference(url);
}
}

final JSONArray credits = object.optJSONArray("credits");
if (credits != null) {
for (int i=0; i<credits.length(); i++) {
final JSONObject credit = credits.getJSONObject(i);
final String name = credit.optString("name", null);
advisory.addCredit(name);
}
}

final JSONArray aliases = object.optJSONArray("aliases");
if(aliases != null) {
for (int i=0; i<aliases.length(); i++) {
advisory.addAlias(aliases.optString(i));
}
}

final JSONObject databaseSpecific = object.optJSONObject("database_specific");
if (databaseSpecific != null) {
advisory.setSeverity(databaseSpecific.optString("severity", null));
final JSONArray cweIds = databaseSpecific.optJSONArray("cwe_ids");
if(cweIds != null) {
for (int i=0; i<cweIds.length(); i++) {
advisory.addCweId(cweIds.optString(i));
}
}
}

final JSONArray cvssList = object.optJSONArray("severity");
if (cvssList != null) {
for (int i=0; i<cvssList.length(); i++) {
final JSONObject cvss = cvssList.getJSONObject(i);
final String type = cvss.optString("type", null);
if (type.equalsIgnoreCase("CVSS_V3")) {
advisory.setCvssV3Vector(cvss.optString("score", null));
}
if (type.equalsIgnoreCase("CVSS_V2")) {
advisory.setCvssV2Vector(cvss.optString("score", null));
}
}
}

final List<OSVVulnerability> vulnerabilities = parseVulnerabilities(object);
advisory.setVulnerabilities(vulnerabilities);
}
return advisory;
}

private List<OSVVulnerability> parseVulnerabilities(JSONObject object) {

List<OSVVulnerability> osvVulnerabilityList = new ArrayList<>();
final JSONArray vulnerabilities = object.optJSONArray("affected");
if (vulnerabilities != null) {
for(int i=0; i<vulnerabilities.length(); i++) {

osvVulnerabilityList.addAll(parseVulnerabilityRange(vulnerabilities.getJSONObject(i)));
}
}
return osvVulnerabilityList;
}

public List<OSVVulnerability> parseVulnerabilityRange(JSONObject vulnerability) {

List<OSVVulnerability> osvVulnerabilityList = new ArrayList<>();
final JSONArray ranges = vulnerability.optJSONArray("ranges");
final JSONArray versions = vulnerability.optJSONArray("versions");
if (ranges != null) {
for (int j=0; j<ranges.length(); j++) {
final JSONObject range = ranges.getJSONObject(j);
String rangeType = range.optString("type", null);
if(rangeType != null && !rangeType.equalsIgnoreCase("GIT")) {
osvVulnerabilityList.addAll(parseVersionRanges(vulnerability, range));
}
}
}
// 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<versions.length(); j++) {
OSVVulnerability vuln = createOSVVulnerability(vulnerability);
vuln.setVersion(versions.getString(j));
osvVulnerabilityList.add(vuln);
}
}
// if no parsable range or version is avilable, add vulnerability without version
else if (osvVulnerabilityList.size() == 0) {
osvVulnerabilityList.add(createOSVVulnerability(vulnerability));
}
return osvVulnerabilityList;
}

private List<OSVVulnerability> parseVersionRanges(JSONObject vulnerability, JSONObject range) {

final List<OSVVulnerability> osvVulnerabilityList = new ArrayList<>();
final JSONArray rangeEvents = range.optJSONArray("events");
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 upper = event.optString("fixed", null);
nscuro marked this conversation as resolved.
Show resolved Hide resolved
if(upper != null) {
osvVulnerability.setUpperVersionRange(upper);
k += 1;
}
}
osvVulnerabilityList.add(osvVulnerability);
}
sahibamittal marked this conversation as resolved.
Show resolved Hide resolved
}
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;

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;
if(summary != null && summary.length() > 255) {
return StringUtils.substring(summary, 0, MAX_LEN-2) + "..";
}
return summary;
}
}
31 changes: 31 additions & 0 deletions src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.dependencytrack.parser.osv.model;

public enum Ecosystem {

ANDROID("Android"),
GSD("GSD"),
GO("Go"),
ERLANG("Hex"),
JAVASCRIPT("JavaScript"),
LINUX("Linux"),
MAVEN("Maven"),
NUGET("NuGet"),
OSSFUZZ("OSS-Fuzz"),
PACKAGIST("Packagist"),
PYPI("PyPI"),
RUBYGEMS("RubyGems"),
UVI("UVI"),
RUST("crates.io"),
NPM("npm"),
DWF("DWF");

private final String value;

Ecosystem(final String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
Loading