1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.xml.suppression;
19
20 import org.apache.commons.lang3.Strings;
21 import org.apache.commons.lang3.time.DateFormatUtils;
22 import org.jspecify.annotations.NonNull;
23 import org.owasp.dependencycheck.dependency.Dependency;
24 import org.owasp.dependencycheck.dependency.Vulnerability;
25 import org.owasp.dependencycheck.dependency.naming.CpeIdentifier;
26 import org.owasp.dependencycheck.dependency.naming.Identifier;
27 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import us.springett.parsers.cpe.Cpe;
31 import us.springett.parsers.cpe.exceptions.CpeEncodingException;
32
33 import javax.annotation.concurrent.NotThreadSafe;
34 import java.util.ArrayList;
35 import java.util.Calendar;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Objects;
39 import java.util.Set;
40
41
42
43
44
45 @NotThreadSafe
46 public class SuppressionRule {
47
48
49
50
51 private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionRule.class);
52
53
54
55 private PropertyType filePath;
56
57
58
59
60 private String sha1;
61
62
63
64 private List<PropertyType> cpe = new ArrayList<>();
65
66
67
68 private List<Double> cvssBelow = new ArrayList<>();
69
70
71
72 private List<Double> cvssV2Below = new ArrayList<>();
73
74
75
76 private List<Double> cvssV3Below = new ArrayList<>();
77
78
79
80 private List<Double> cvssV4Below = new ArrayList<>();
81
82
83
84 private List<String> cwe = new ArrayList<>();
85
86
87
88 private List<String> cve = new ArrayList<>();
89
90
91
92 private final List<PropertyType> vulnerabilityNames = new ArrayList<>();
93
94
95
96 private PropertyType gav = null;
97
98
99
100 private PropertyType packageUrl = null;
101
102
103
104
105 private String notes;
106
107
108
109
110
111
112 private boolean base;
113
114
115
116
117
118
119 private Calendar until;
120
121
122
123
124 private boolean matched = false;
125
126
127
128
129
130
131 public boolean isMatched() {
132 return matched;
133 }
134
135
136
137
138
139
140 public void setMatched(boolean matched) {
141 this.matched = matched;
142 }
143
144
145
146
147
148
149 public Calendar getUntil() {
150 return until;
151 }
152
153
154
155
156
157
158 public void setUntil(Calendar until) {
159 this.until = until;
160 }
161
162
163
164
165
166
167 public PropertyType getFilePath() {
168 return filePath;
169 }
170
171
172
173
174
175
176 public void setFilePath(PropertyType filePath) {
177 this.filePath = filePath;
178 }
179
180
181
182
183
184
185 public String getSha1() {
186 return sha1;
187 }
188
189
190
191
192
193
194 public void setSha1(String sha1) {
195 this.sha1 = sha1;
196 }
197
198
199
200
201
202
203 public List<PropertyType> getCpe() {
204 return cpe;
205 }
206
207
208
209
210
211
212 public void setCpe(List<PropertyType> cpe) {
213 this.cpe = cpe;
214 }
215
216
217
218
219
220
221 public void addCpe(PropertyType cpe) {
222 this.cpe.add(cpe);
223 }
224
225
226
227
228
229
230 public void addVulnerabilityName(PropertyType name) {
231 this.vulnerabilityNames.add(name);
232 }
233
234
235
236
237
238
239 public boolean hasCpe() {
240 return !cpe.isEmpty();
241 }
242
243
244
245
246
247
248 public List<Double> getCvssBelow() {
249 return cvssBelow;
250 }
251
252
253
254
255
256
257 public void setCvssBelow(List<Double> cvssBelow) {
258 this.cvssBelow = cvssBelow;
259 }
260
261
262
263
264
265
266 public void addCvssBelow(Double cvss) {
267 this.cvssBelow.add(cvss);
268 }
269
270
271
272
273
274
275 public boolean hasCvssBelow() {
276 return !cvssBelow.isEmpty();
277 }
278
279
280
281
282
283
284 public List<Double> getCvssV2Below() {
285 return cvssV2Below;
286 }
287
288
289
290
291
292
293 public void setCvssV2Below(List<Double> cvssV2Below) {
294 this.cvssV2Below = cvssV2Below;
295 }
296
297
298
299
300
301
302 public void addCvssV2Below(Double cvss) {
303 this.cvssV2Below.add(cvss);
304 }
305
306
307
308
309
310
311 public boolean hasCvssV2Below() {
312 return !cvssV2Below.isEmpty();
313 }
314
315
316
317
318
319
320 public List<Double> getCvssV3Below() {
321 return cvssV3Below;
322 }
323
324
325
326
327
328
329 public void setCvssV3Below(List<Double> cvssV3Below) {
330 this.cvssV3Below = cvssV3Below;
331 }
332
333
334
335
336
337
338 public void addCvssV3Below(Double cvss) {
339 this.cvssV3Below.add(cvss);
340 }
341
342
343
344
345
346
347 public boolean hasCvssV3Below() {
348 return !cvssV3Below.isEmpty();
349 }
350
351
352
353
354
355
356 public List<Double> getCvssV4Below() {
357 return cvssV4Below;
358 }
359
360
361
362
363
364
365 public void setCvssV4Below(List<Double> cvssV4Below) {
366 this.cvssV4Below = cvssV4Below;
367 }
368
369
370
371
372
373
374 public void addCvssV4Below(Double cvss) {
375 this.cvssV4Below.add(cvss);
376 }
377
378
379
380
381
382
383 public boolean hasCvssV4Below() {
384 return !cvssV4Below.isEmpty();
385 }
386
387
388
389
390
391
392 public String getNotes() {
393 return notes;
394 }
395
396
397
398
399
400
401 public void setNotes(String notes) {
402 this.notes = notes;
403 }
404
405
406
407
408
409
410 public boolean hasNotes() {
411 return !notes.isEmpty();
412 }
413
414
415
416
417
418
419 public List<String> getCwe() {
420 return cwe;
421 }
422
423
424
425
426
427
428 public void setCwe(List<String> cwe) {
429 this.cwe = cwe;
430 }
431
432
433
434
435
436
437 public void addCwe(String cwe) {
438 this.cwe.add(cwe);
439 }
440
441
442
443
444
445
446 public boolean hasCwe() {
447 return !cwe.isEmpty();
448 }
449
450
451
452
453
454
455 public List<String> getCve() {
456 return cve;
457 }
458
459
460
461
462
463
464 public void setCve(List<String> cve) {
465 this.cve = cve;
466 }
467
468
469
470
471
472
473 public void addCve(String cve) {
474 this.cve.add(cve);
475 }
476
477
478
479
480
481
482 public boolean hasCve() {
483 return !cve.isEmpty();
484 }
485
486
487
488
489
490
491 public boolean hasVulnerabilityName() {
492 return !vulnerabilityNames.isEmpty();
493 }
494
495
496
497
498
499
500 public PropertyType getGav() {
501 return gav;
502 }
503
504
505
506
507
508
509 public void setGav(PropertyType gav) {
510 this.gav = gav;
511 }
512
513
514
515
516
517
518 public boolean hasGav() {
519 return gav != null;
520 }
521
522
523
524
525
526
527 public void setPackageUrl(PropertyType purl) {
528 this.packageUrl = purl;
529 }
530
531
532
533
534
535
536 public boolean hasPackageUrl() {
537 return packageUrl != null;
538 }
539
540
541
542
543
544
545 public boolean isBase() {
546 return base;
547 }
548
549
550
551
552
553
554 public void setBase(boolean base) {
555 this.base = base;
556 }
557
558
559
560
561
562
563
564
565 public void process(Dependency dependency) {
566 if (filePath != null && !filePath.matches(dependency.getFilePath())) {
567 return;
568 }
569 if (sha1 != null && !sha1.equalsIgnoreCase(dependency.getSha1sum())) {
570 return;
571 }
572 if (hasGav() && dependency.getSoftwareIdentifiers().stream()
573 .noneMatch(i -> identifierMatches(this.gav, i))) {
574 return;
575 }
576 if (hasPackageUrl() && dependency.getSoftwareIdentifiers().stream()
577 .noneMatch(i -> purlMatches(this.packageUrl, i))) {
578 return;
579 }
580
581 if (hasCpe()) {
582 final Set<Identifier> removeIdentifiers = new HashSet<>();
583 for (Identifier i : dependency.getVulnerableSoftwareIdentifiers()) {
584 for (PropertyType c : this.cpe) {
585 if (identifierMatches(c, i)) {
586 if (!isBase()) {
587 matched = true;
588 if (this.notes != null) {
589 i.setNotes(this.notes);
590 }
591 dependency.addSuppressedIdentifier(i);
592 }
593 removeIdentifiers.add(i);
594 break;
595 }
596 }
597 }
598 removeIdentifiers.forEach(dependency::removeVulnerableSoftwareIdentifier);
599 }
600 if (hasCve() || hasVulnerabilityName() || hasCwe() || hasCvssBelow() || hasCvssV2Below() || hasCvssV3Below() || hasCvssV4Below()) {
601 final Set<Vulnerability> removeVulns = new HashSet<>();
602 for (Vulnerability v : dependency.getVulnerabilities()) {
603 boolean remove = false;
604 for (String entry : this.cve) {
605 if (entry.equalsIgnoreCase(v.getName())) {
606 removeVulns.add(v);
607 remove = true;
608 break;
609 }
610 }
611 if (!remove && this.cwe != null && !v.getCwes().isEmpty()) {
612 for (String entry : this.cwe) {
613 final String toMatch = String.format("CWE-%s", entry);
614 if (v.getCwes().stream().anyMatch(toTest -> toMatch.regionMatches(0, toTest, 0, toMatch.length()))) {
615 remove = true;
616 removeVulns.add(v);
617 break;
618 }
619 }
620 }
621 if (!remove && v.getName() != null) {
622 for (PropertyType entry : this.vulnerabilityNames) {
623 if (entry.matches(v.getName())) {
624 remove = true;
625 removeVulns.add(v);
626 break;
627 }
628 }
629 }
630 if (!remove) {
631 if (suppressedBasedOnScore(v)) {
632 remove = true;
633 removeVulns.add(v);
634 }
635 }
636 if (remove && !isBase()) {
637 matched = true;
638 if (this.notes != null) {
639 v.setNotes(this.notes);
640 }
641 dependency.addSuppressedVulnerability(v);
642 }
643 }
644 removeVulns.forEach(dependency::removeVulnerability);
645 }
646 }
647
648 boolean suppressedBasedOnScore(Vulnerability v) {
649 if (!cvssBelow.isEmpty()) {
650 for (Double cvss : this.cvssBelow) {
651
652 if (v.getCvssV2() != null && v.getCvssV2().getCvssData().getBaseScore().compareTo(cvss) < 0) {
653 return true;
654 }
655 if (v.getCvssV3() != null && v.getCvssV3().getCvssData().getBaseScore().compareTo(cvss) < 0) {
656 return true;
657 }
658 if (v.getCvssV4() != null && v.getCvssV4().getCvssData().getBaseScore().compareTo(cvss) < 0) {
659 return true;
660 }
661 }
662 return false;
663 }
664
665 if (hasCvssV2Below() || hasCvssV3Below() || hasCvssV4Below()) {
666 Double v2SuppressionThreshold = this.cvssV2Below.stream().max(Double::compare).orElse(11.0);
667 Double v3SuppressionThreshold = this.cvssV3Below.stream().max(Double::compare).orElse(11.0);
668 Double v4SuppressionThreshold = this.cvssV4Below.stream().max(Double::compare).orElse(11.0);
669
670 Double v2Score = v.getCvssV2() != null ? v.getCvssV2().getCvssData().getBaseScore() : null;
671 Double v3Score = v.getCvssV3() != null ? v.getCvssV3().getCvssData().getBaseScore() : null;
672 Double v4Score = v.getCvssV4() != null ? v.getCvssV4().getCvssData().getBaseScore() : null;
673
674
675
676 boolean cvssV2CheckSuppressing = v2Score == null || v2Score < v2SuppressionThreshold;
677 boolean cvssV3CheckSuppressing = v3Score == null || v3Score < v3SuppressionThreshold;
678 boolean cvssV4CheckSuppressing = v4Score == null || v4Score < v4SuppressionThreshold;
679
680 return cvssV2CheckSuppressing && cvssV3CheckSuppressing && cvssV4CheckSuppressing;
681 }
682
683 return false;
684 }
685
686
687
688
689
690
691
692
693
694 protected boolean purlMatches(PropertyType suppressionEntry, Identifier identifier) {
695 if (identifier instanceof PurlIdentifier) {
696 final PurlIdentifier purl = (PurlIdentifier) identifier;
697 return suppressionEntry.matches(purl.toString());
698 }
699 return false;
700 }
701
702
703
704
705
706
707
708
709
710 protected boolean identifierMatches(PropertyType suppressionEntry, Identifier identifier) {
711 if (identifier instanceof PurlIdentifier) {
712 final PurlIdentifier purl = (PurlIdentifier) identifier;
713 return suppressionEntry.matches(purl.toGav());
714 } else if (identifier instanceof CpeIdentifier) {
715 final Cpe cpe = ((CpeIdentifier) identifier).getCpe();
716 try {
717
718 String cpe22Uri = cpe.toCpe22Uri();
719 return suppressionEntry.isRegex() ? suppressionEntry.matches(cpe22Uri) : cpe22UriPrefixMatches(suppressionEntry, cpe22Uri);
720 } catch (CpeEncodingException ex) {
721 LOGGER.debug("Unable to convert CPE [{}] to 22 URI due to [{}], will try direct string match to rule.", cpe, ex.toString());
722 }
723 }
724
725 return suppressionEntry.matches(identifier.getValue());
726 }
727
728 private static boolean cpe22UriPrefixMatches(PropertyType suppressionEntry, String cpe22Uri) {
729 String candidate = cpe22Uri + cpePartMatchingSuffixFor(suppressionEntry);
730 return (suppressionEntry.isCaseSensitive() ? Strings.CS : Strings.CI)
731 .startsWith(candidate, suppressionEntry.getValue());
732 }
733
734
735
736
737
738
739
740
741
742 private static @NonNull String cpePartMatchingSuffixFor(PropertyType rule) {
743 return rule.getValue().endsWith(":") ? ":" : "";
744 }
745
746
747
748
749
750
751 @Override
752 public String toString() {
753 final StringBuilder sb = new StringBuilder(64);
754 sb.append("SuppressionRule{");
755 if (until != null) {
756 final String dt = DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(until);
757 sb.append("until=").append(dt).append(',');
758 }
759 if (filePath != null) {
760 sb.append("filePath=").append(filePath).append(',');
761 }
762 if (sha1 != null) {
763 sb.append("sha1=").append(sha1).append(',');
764 }
765 if (packageUrl != null) {
766 sb.append("packageUrl=").append(packageUrl).append(',');
767 }
768 if (gav != null) {
769 sb.append("gav=").append(gav).append(',');
770 }
771 if (cpe != null && !cpe.isEmpty()) {
772 sb.append("cpe={");
773 cpe.forEach((pt) -> sb.append(pt).append(','));
774 sb.append('}');
775 }
776 if (cwe != null && !cwe.isEmpty()) {
777 sb.append("cwe={");
778 cwe.forEach((s) -> sb.append(s).append(','));
779 sb.append('}');
780 }
781 if (cve != null && !cve.isEmpty()) {
782 sb.append("cve={");
783 cve.forEach((s) -> sb.append(s).append(','));
784 sb.append('}');
785 }
786 if (vulnerabilityNames != null && !vulnerabilityNames.isEmpty()) {
787 sb.append("vulnerabilityName={");
788 vulnerabilityNames.forEach((pt) -> sb.append(pt).append(','));
789 sb.append('}');
790 }
791 if (cvssBelow != null && !cvssBelow.isEmpty()) {
792 sb.append("cvssBelow={");
793 cvssBelow.forEach((s) -> sb.append(s).append(','));
794 sb.append('}');
795 }
796 if (cvssV2Below != null && !cvssV2Below.isEmpty()) {
797 sb.append("cvssV2Below={");
798 cvssV2Below.forEach((s) -> sb.append(s).append(','));
799 sb.append('}');
800 }
801 if (cvssV3Below != null && !cvssV3Below.isEmpty()) {
802 sb.append("cvssV3Below={");
803 cvssV3Below.forEach((s) -> sb.append(s).append(','));
804 sb.append('}');
805 }
806 if (cvssV4Below != null && !cvssV4Below.isEmpty()) {
807 sb.append("cvssV4Below={");
808 cvssV4Below.forEach((s) -> sb.append(s).append(','));
809 sb.append('}');
810 }
811 sb.append('}');
812 return sb.toString();
813 }
814
815
816
817
818
819
820
821
822 @Override
823 public boolean equals(Object o) {
824 if (o == null || getClass() != o.getClass()) return false;
825 if (this == o) return true;
826 SuppressionRule that = (SuppressionRule) o;
827 return base == that.base
828 && Objects.equals(filePath, that.filePath)
829 && Objects.equals(sha1, that.sha1)
830 && Objects.equals(cpe, that.cpe)
831 && Objects.equals(cvssBelow, that.cvssBelow)
832 && Objects.equals(cvssV2Below, that.cvssV2Below)
833 && Objects.equals(cvssV3Below, that.cvssV3Below)
834 && Objects.equals(cvssV4Below, that.cvssV4Below)
835 && Objects.equals(cwe, that.cwe)
836 && Objects.equals(cve, that.cve)
837 && Objects.equals(vulnerabilityNames, that.vulnerabilityNames)
838 && Objects.equals(gav, that.gav)
839 && Objects.equals(packageUrl, that.packageUrl)
840 && Objects.equals(until, that.until);
841 }
842
843 @Override
844 public int hashCode() {
845 return Objects.hash(base, filePath, sha1, cpe, cvssBelow, cvssV2Below, cvssV3Below, cvssV4Below, cwe, cve, vulnerabilityNames, gav, packageUrl, until);
846 }
847 }