1 /*
2 * This file is part of dependency-check-core.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
17 */
18 package org.owasp.dependencycheck.dependency;
19
20 import io.github.jeremylong.openvulnerability.client.nvd.CvssV2;
21 import io.github.jeremylong.openvulnerability.client.nvd.CvssV3;
22 import io.github.jeremylong.openvulnerability.client.nvd.CvssV4;
23 import java.io.Serializable;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.ConcurrentHashMap;
29 import javax.annotation.concurrent.NotThreadSafe;
30
31 import org.apache.commons.lang3.builder.CompareToBuilder;
32 import org.apache.commons.lang3.builder.EqualsBuilder;
33 import org.apache.commons.lang3.builder.HashCodeBuilder;
34 import org.jetbrains.annotations.NotNull;
35 import org.owasp.dependencycheck.utils.SeverityUtil;
36
37 /**
38 * Contains the information about a vulnerability.
39 *
40 * @author Jeremy Long
41 */
42 @NotThreadSafe
43 public class Vulnerability implements Serializable, Comparable<Vulnerability> {
44
45 /**
46 * An enumeration for the source of vulnerability.
47 */
48 public enum Source {
49 /**
50 * National Vulnerability Database.
51 */
52 NVD,
53 /**
54 * NPM Public Advisory.
55 */
56 NPM,
57 /**
58 * RetireJS.
59 */
60 RETIREJS,
61 /**
62 * Sonatype OSS Index.
63 */
64 OSSINDEX,
65 /**
66 * Vulnerability from Bundle Audit.
67 */
68 BUNDLEAUDIT,
69 /**
70 * Vulnerability from Mix Audit.
71 */
72 MIXAUDIT
73 }
74
75 /**
76 * The serial version uid.
77 */
78 private static final long serialVersionUID = 307319490326651053L;
79
80 /**
81 * The name of the vulnerability.
82 */
83 private String name;
84 /**
85 * the description of the vulnerability.
86 */
87 private String description;
88 /**
89 * Data if the vulnerability is a known exploited vulnerability.
90 */
91 private org.owasp.dependencycheck.data.knownexploited.json.Vulnerability knownExploitedVulnerability;
92 /**
93 * References for this vulnerability.
94 */
95 private final Set<Reference> references = ConcurrentHashMap.newKeySet();
96 /**
97 * A set of vulnerable software.
98 */
99 private final Set<VulnerableSoftware> vulnerableSoftware = ConcurrentHashMap.newKeySet();
100 /**
101 * Immutable views for getters.
102 */
103 private final Set<Reference> referencesView = Collections.unmodifiableSet(references);
104 private final Set<VulnerableSoftware> vulnerableSoftwareView = Collections.unmodifiableSet(vulnerableSoftware);
105
106 /**
107 * The CWE(s) for the vulnerability.
108 */
109 private final CweSet cwes = new CweSet();
110 /**
111 * The severity a {@link Source} has assigned for which a CVSS score is not
112 * available. Severity could be anything ranging from 'critical', 'high',
113 * 'medium', and 'low', to non-traditional labels like 'major', 'minor', and
114 * 'important'.
115 */
116 private String unscoredSeverity;
117 /**
118 * The CVSS V2 scoring information.
119 */
120 private CvssV2 cvssV2;
121
122 /**
123 * The CVSS V3 scoring information.
124 */
125 private CvssV3 cvssV3;
126
127 /**
128 * The CVSS V4 scoring information.
129 */
130 private CvssV4 cvssV4;
131
132 /**
133 * The Vulnerable Software that caused this vulnerability to be flagged.
134 */
135 private VulnerableSoftware matchedVulnerableSoftware;
136 /**
137 * Notes about the vulnerability. Generally used for suppression
138 * information.
139 */
140 private String notes;
141
142 /**
143 * The source that identified the vulnerability.
144 */
145 private Source source = null;
146
147 /**
148 * Default constructor.
149 */
150 public Vulnerability() {
151 //empty
152 }
153
154 /**
155 * Constructs a new Vulnerability by its name.
156 *
157 * @param name the name of the vulnerability
158 */
159 public Vulnerability(String name) {
160 this.name = name;
161 }
162
163 /**
164 * Get the value of name.
165 *
166 * @return the value of name
167 */
168 public String getName() {
169 return name;
170 }
171
172 /**
173 * Set the value of name.
174 *
175 * @param name new value of name
176 */
177 public void setName(String name) {
178 this.name = name;
179 }
180
181 /**
182 * Get the value of description.
183 *
184 * @return the value of description
185 */
186 public String getDescription() {
187 return description;
188 }
189
190 /**
191 * Set the value of description.
192 *
193 * @param description new value of description
194 */
195 public void setDescription(String description) {
196 this.description = description;
197 }
198
199 /**
200 * Get the value of references.
201 *
202 * @return the value of references
203 */
204 public Set<Reference> getReferences() {
205 return referencesView;
206 }
207
208 /**
209 * Returns the list of references. This is primarily used within the
210 * generated reports.
211 *
212 * @param sorted whether the returned list should be sorted
213 * @return the list of references
214 */
215 public List<Reference> getReferences(boolean sorted) {
216 final List<Reference> sortedRefs = new ArrayList<>(this.references);
217 if (sorted) {
218 Collections.sort(sortedRefs);
219 }
220 return sortedRefs;
221 }
222
223 /**
224 * Adds the references to the collection.
225 *
226 * @param references a collection of references to add
227 */
228 public void addReferences(Set<Reference> references) {
229 this.references.addAll(references);
230 }
231
232 /**
233 * Adds a reference to the references collection.
234 *
235 * @param ref a reference for the vulnerability
236 */
237 public void addReference(Reference ref) {
238 this.references.add(ref);
239 }
240
241 /**
242 * Adds a reference.
243 *
244 * @param referenceSource the source of the reference
245 * @param referenceName the referenceName of the reference
246 * @param referenceUrl the url of the reference
247 */
248 public void addReference(String referenceSource, String referenceName, String referenceUrl) {
249 final Reference ref = new Reference();
250 ref.setSource(referenceSource);
251 ref.setName(referenceName);
252 ref.setUrl(referenceUrl);
253 this.references.add(ref);
254 }
255
256 /**
257 * Adds information about known exploited vulnerabilities.
258 *
259 * @param kev the known exploited vulnerability information
260 */
261 public void setKnownExploitedVulnerability(org.owasp.dependencycheck.data.knownexploited.json.Vulnerability kev) {
262 this.knownExploitedVulnerability = kev;
263 }
264
265 /**
266 * Get the value of knownExploitedVulnerability.
267 *
268 * @return the value of knownExploitedVulnerability
269 */
270 public org.owasp.dependencycheck.data.knownexploited.json.Vulnerability getKnownExploitedVulnerability() {
271 return knownExploitedVulnerability;
272 }
273
274 /**
275 * Get the value of vulnerableSoftware.
276 *
277 * @return the value of vulnerableSoftware
278 */
279 public Set<VulnerableSoftware> getVulnerableSoftware() {
280 return vulnerableSoftwareView;
281 }
282
283 /**
284 * Returns a sorted list of vulnerable software. This is primarily used for
285 * display within reports.
286 *
287 * @param sorted whether or not the list should be sorted
288 * @return the list of vulnerable software
289 */
290 @SuppressWarnings("unchecked")
291 public List<VulnerableSoftware> getVulnerableSoftware(boolean sorted) {
292 final List<VulnerableSoftware> sortedVulnerableSoftware = new ArrayList<>(this.vulnerableSoftware);
293 if (sorted) {
294 Collections.sort(sortedVulnerableSoftware);
295 }
296 return sortedVulnerableSoftware;
297 }
298
299 /**
300 * Removes the specified vulnerableSoftware from the collection.
301 *
302 * @param vulnerableSoftware a collection of vulnerable software to be removed
303 */
304 public void removeVulnerableSoftware(Set<VulnerableSoftware> vulnerableSoftware) {
305 this.vulnerableSoftware.removeAll(vulnerableSoftware);
306 }
307
308 /**
309 * Adds the vulnerableSoftware to the collection.
310 *
311 * @param vulnerableSoftware a collection of vulnerable software
312 */
313 public void addVulnerableSoftware(Set<VulnerableSoftware> vulnerableSoftware) {
314 this.vulnerableSoftware.addAll(vulnerableSoftware);
315 }
316
317 /**
318 * Adds an entry for vulnerable software.
319 *
320 * @param software the vulnerable software reference to add
321 */
322 public void addVulnerableSoftware(VulnerableSoftware software) {
323 vulnerableSoftware.add(software);
324 }
325
326 /**
327 * Get the CVSS V2 scoring information.
328 *
329 * @return the CVSS V2 scoring information
330 */
331 public CvssV2 getCvssV2() {
332 return cvssV2;
333 }
334
335 /**
336 * Sets the CVSS V2 scoring information.
337 *
338 * @param cvssV2 the CVSS V2 scoring information
339 */
340 public void setCvssV2(CvssV2 cvssV2) {
341 this.cvssV2 = cvssV2;
342 }
343
344 /**
345 * Get the CVSS V3 scoring information.
346 *
347 * @return the CVSS V3 scoring information
348 */
349 public CvssV3 getCvssV3() {
350 return cvssV3;
351 }
352
353 /**
354 * Sets the CVSS V3 scoring information.
355 *
356 * @param cvssV3 the CVSS V3 scoring information
357 */
358 public void setCvssV3(CvssV3 cvssV3) {
359 this.cvssV3 = cvssV3;
360 }
361
362 /**
363 * Get the CVSS V3 scoring information.
364 *
365 * @return the CVSS V3 scoring information
366 */
367 public CvssV4 getCvssV4() {
368 return cvssV4;
369 }
370
371 /**
372 * Sets the CVSS V4 scoring information.
373 *
374 * @param cvssV4 the CVSS V4 scoring information
375 */
376 public void setCvssV4(CvssV4 cvssV4) {
377 this.cvssV4 = cvssV4;
378 }
379
380 /**
381 * Get the set of CWEs.
382 *
383 * @return the set of CWEs
384 */
385 public CweSet getCwes() {
386 return cwes;
387 }
388
389 /**
390 * Adds a CWE to the set.
391 *
392 * @param cwe new CWE to add
393 */
394 public void addCwe(String cwe) {
395 this.cwes.addCwe(cwe);
396 }
397
398 /**
399 * Retrieves the severity a {@link Source} has assigned for which a CVSS
400 * score is not available. Severity could be anything ranging from
401 * 'critical', 'high', 'medium', and 'low', to non-traditional labels like
402 * 'major', 'minor', and 'important'.
403 *
404 * @return the un-scored severity
405 */
406 public String getUnscoredSeverity() {
407 return unscoredSeverity;
408 }
409
410 /**
411 * Sets the severity a {@link Source} has assigned for which a CVSS score is
412 * not available. Severity could be anything ranging from 'critical',
413 * 'high', 'medium', and 'low', to non-traditional labels like 'major',
414 * 'minor', and 'important'.
415 *
416 * @param unscoredSeverity the un-scored severity
417 */
418 public void setUnscoredSeverity(String unscoredSeverity) {
419 this.unscoredSeverity = unscoredSeverity;
420 }
421
422 /**
423 * Get the value of notes from suppression notes.
424 *
425 * @return the value of notes
426 */
427 public String getNotes() {
428 return notes;
429 }
430
431 /**
432 * Set the value of notes.
433 *
434 * @param notes new value of notes
435 */
436 public void setNotes(String notes) {
437 this.notes = notes;
438 }
439
440 @Override
441 public boolean equals(Object obj) {
442 if (obj == null || !(obj instanceof Vulnerability)) {
443 return false;
444 }
445 if (this == obj) {
446 return true;
447 }
448 final Vulnerability other = (Vulnerability) obj;
449 return new EqualsBuilder()
450 .append(name, other.name)
451 .isEquals();
452 }
453
454 @Override
455 public int hashCode() {
456 return new HashCodeBuilder(3, 73)
457 .append(name)
458 .toHashCode();
459 }
460
461 @Override
462 public String toString() {
463 final StringBuilder sb = new StringBuilder("Vulnerability ");
464 sb.append(this.name);
465 sb.append("\nReferences:\n");
466 for (Reference reference : getReferences(true)) {
467 sb.append("=> ");
468 sb.append(reference);
469 sb.append("\n");
470 }
471 sb.append("\nSoftware:\n");
472
473 for (VulnerableSoftware software : getVulnerableSoftware(true)) {
474 sb.append("=> ");
475 sb.append(software);
476 sb.append("\n");
477 }
478 return sb.toString();
479 }
480
481 /**
482 * Compares two vulnerabilities.<br>
483 * Natural order of vulnerabilities is defined as decreasing in severity and
484 * alphabetically by name for equal severity. This way the most severe
485 * issues are listed first in a sorted list.
486 * <br>
487 * This uses a
488 * {@link #bestEffortSeverityLevelForSorting() best-effort ordering} for
489 * severity as the variety of sources do not guarantee a consistent
490 * availability of standardized severity scores. The bestEffort severity
491 * level estimation will use CVSSv3 baseScore for comparison when available
492 * on both sides. If any of the vulnerabilities does not have a CVSSv3 score
493 * the sort order may be off, but it will be consistent.
494 * <br>
495 * The ranking (high to low) of severity can be informally represented as {@code <CVSSv3 critical> >> <Unscored recognized critical> >>
496 * <Unscored unrecognized (assumed Critical)> >> <Score-based comparison for high-or-lower scoring severities with
497 * recognized unscored severities taking the lower bound of the comparable CVSSv3 range>
498 * }
499 *
500 * @param o a vulnerability to be compared
501 * @return a negative integer, zero, or a positive integer as this object is
502 * less than , equal to, or greater than the specified vulnerability
503 * @see #bestEffortSeverityLevelForSorting()
504 */
505 @Override
506 public int compareTo(@NotNull Vulnerability o) {
507 return new CompareToBuilder()
508 .append(o.bestEffortSeverityLevelForSorting(), this.bestEffortSeverityLevelForSorting())
509 .append(this.name, o.name)
510 .toComparison();
511 }
512
513 /**
514 * Compute a best-effort score for the severity of a vulnerability for the
515 * purpose of sorting.
516 * <br>
517 * Note that CVSSv2 and CVSSv3 scores are essentially uncomparable. For the
518 * purpose of sorting we nevertheless treat them comparable, with an
519 * exception for the 9.0-10.0 range. For that entire range CVSSv3 is scoring
520 * more severe than CVSSv2, so that the 'CRITICAL' severity is retained to
521 * be reported as the highest severity after sorting on descending severity.
522 * <br>
523 * For vulnerabilities not scored with a CVSS score we estimate a score from
524 * the severity text. For textual severities assumed or semantically
525 * confirmed to be of a critical nature we assign a value in between the
526 * highest CVSSv2 HIGH and the lowest CVSSv3 CRITICAL severity level.
527 *
528 * @see SeverityUtil#estimatedSortAdjustedCVSSv3(String)
529 * @see SeverityUtil#sortAdjustedCVSSv3BaseScore(float)
530 * @return A float value that allows for best-effort sorting on
531 * vulnerability severity
532 */
533 private Double bestEffortSeverityLevelForSorting() {
534 if (this.cvssV4 != null) {
535 return SeverityUtil.sortAdjustedCVSSv3BaseScore(this.cvssV4.getCvssData().getBaseScore());
536 }
537 if (this.cvssV3 != null) {
538 return SeverityUtil.sortAdjustedCVSSv3BaseScore(this.cvssV3.getCvssData().getBaseScore());
539 }
540 if (this.cvssV2 != null) {
541 return this.cvssV2.getCvssData().getBaseScore();
542 }
543 return SeverityUtil.estimatedSortAdjustedCVSSv3(this.unscoredSeverity);
544 }
545
546 /**
547 * The report text to use for highest severity when this issue is ranked
548 * highest.
549 *
550 * @return The string to display in the report, clarifying for unrecognized
551 * unscored severities that critical is assumed.
552 */
553 public String getHighestSeverityText() {
554 if (this.cvssV4 != null) {
555 return this.cvssV4.getCvssData().getBaseSeverity().value().toUpperCase();
556 }
557 if (this.cvssV3 != null) {
558 return this.cvssV3.getCvssData().getBaseSeverity().value().toUpperCase();
559 }
560 if (this.cvssV2 != null) {
561 return this.cvssV2.getCvssData().getBaseSeverity().toUpperCase();
562 }
563 return SeverityUtil.unscoredToSeveritytext(this.unscoredSeverity).toUpperCase();
564 }
565
566 /**
567 * Sets the CPE that caused this vulnerability to be flagged.
568 *
569 * @param software a Vulnerable Software identifier
570 */
571 public void setMatchedVulnerableSoftware(VulnerableSoftware software) {
572 matchedVulnerableSoftware = software;
573 }
574
575 /**
576 * Get the value of matchedVulnerableSoftware.
577 *
578 * @return the value of matchedVulnerableSoftware
579 */
580 public VulnerableSoftware getMatchedVulnerableSoftware() {
581 return matchedVulnerableSoftware;
582 }
583
584 /**
585 * Returns the source that identified the vulnerability.
586 *
587 * @return the source
588 */
589 public Source getSource() {
590 return source;
591 }
592
593 /**
594 * Sets the source that identified the vulnerability.
595 *
596 * @param source the source
597 */
598 public void setSource(Source source) {
599 this.source = source;
600 }
601 }