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