Skip to content

Commit 286d2d7

Browse files
committed
feat: support multple cvssBelow thesholds per version (#2563)
1 parent 98fa933 commit 286d2d7

File tree

7 files changed

+424
-20
lines changed

7 files changed

+424
-20
lines changed

core/src/main/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ protected String getAnalyzerEnabledSettingKey() {
7676

7777
@Override
7878
public boolean filter(SuppressionRule rule) {
79-
return rule.hasCve() || rule.hasCvssBelow() || rule.hasCwe() || rule.hasVulnerabilityName();
79+
return rule.hasCve() || rule.hasCvssBelow() || rule.hasCvssV2Below() || rule.hasCvssV3Below() || rule.hasCvssV4Below() || rule.hasCwe() || rule.hasVulnerabilityName();
8080
}
8181

8282
@Override

core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandler.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ public class SuppressionHandler extends DefaultHandler {
8888
* The cvssBelow element name.
8989
*/
9090
public static final String CVSS_BELOW = "cvssBelow";
91+
/**
92+
* The cvssV2Below element name.
93+
*/
94+
public static final String CVSS_V2_BELOW = "cvssV2Below";
95+
/**
96+
* The cvssV3Below element name.
97+
*/
98+
public static final String CVSS_V3_BELOW = "cvssV3Below";
99+
/**
100+
* The cvssV4Below element name.
101+
*/
102+
public static final String CVSS_V4_BELOW = "cvssV4Below";
91103
/**
92104
* A list of suppression rules.
93105
*/
@@ -197,6 +209,18 @@ public void endElement(String uri, String localName, String qName) throws SAXExc
197209
final Double cvss = Double.valueOf(currentText.toString().trim());
198210
rule.addCvssBelow(cvss);
199211
break;
212+
case CVSS_V2_BELOW:
213+
final Double cvssV2 = Double.valueOf(currentText.toString().trim());
214+
rule.addCvssV2Below(cvssV2);
215+
break;
216+
case CVSS_V3_BELOW:
217+
final Double cvssV3 = Double.valueOf(currentText.toString().trim());
218+
rule.addCvssV3Below(cvssV3);
219+
break;
220+
case CVSS_V4_BELOW:
221+
final Double cvssV4 = Double.valueOf(currentText.toString().trim());
222+
rule.addCvssV4Below(cvssV4);
223+
break;
200224
default:
201225
break;
202226
}

core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParser.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public class SuppressionParser {
5454
* The logger.
5555
*/
5656
private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionParser.class);
57+
/**
58+
* The suppression schema file location for v 1.4.
59+
*/
60+
public static final String SUPPRESSION_SCHEMA_1_4 = "schema/dependency-suppression.1.4.xsd";
5761
/**
5862
* The suppression schema file location for v 1.3.
5963
*/
@@ -101,6 +105,7 @@ public List<SuppressionRule> parseSuppressionRules(File file) throws Suppression
101105
public List<SuppressionRule> parseSuppressionRules(InputStream inputStream)
102106
throws SuppressionParseException, SAXException {
103107
try (
108+
InputStream schemaStream14 = FileUtils.getResourceAsStream(SUPPRESSION_SCHEMA_1_4);
104109
InputStream schemaStream13 = FileUtils.getResourceAsStream(SUPPRESSION_SCHEMA_1_3);
105110
InputStream schemaStream12 = FileUtils.getResourceAsStream(SUPPRESSION_SCHEMA_1_2);
106111
InputStream schemaStream11 = FileUtils.getResourceAsStream(SUPPRESSION_SCHEMA_1_1);
@@ -112,7 +117,7 @@ public List<SuppressionRule> parseSuppressionRules(InputStream inputStream)
112117
final String charsetName = bom == null ? defaultEncoding : bom.getCharsetName();
113118

114119
final SuppressionHandler handler = new SuppressionHandler();
115-
final SAXParser saxParser = XmlUtils.buildSecureSaxParser(schemaStream13, schemaStream12, schemaStream11, schemaStream10);
120+
final SAXParser saxParser = XmlUtils.buildSecureSaxParser(schemaStream14, schemaStream13, schemaStream12, schemaStream11, schemaStream10);
116121
final XMLReader xmlReader = saxParser.getXMLReader();
117122
xmlReader.setErrorHandler(new SuppressionErrorHandler());
118123
xmlReader.setContentHandler(handler);

core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionRule.java

Lines changed: 177 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ public class SuppressionRule {
6363
* The list of cvssBelow scores.
6464
*/
6565
private List<Double> cvssBelow = new ArrayList<>();
66+
/**
67+
* The list of cvssV2Below scores.
68+
*/
69+
private List<Double> cvssV2Below = new ArrayList<>();
70+
/**
71+
* The list of cvssV3Below scores.
72+
*/
73+
private List<Double> cvssV3Below = new ArrayList<>();
74+
/**
75+
* The list of cvssV4Below scores.
76+
*/
77+
private List<Double> cvssV4Below = new ArrayList<>();
6678
/**
6779
* The list of CWE entries to suppress.
6880
*/
@@ -261,6 +273,114 @@ public boolean hasCvssBelow() {
261273
return !cvssBelow.isEmpty();
262274
}
263275

276+
/**
277+
* Get the value of cvssV2Below.
278+
*
279+
* @return the value of cvssV2Below
280+
*/
281+
public List<Double> getCvssV2Below() {
282+
return cvssV2Below;
283+
}
284+
285+
/**
286+
* Set the value of cvssV2Below.
287+
*
288+
* @param cvssV2Below new value of cvssV2Below
289+
*/
290+
public void setCvssV2Below(List<Double> cvssV2Below) {
291+
this.cvssV2Below = cvssV2Below;
292+
}
293+
294+
/**
295+
* Adds the CVSS to the cvssV2Below list.
296+
*
297+
* @param cvss the CVSS to add
298+
*/
299+
public void addCvssV2Below(Double cvss) {
300+
this.cvssV2Below.add(cvss);
301+
}
302+
303+
/**
304+
* Returns whether or not this suppression rule has CVSS v2 suppression criteria.
305+
*
306+
* @return whether or not this suppression rule has CVSS v2 suppression criteria.
307+
*/
308+
public boolean hasCvssV2Below() {
309+
return !cvssV2Below.isEmpty();
310+
}
311+
312+
/**
313+
* Get the value of cvssV3Below.
314+
*
315+
* @return the value of cvssV3Below
316+
*/
317+
public List<Double> getCvssV3Below() {
318+
return cvssV3Below;
319+
}
320+
321+
/**
322+
* Set the value of cvssV3Below.
323+
*
324+
* @param cvssV3Below new value of cvssV3Below
325+
*/
326+
public void setCvssV3Below(List<Double> cvssV3Below) {
327+
this.cvssV3Below = cvssV3Below;
328+
}
329+
330+
/**
331+
* Adds the CVSS to the cvssV3Below list.
332+
*
333+
* @param cvss the CVSS to add
334+
*/
335+
public void addCvssV3Below(Double cvss) {
336+
this.cvssV3Below.add(cvss);
337+
}
338+
339+
/**
340+
* Returns whether or not this suppression rule has CVSS v3 suppression criteria.
341+
*
342+
* @return whether or not this suppression rule has CVSS v3 suppression criteria.
343+
*/
344+
public boolean hasCvssV3Below() {
345+
return !cvssV3Below.isEmpty();
346+
}
347+
348+
/**
349+
* Get the value of cvssV4Below.
350+
*
351+
* @return the value of cvssV4Below
352+
*/
353+
public List<Double> getCvssV4Below() {
354+
return cvssV4Below;
355+
}
356+
357+
/**
358+
* Set the value of cvssV4Below.
359+
*
360+
* @param cvssV4Below new value of cvssV4Below
361+
*/
362+
public void setCvssV4Below(List<Double> cvssV4Below) {
363+
this.cvssV4Below = cvssV4Below;
364+
}
365+
366+
/**
367+
* Adds the CVSS to the cvssV4Below list.
368+
*
369+
* @param cvss the CVSS to add
370+
*/
371+
public void addCvssV4Below(Double cvss) {
372+
this.cvssV4Below.add(cvss);
373+
}
374+
375+
/**
376+
* Returns whether or not this suppression rule has CVSS v4 suppression criteria.
377+
*
378+
* @return whether or not this suppression rule has CVSS v4 suppression criteria.
379+
*/
380+
public boolean hasCvssV4Below() {
381+
return !cvssV4Below.isEmpty();
382+
}
383+
264384
/**
265385
* Get the value of notes.
266386
*
@@ -494,7 +614,7 @@ public void process(Dependency dependency) {
494614
}
495615
removalList.forEach(dependency::removeVulnerableSoftwareIdentifier);
496616
}
497-
if (hasCve() || hasVulnerabilityName() || hasCwe() || hasCvssBelow()) {
617+
if (hasCve() || hasVulnerabilityName() || hasCwe() || hasCvssBelow() || hasCvssV2Below() || hasCvssV3Below() || hasCvssV4Below()) {
498618
final Set<Vulnerability> removeVulns = new HashSet<>();
499619
for (Vulnerability v : dependency.getVulnerabilities()) {
500620
boolean remove = false;
@@ -525,23 +645,9 @@ public void process(Dependency dependency) {
525645
}
526646
}
527647
if (!remove) {
528-
for (Double cvss : this.cvssBelow) {
529-
//TODO validate this comparison
530-
if (v.getCvssV2() != null && v.getCvssV2().getCvssData().getBaseScore().compareTo(cvss) < 0) {
531-
remove = true;
532-
removeVulns.add(v);
533-
break;
534-
}
535-
if (v.getCvssV3() != null && v.getCvssV3().getCvssData().getBaseScore().compareTo(cvss) < 0) {
536-
remove = true;
537-
removeVulns.add(v);
538-
break;
539-
}
540-
if (v.getCvssV4() != null && v.getCvssV4().getCvssData().getBaseScore().compareTo(cvss) < 0) {
541-
remove = true;
542-
removeVulns.add(v);
543-
break;
544-
}
648+
if (suppressedBasedOnScore(v)) {
649+
remove = true;
650+
removeVulns.add(v);
545651
}
546652
}
547653
if (remove && !isBase()) {
@@ -556,6 +662,44 @@ public void process(Dependency dependency) {
556662
}
557663
}
558664

665+
boolean suppressedBasedOnScore(Vulnerability v) {
666+
if (!cvssBelow.isEmpty()) {
667+
for (Double cvss : this.cvssBelow) {
668+
//TODO validate this comparison
669+
if (v.getCvssV2() != null && v.getCvssV2().getCvssData().getBaseScore().compareTo(cvss) < 0) {
670+
return true;
671+
}
672+
if (v.getCvssV3() != null && v.getCvssV3().getCvssData().getBaseScore().compareTo(cvss) < 0) {
673+
return true;
674+
}
675+
if (v.getCvssV4() != null && v.getCvssV4().getCvssData().getBaseScore().compareTo(cvss) < 0) {
676+
return true;
677+
}
678+
}
679+
return false;
680+
}
681+
682+
if (hasCvssV2Below() || hasCvssV3Below() || hasCvssV4Below()) {
683+
Double v2SuppressionThreshold = this.cvssV2Below.stream().max(Double::compare).orElse(11.0);
684+
Double v3SuppressionThreshold = this.cvssV3Below.stream().max(Double::compare).orElse(11.0);
685+
Double v4SuppressionThreshold = this.cvssV4Below.stream().max(Double::compare).orElse(11.0);
686+
687+
Double v2Score = v.getCvssV2() != null ? v.getCvssV2().getCvssData().getBaseScore() : null;
688+
Double v3Score = v.getCvssV3() != null ? v.getCvssV3().getCvssData().getBaseScore() : null;
689+
Double v4Score = v.getCvssV4() != null ? v.getCvssV4().getCvssData().getBaseScore() : null;
690+
691+
// only if all version indicate suppression will the vulnerability be suppressed
692+
// so if we are missing data (score or threshold) for a specific version we assume suppression
693+
boolean cvssV2CheckSuppressing = v2Score == null || v2Score < v2SuppressionThreshold;
694+
boolean cvssV3CheckSuppressing = v3Score == null || v3Score < v3SuppressionThreshold;
695+
boolean cvssV4CheckSuppressing = v4Score == null || v4Score < v4SuppressionThreshold;
696+
697+
return cvssV2CheckSuppressing && cvssV3CheckSuppressing && cvssV4CheckSuppressing;
698+
}
699+
700+
return false;
701+
}
702+
559703
/**
560704
* Identifies if the cpe specified by the cpe suppression rule does not
561705
* specify a version.
@@ -694,6 +838,21 @@ public String toString() {
694838
cvssBelow.forEach((s) -> sb.append(s).append(','));
695839
sb.append('}');
696840
}
841+
if (cvssV2Below != null && !cvssV2Below.isEmpty()) {
842+
sb.append("cvssV2Below={");
843+
cvssV2Below.forEach((s) -> sb.append(s).append(','));
844+
sb.append('}');
845+
}
846+
if (cvssV3Below != null && !cvssV3Below.isEmpty()) {
847+
sb.append("cvssV3Below={");
848+
cvssV3Below.forEach((s) -> sb.append(s).append(','));
849+
sb.append('}');
850+
}
851+
if (cvssV4Below != null && !cvssV4Below.isEmpty()) {
852+
sb.append("cvssV4Below={");
853+
cvssV4Below.forEach((s) -> sb.append(s).append(','));
854+
sb.append('}');
855+
}
697856
sb.append('}');
698857
return sb.toString();
699858
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema id="suppressions"
3+
xmlns:xs="http://www.w3.org/2001/XMLSchema"
4+
elementFormDefault="qualified"
5+
targetNamespace="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.4.xsd"
6+
xmlns:dc="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.4.xsd">
7+
8+
<xs:complexType name="regexStringType">
9+
<xs:simpleContent>
10+
<xs:extension base="xs:string">
11+
<xs:attribute name="regex" use="optional" type="xs:boolean" default="false"/>
12+
<xs:attribute name="caseSensitive" use="optional" type="xs:boolean" default="false"/>
13+
</xs:extension>
14+
</xs:simpleContent>
15+
</xs:complexType>
16+
<xs:simpleType name="cvssScoreType">
17+
<xs:restriction base="xs:decimal">
18+
<xs:minInclusive value="0"/>
19+
<xs:maxInclusive value="10"/>
20+
</xs:restriction>
21+
</xs:simpleType>
22+
<xs:simpleType name="cveType">
23+
<xs:restriction base="xs:string">
24+
<xs:pattern value="((\w+\-)?CVE\-\d\d\d\d\-\d+|\d+)"/>
25+
</xs:restriction>
26+
</xs:simpleType>
27+
<xs:simpleType name="sha1Type">
28+
<xs:restriction base="xs:string">
29+
<xs:pattern value="[a-fA-F0-9]{40}"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
<xs:element name="suppressions">
33+
<xs:complexType>
34+
<xs:sequence minOccurs="0" maxOccurs="unbounded">
35+
<xs:element name="suppress">
36+
<xs:complexType>
37+
<xs:sequence minOccurs="1" maxOccurs="1">
38+
<xs:sequence minOccurs="0" maxOccurs="1">
39+
<xs:element name="notes" type="xs:string"/>
40+
</xs:sequence>
41+
<xs:choice minOccurs="0" maxOccurs="1">
42+
<xs:element name="filePath" type="dc:regexStringType"/>
43+
<xs:element name="sha1" type="dc:sha1Type"/>
44+
<xs:element name="gav" type="dc:regexStringType"/>
45+
<xs:element name="packageUrl" type="dc:regexStringType"/>
46+
</xs:choice>
47+
<xs:choice minOccurs="1" maxOccurs="unbounded">
48+
<xs:element name="cpe" type="dc:regexStringType"/>
49+
<xs:element name="cve" type="dc:cveType"/>
50+
<xs:element name="vulnerabilityName" type="dc:regexStringType"/>
51+
<xs:element name="cwe" type="xs:positiveInteger"/>
52+
<xs:element name="cvssBelow" type="dc:cvssScoreType"/>
53+
<xs:element name="cvssV2Below" type="dc:cvssScoreType" />
54+
<xs:element name="cvssV3Below" type="dc:cvssScoreType" />
55+
<xs:element name="cvssV4Below" type="dc:cvssScoreType" />
56+
</xs:choice>
57+
</xs:sequence>
58+
<xs:attribute name="base" use="optional" type="xs:boolean" default="false"/>
59+
<xs:attribute name="until" use="optional" type="xs:date">
60+
<xs:annotation>
61+
<xs:documentation>
62+
When specified the suppression will only be active when the specified date is still in the future. On and after the 'until' date the suppression will no longer be active.
63+
</xs:documentation>
64+
</xs:annotation>
65+
</xs:attribute>
66+
</xs:complexType>
67+
</xs:element>
68+
</xs:sequence>
69+
</xs:complexType>
70+
</xs:element>
71+
</xs:schema>
72+

0 commit comments

Comments
 (0)