Skip to content

Commit

Permalink
refactored vers to vulnerable softwares
Browse files Browse the repository at this point in the history
  • Loading branch information
sahibamittal committed Mar 5, 2024
1 parent 37c5f91 commit 56bfc7d
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 70 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
<lib.testcontainers.version>1.19.6</lib.testcontainers.version>
<lib.resilience4j.version>2.2.0</lib.resilience4j.version>
<lib.system-rules.version>1.19.0</lib.system-rules.version>
<lib.versatile.version>0.4.1</lib.versatile.version>
<lib.versatile.version>0.5.0</lib.versatile.version>
<lib.woodstox.version>6.6.0</lib.woodstox.version>
<lib.junit-params.version>1.1.1</lib.junit-params.version>
<lib.log4j-over-slf4j.version>2.0.12</lib.log4j-over-slf4j.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,31 @@ public void process(final Record<String, Bom> record) {
// Alias synchronization across multiple sources is too unreliable right now.
// We can re-enable this once we have more confidence in data quality, or a better
// way of auditing reported aliases. See also: https://github.com/google/osv.dev/issues/888
// if (!cycloneVuln.getReferencesList().isEmpty()) {
// cycloneVuln.getReferencesList().stream().forEach(reference -> {
// final String alias = reference.getId();
// final VulnerabilityAlias vulnerabilityAlias = new VulnerabilityAlias();
//
// // OSV will use IDs of other vulnerability databases for its
// // primary advisory ID (e.g. GHSA-45hx-wfhj-473x). We need to ensure
// // that we don't falsely report GHSA IDs as stemming from OSV.
// final Vulnerability.Source advisorySource = extractSource(cycloneVuln.getId(), cycloneVuln.getSource());
// if (mirrorSource.equals("OSV")) {
// switch (advisorySource) {
// case NVD -> vulnerabilityAlias.setCveId(cycloneVuln.getId());
// case GITHUB -> vulnerabilityAlias.setGhsaId(cycloneVuln.getId());
// default -> vulnerabilityAlias.setOsvId(cycloneVuln.getId());
// }
// }
// if (alias.startsWith("CVE") && Vulnerability.Source.NVD != advisorySource) {
// vulnerabilityAlias.setCveId(alias);
// qm.synchronizeVulnerabilityAlias(vulnerabilityAlias);
// } else if (alias.startsWith("GHSA") && Vulnerability.Source.GITHUB != advisorySource) {
// vulnerabilityAlias.setGhsaId(alias);
// qm.synchronizeVulnerabilityAlias(vulnerabilityAlias);
// }
// });
// }
/* if (!cycloneVuln.getReferencesList().isEmpty()) {
cycloneVuln.getReferencesList().stream().forEach(reference -> {
final String alias = reference.getId();
final VulnerabilityAlias vulnerabilityAlias = new VulnerabilityAlias();
// OSV will use IDs of other vulnerability databases for its
// primary advisory ID (e.g. GHSA-45hx-wfhj-473x). We need to ensure
// that we don't falsely report GHSA IDs as stemming from OSV.
final Vulnerability.Source advisorySource = extractSource(cycloneVuln.getId(), cycloneVuln.getSource());
if (mirrorSource.equals("OSV")) {
switch (advisorySource) {
case NVD -> vulnerabilityAlias.setCveId(cycloneVuln.getId());
case GITHUB -> vulnerabilityAlias.setGhsaId(cycloneVuln.getId());
default -> vulnerabilityAlias.setOsvId(cycloneVuln.getId());
}
}
if (alias.startsWith("CVE") && Vulnerability.Source.NVD != advisorySource) {
vulnerabilityAlias.setCveId(alias);
qm.synchronizeVulnerabilityAlias(vulnerabilityAlias);
} else if (alias.startsWith("GHSA") && Vulnerability.Source.GITHUB != advisorySource) {
vulnerabilityAlias.setGhsaId(alias);
qm.synchronizeVulnerabilityAlias(vulnerabilityAlias);
}
});
}*/
final List<VulnerableSoftware> vsList = new ArrayList<>();
for (final VulnerabilityAffects affect : cycloneVuln.getAffectsList()) {
final Optional<Component> component = bom.getComponentsList().stream()
Expand All @@ -90,10 +90,10 @@ public void process(final Record<String, Bom> record) {

affect.getVersionsList().forEach(version -> {
if (version.hasRange()) {
final VulnerableSoftware vs = mapAffectedRangeToVulnerableSoftware(qm,
final List<VulnerableSoftware> vs = mapAffectedRangeToVulnerableSoftwares(qm,
vulnerability.getVulnId(), version.getRange(), component.get().getPurl(), component.get().getCpe());
if (vs != null) {
vsList.add(vs);
vsList.addAll(vs);
}
}
if (version.hasVersion()) {
Expand Down Expand Up @@ -167,61 +167,59 @@ public VulnerableSoftware mapAffectedVersionToVulnerableSoftware(final QueryMana
return vs;
}

public VulnerableSoftware mapAffectedRangeToVulnerableSoftware(final QueryManager qm, final String vulnId,
String range, String purlStr, String cpeStr) {
public List<VulnerableSoftware> mapAffectedRangeToVulnerableSoftwares(final QueryManager qm, final String vulnId,
String range, String purlStr, String cpeStr) {
range = StringUtils.trimToNull(range);
cpeStr = StringUtils.trimToNull(cpeStr);
purlStr = StringUtils.trimToNull(purlStr);
if (range == null || (cpeStr == null && purlStr == null)) {
return null;
}

final Vers vers;
final List<VulnerableSoftware> vsList = new ArrayList<>();
final List<Vers> versList;
try {
vers = Vers.parse(range).validate();
// Calling split to address ranges with all possible length of constraints
versList = Vers.parse(range).validate().split();
} catch (VersException e) {
LOGGER.warn("Failed to parse vers range from \"%s\" for %s".formatted(range, vulnId), e);
return null;
}

if (vers.constraints().isEmpty()) {
LOGGER.debug("Vers range \"%s\" (parsed: %s) for %s does not contain any constraints; Skipping".formatted(range, vers, vulnId));
return null;
} else if (vers.constraints().size() > 2) {
// Vers ranges can express multiple "branches", which means that this method must potentially be able to return
// multiple `VulnerableSoftware`s, not just one. For example:
// vers:tomee/>=1.0.0-beta1|<=1.7.5|>=7.0.0-M1|<=7.0.7|>=7.1.0|<=7.1.2|>=8.0.0-M1|<=8.0.1
// would result in the following branches:
// * vers:tomee/>=1.0.0-beta1|<=1.7.5
// * vers:tomee/>=7.0.0-M1|<=7.0.7
// * vers:tomee/>=7.1.0|<=7.1.2
// * vers:tomee/>=8.0.0-M1|<=8.0.1
//
// Branches are not always pairs, for example:
// vers:npm/1.2.3|>=2.0.0|<5.0.0
// would result in:
// * vers:npm/1.2.3
// * vers:npm/>=2.0.0|5.0.0
// In both cases, separate `VulnerableSoftware`s are required.
//
// Because mirror-service does not currently produce such complex ranges, we log a warning
// and skip them if we encounter them. Occurrences of this log would indicate broken parsing
// logic in mirror-service.
LOGGER.warn("Vers range \"%s\" (parsed: %s) for %s contains more than two constraints; Skipping".formatted(range, vers, vulnId));
return null;
for (var vers : versList) {
if (vers.constraints().isEmpty()) {
LOGGER.debug("Vers range \"%s\" (parsed: %s) for %s does not contain any constraints; Skipping".formatted(range, vers, vulnId));
continue;
}
else if (vers.constraints().size() == 1) {
var versConstraint = vers.constraints().get(0);
if (versConstraint.comparator() == Comparator.WILDCARD) {
// Wildcards in VulnerableSoftware can be represented via either:
// * version=*, or
// * versionStartIncluding=0
// We choose the more explicit first option.
//
// Also, as wildcards have the potential to lead to lots of false positives,
// we want to be informed when they enter our system. So logging a warning.
LOGGER.warn("Wildcard range %s was reported for %s".formatted(vers, vulnId));
vsList.add(mapAffectedVersionToVulnerableSoftware(qm, vulnId, "*", purlStr, cpeStr));
continue;
} else if (versConstraint.comparator() == Comparator.EQUAL
&& !versConstraint.version().toString().equals("0")) {
// Mapping single exact versions (greater than 0) to vulnerable software.
vsList.add(mapAffectedVersionToVulnerableSoftware(qm, vulnId, String.valueOf(versConstraint.version()), purlStr, cpeStr));
continue;
}
}
var vulnerableSoftware = convertVersToVulnerableSoftware(qm, vers, vulnId, purlStr, cpeStr);
if (vulnerableSoftware != null) {
vsList.add(convertVersToVulnerableSoftware(qm, vers, vulnId, purlStr, cpeStr));
}
}
return vsList;
}

if (vers.constraints().size() == 1 && vers.constraints().get(0).comparator() == Comparator.WILDCARD) {
// Wildcards in VulnerableSoftware can be represented via either:
// * version=*, or
// * versionStartIncluding=0
// We choose the more explicit first option.
//
// Also, as wildcards have the potential to lead to lots of false positives,
// we want to be informed when they enter our system. So logging a warning.
LOGGER.warn("Wildcard range %s was reported for %s".formatted(vers, vulnId));
return mapAffectedVersionToVulnerableSoftware(qm, vulnId, "*", purlStr, cpeStr);
}
private VulnerableSoftware convertVersToVulnerableSoftware(QueryManager qm, Vers vers, String vulnId, String purlStr, String cpeStr) {

Check warning on line 222 in src/main/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessor.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessor.java#L222

The method 'convertVersToVulnerableSoftware(QueryManager, Vers, String, String, String)' has an NPath complexity of 4200, current threshold is 200

String versionStartIncluding = null;
String versionStartExcluding = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,10 +710,9 @@ public void testProcessVulnWithVersConstraints() throws Exception {
{ "range": "vers:generic/*" },
{ "range": "vers:generic/0" },
{ "range": "vers:generic/>0" },
{ "range": "vers:generic/1" },
{ "range": "vers:generic/1.2.1" },
{ "range": "vers:generic/>2" },
{ "range": "vers:generic/>3|< 4" },
{ "range": "vers:generic/>5|<6|6.0.1" },
{ "range": "vers:generic/>*|<7" },
{ "range": "vers:generic/>8" },
{ "range": "vers:generic/>9|>=10" },
Expand Down Expand Up @@ -822,6 +821,33 @@ public void testProcessVulnWithVersConstraints() throws Exception {
assertThat(vs.getPurlSubpath()).isNull();
assertThat(vs.getPurl()).isNull();
},
vs -> {
assertThat(vs.getCpe22()).isEqualTo("cpe:/a:thinkcmf:thinkcmf");
assertThat(vs.getCpe23()).isEqualTo("cpe:2.3:a:thinkcmf:thinkcmf:*:*:*:*:*:*:*:*");
assertThat(vs.getPart()).isEqualTo("a");
assertThat(vs.getVendor()).isEqualTo("thinkcmf");
assertThat(vs.getProduct()).isEqualTo("thinkcmf");
assertThat(vs.getVersion()).isEqualTo("1.2.1");
assertThat(vs.getUpdate()).isEqualTo("*");
assertThat(vs.getEdition()).isEqualTo("*");
assertThat(vs.getLanguage()).isEqualTo("*");
assertThat(vs.getSwEdition()).isEqualTo("*");
assertThat(vs.getTargetSw()).isEqualTo("*");
assertThat(vs.getTargetHw()).isEqualTo("*");
assertThat(vs.getOther()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isNull();
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isNull();
assertThat(vs.getVersionEndExcluding()).isNull();
assertThat(vs.isVulnerable()).isTrue();
assertThat(vs.getPurlType()).isNull();
assertThat(vs.getPurlNamespace()).isNull();
assertThat(vs.getPurlName()).isNull();
assertThat(vs.getPurlVersion()).isNull();
assertThat(vs.getPurlQualifiers()).isNull();
assertThat(vs.getPurlSubpath()).isNull();
assertThat(vs.getPurl()).isNull();
},
vs -> {
assertThat(vs.getCpe22()).isEqualTo("cpe:/a:thinkcmf:thinkcmf");
assertThat(vs.getCpe23()).isEqualTo("cpe:2.3:a:thinkcmf:thinkcmf:*:*:*:*:*:*:*:*");
Expand Down Expand Up @@ -933,6 +959,104 @@ public void testProcessVulnWithVersConstraints() throws Exception {
);
}

@Test
public void testProcessVulnWithVersConstraintsMoreThanTwo() throws Exception {
inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson("""
{
"components": [
{
"bomRef": "02cd44fb-2f0a-569b-a508-1e179e123e38",
"type": "CLASSIFICATION_APPLICATION",
"publisher": "thinkcmf",
"name": "thinkcmf",
"cpe": "cpe:2.3:a:thinkcmf:thinkcmf:*:*:*:*:*:*:*:*"
},
{
"bomRef": "ed08bfc7-e88a-4647-bb2a-cad271aec5cc",
"purl": "pkg:maven/com.example/foo"
}
],
"vulnerabilities": [
{
"id": "CVE-2022-40489",
"source": { "name": "NVD" },
"affects": [
{
"ref": "02cd44fb-2f0a-569b-a508-1e179e123e38",
"versions": [
{ "range": "vers:generic/>5|<6|6.0.1" },
{ "range": "vers:generic/>=1.0.0-beta1|<=1.7.5|>=7.0.0-M1|<=7.0.7|>=7.1.0|<=7.1.2|>=8.0.0-M1|<=8.0.1" },
{ "range": "vers:generic/1.2.3|>=2.0.0|<5.0.0" }
]
}
]
}
]
}
"""));

final Vulnerability vuln = qm.getVulnerabilityByVulnId("NVD", "CVE-2022-40489");
assertThat(vuln).isNotNull();
assertThat(vuln.getVulnerableSoftware()).satisfiesExactly(
vs -> {
assertThat(vs.getVersion()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isNull();
assertThat(vs.getVersionStartExcluding()).isEqualTo("5");
assertThat(vs.getVersionEndIncluding()).isNull();
assertThat(vs.getVersionEndExcluding()).isEqualTo("6");
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("6.0.1");
assertThat(vs.getVersionStartIncluding()).isNull();
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isNull();
assertThat(vs.getVersionEndExcluding()).isNull();
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isEqualTo("1.0.0-beta1");
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isEqualTo("1.7.5");
assertThat(vs.getVersionEndExcluding()).isNull();
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isEqualTo("7.0.0-M1");
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isEqualTo("7.0.7");
assertThat(vs.getVersionEndExcluding()).isNull();
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isEqualTo("7.1.0");
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isEqualTo("7.1.2");
assertThat(vs.getVersionEndExcluding()).isNull();
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isEqualTo("8.0.0-M1");
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isEqualTo("8.0.1");
assertThat(vs.getVersionEndExcluding()).isNull();
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("1.2.3");
assertThat(vs.getVersionStartIncluding()).isNull();
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isNull();
assertThat(vs.getVersionEndExcluding()).isNull();
},
vs -> {
assertThat(vs.getVersion()).isEqualTo("*");
assertThat(vs.getVersionStartIncluding()).isEqualTo("2.0.0");
assertThat(vs.getVersionStartExcluding()).isNull();
assertThat(vs.getVersionEndIncluding()).isNull();
assertThat(vs.getVersionEndExcluding()).isEqualTo("5.0.0");
}
);
}

@Test
public void testProcessVulnWithInvalidCpeOrPurl() throws Exception {
inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson("""
Expand Down

0 comments on commit 56bfc7d

Please sign in to comment.