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