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 /**
105 * Immutable views for getters.
106 */
107 private final Set<VulnerableSoftware> vulnerableSoftwareView = Collections.unmodifiableSet(vulnerableSoftware);
108
109 /**
110 * The CWE(s) for the vulnerability.
111 */
112 private final CweSet cwes = new CweSet();
113 /**
114 * The severity a {@link Source} has assigned for which a CVSS score is not
115 * available. Severity could be anything ranging from 'critical', 'high',
116 * 'medium', and 'low', to non-traditional labels like 'major', 'minor', and
117 * 'important'.
118 */
119 private String unscoredSeverity;
120 /**
121 * The CVSS V2 scoring information.
122 */
123 private CvssV2 cvssV2;
124
125 /**
126 * The CVSS V3 scoring information.
127 */
128 private CvssV3 cvssV3;
129
130 /**
131 * The CVSS V4 scoring information.
132 */
133 private CvssV4 cvssV4;
134
135 /**
136 * The Vulnerable Software that caused this vulnerability to be flagged.
137 */
138 private VulnerableSoftware matchedVulnerableSoftware;
139 /**
140 * Notes about the vulnerability. Generally used for suppression
141 * information.
142 */
143 private String notes;
144
145 /**
146 * The source that identified the vulnerability.
147 */
148 private Source source = null;
149
150 /**
151 * Default constructor.
152 */
153 public Vulnerability() {
154 //empty
155 }
156
157 /**
158 * Constructs a new Vulnerability by its name.
159 *
160 * @param name the name of the vulnerability
161 */
162 public Vulnerability(String name) {
163 this.name = name;
164 }
165
166 /**
167 * Get the value of name.
168 *
169 * @return the value of name
170 */
171 public String getName() {
172 return name;
173 }
174
175 /**
176 * Set the value of name.
177 *
178 * @param name new value of name
179 */
180 public void setName(String name) {
181 this.name = name;
182 }
183
184 /**
185 * Get the value of description.
186 *
187 * @return the value of description
188 */
189 public String getDescription() {
190 return description;
191 }
192
193 /**
194 * Set the value of description.
195 *
196 * @param description new value of description
197 */
198 public void setDescription(String description) {
199 this.description = description;
200 }
201
202 /**
203 * Get the value of references.
204 *
205 * @return the value of references
206 */
207 public Set<Reference> getReferences() {
208 return referencesView;
209 }
210
211 /**
212 * Returns the list of references. This is primarily used within the
213 * generated reports.
214 *
215 * @param sorted whether the returned list should be sorted
216 * @return the list of references
217 */
218 public List<Reference> getReferences(boolean sorted) {
219 final List<Reference> sortedRefs = new ArrayList<>(this.references);
220 if (sorted) {
221 Collections.sort(sortedRefs);
222 }
223 return sortedRefs;
224 }
225
226 /**
227 * Adds the references to the collection.
228 *
229 * @param references a collection of references to add
230 */
231 public void addReferences(Set<Reference> references) {
232 this.references.addAll(references);
233 }
234
235 /**
236 * Adds a reference to the references collection.
237 *
238 * @param ref a reference for the vulnerability
239 */
240 public void addReference(Reference ref) {
241 this.references.add(ref);
242 }
243
244 /**
245 * Adds a reference.
246 *
247 * @param referenceSource the source of the reference
248 * @param referenceName the referenceName of the reference
249 * @param referenceUrl the url of the reference
250 */
251 public void addReference(String referenceSource, String referenceName, String referenceUrl) {
252 final Reference ref = new Reference();
253 ref.setSource(referenceSource);
254 ref.setName(referenceName);
255 ref.setUrl(referenceUrl);
256 this.references.add(ref);
257 }
258
259 /**
260 * Adds information about known exploited vulnerabilities.
261 *
262 * @param kev the known exploited vulnerability information
263 */
264 public void setKnownExploitedVulnerability(org.owasp.dependencycheck.data.knownexploited.json.Vulnerability kev) {
265 this.knownExploitedVulnerability = kev;
266 }
267
268 /**
269 * Get the value of knownExploitedVulnerability.
270 *
271 * @return the value of knownExploitedVulnerability
272 */
273 public org.owasp.dependencycheck.data.knownexploited.json.Vulnerability getKnownExploitedVulnerability() {
274 return knownExploitedVulnerability;
275 }
276
277 /**
278 * Get the value of vulnerableSoftware.
279 *
280 * @return the value of vulnerableSoftware
281 */
282 public Set<VulnerableSoftware> getVulnerableSoftware() {
283 return vulnerableSoftwareView;
284 }
285
286 /**
287 * Returns a sorted list of vulnerable software. This is primarily used for
288 * display within reports.
289 *
290 * @param sorted whether or not the list should be sorted
291 * @return the list of vulnerable software
292 */
293 @SuppressWarnings("unchecked")
294 public List<VulnerableSoftware> getVulnerableSoftware(boolean sorted) {
295 final List<VulnerableSoftware> sortedVulnerableSoftware = new ArrayList<>(this.vulnerableSoftware);
296 if (sorted) {
297 Collections.sort(sortedVulnerableSoftware);
298 }
299 return sortedVulnerableSoftware;
300 }
301
302 /**
303 * Removes the specified vulnerableSoftware from the collection.
304 *
305 * @param vulnerableSoftware a collection of vulnerable software to be removed
306 */
307 public void removeVulnerableSoftware(Set<VulnerableSoftware> vulnerableSoftware) {
308 this.vulnerableSoftware.removeAll(vulnerableSoftware);
309 }
310
311 /**
312 * Adds the vulnerableSoftware to the collection.
313 *
314 * @param vulnerableSoftware a collection of vulnerable software
315 */
316 public void addVulnerableSoftware(Set<VulnerableSoftware> vulnerableSoftware) {
317 this.vulnerableSoftware.addAll(vulnerableSoftware);
318 }
319
320 /**
321 * Adds an entry for vulnerable software.
322 *
323 * @param software the vulnerable software reference to add
324 */
325 public void addVulnerableSoftware(VulnerableSoftware software) {
326 vulnerableSoftware.add(software);
327 }
328
329 /**
330 * Get the CVSS V2 scoring information.
331 *
332 * @return the CVSS V2 scoring information
333 */
334 public CvssV2 getCvssV2() {
335 return cvssV2;
336 }
337
338 /**
339 * Sets the CVSS V2 scoring information.
340 *
341 * @param cvssV2 the CVSS V2 scoring information
342 */
343 public void setCvssV2(CvssV2 cvssV2) {
344 this.cvssV2 = cvssV2;
345 }
346
347 /**
348 * Get the CVSS V3 scoring information.
349 *
350 * @return the CVSS V3 scoring information
351 */
352 public CvssV3 getCvssV3() {
353 return cvssV3;
354 }
355
356 /**
357 * Sets the CVSS V3 scoring information.
358 *
359 * @param cvssV3 the CVSS V3 scoring information
360 */
361 public void setCvssV3(CvssV3 cvssV3) {
362 this.cvssV3 = cvssV3;
363 }
364
365 /**
366 * Get the CVSS V3 scoring information.
367 *
368 * @return the CVSS V3 scoring information
369 */
370 public CvssV4 getCvssV4() {
371 return cvssV4;
372 }
373
374 /**
375 * Sets the CVSS V4 scoring information.
376 *
377 * @param cvssV4 the CVSS V4 scoring information
378 */
379 public void setCvssV4(CvssV4 cvssV4) {
380 this.cvssV4 = cvssV4;
381 }
382
383 /**
384 * Get the set of CWEs.
385 *
386 * @return the set of CWEs
387 */
388 public CweSet getCwes() {
389 return cwes;
390 }
391
392 /**
393 * Adds a CWE to the set.
394 *
395 * @param cwe new CWE to add
396 */
397 public void addCwe(String cwe) {
398 this.cwes.addCwe(cwe);
399 }
400
401 /**
402 * Retrieves the severity a {@link Source} has assigned for which a CVSS
403 * score is not available. Severity could be anything ranging from
404 * 'critical', 'high', 'medium', and 'low', to non-traditional labels like
405 * 'major', 'minor', and 'important'.
406 *
407 * @return the un-scored severity
408 */
409 public String getUnscoredSeverity() {
410 return unscoredSeverity;
411 }
412
413 /**
414 * Sets the severity a {@link Source} has assigned for which a CVSS score is
415 * not available. Severity could be anything ranging from 'critical',
416 * 'high', 'medium', and 'low', to non-traditional labels like 'major',
417 * 'minor', and 'important'.
418 *
419 * @param unscoredSeverity the un-scored severity
420 */
421 public void setUnscoredSeverity(String unscoredSeverity) {
422 this.unscoredSeverity = unscoredSeverity;
423 }
424
425 /**
426 * Get the value of notes from suppression notes.
427 *
428 * @return the value of notes
429 */
430 public String getNotes() {
431 return notes;
432 }
433
434 /**
435 * Set the value of notes.
436 *
437 * @param notes new value of notes
438 */
439 public void setNotes(String notes) {
440 this.notes = notes;
441 }
442
443 @Override
444 public boolean equals(Object obj) {
445 if (obj == null || !(obj instanceof Vulnerability)) {
446 return false;
447 }
448 if (this == obj) {
449 return true;
450 }
451 final Vulnerability other = (Vulnerability) obj;
452 return new EqualsBuilder()
453 .append(name, other.name)
454 .isEquals();
455 }
456
457 @Override
458 public int hashCode() {
459 return new HashCodeBuilder(3, 73)
460 .append(name)
461 .toHashCode();
462 }
463
464 @Override
465 public String toString() {
466 final StringBuilder sb = new StringBuilder("Vulnerability ");
467 sb.append(this.name);
468 sb.append("\nReferences:\n");
469 for (Reference reference : getReferences(true)) {
470 sb.append("=> ");
471 sb.append(reference);
472 sb.append("\n");
473 }
474 sb.append("\nSoftware:\n");
475
476 for (VulnerableSoftware software : getVulnerableSoftware(true)) {
477 sb.append("=> ");
478 sb.append(software);
479 sb.append("\n");
480 }
481 return sb.toString();
482 }
483
484 /**
485 * Compares two vulnerabilities.<br>
486 * Natural order of vulnerabilities is defined as decreasing in severity and
487 * alphabetically by name for equal severity. This way the most severe
488 * issues are listed first in a sorted list.
489 * <br>
490 * This uses a
491 * {@link #bestEffortSeverityLevelForSorting() best-effort ordering} for
492 * severity as the variety of sources do not guarantee a consistent
493 * availability of standardized severity scores. The bestEffort severity
494 * level estimation will use CVSSv3 baseScore for comparison when available
495 * on both sides. If any of the vulnerabilities does not have a CVSSv3 score
496 * the sort order may be off, but it will be consistent.
497 * <br>
498 * The ranking (high to low) of severity can be informally represented as {@code <CVSSv3 critical> >> <Unscored recognized critical> >>
499 * <Unscored unrecognized (assumed Critical)> >> <Score-based comparison for high-or-lower scoring severities with
500 * recognized unscored severities taking the lower bound of the comparable CVSSv3 range>
501 * }
502 *
503 * @param o a vulnerability to be compared
504 * @return a negative integer, zero, or a positive integer as this object is
505 * less than , equal to, or greater than the specified vulnerability
506 * @see #bestEffortSeverityLevelForSorting()
507 */
508 @Override
509 public int compareTo(@NotNull Vulnerability o) {
510 return new CompareToBuilder()
511 .append(o.bestEffortSeverityLevelForSorting(), this.bestEffortSeverityLevelForSorting())
512 .append(this.name, o.name)
513 .toComparison();
514 }
515
516 /**
517 * Compute a best-effort score for the severity of a vulnerability for the
518 * purpose of sorting.
519 * <br>
520 * Note that CVSSv2 and CVSSv3 scores are essentially uncomparable. For the
521 * purpose of sorting we nevertheless treat them comparable, with an
522 * exception for the 9.0-10.0 range. For that entire range CVSSv3 is scoring
523 * more severe than CVSSv2, so that the 'CRITICAL' severity is retained to
524 * be reported as the highest severity after sorting on descending severity.
525 * <br>
526 * For vulnerabilities not scored with a CVSS score we estimate a score from
527 * the severity text. For textual severities assumed or semantically
528 * confirmed to be of a critical nature we assign a value in between the
529 * highest CVSSv2 HIGH and the lowest CVSSv3 CRITICAL severity level.
530 *
531 * @see SeverityUtil#estimatedSortAdjustedCVSSv3(String)
532 * @see SeverityUtil#sortAdjustedCVSSv3BaseScore(float)
533 * @return A float value that allows for best-effort sorting on
534 * vulnerability severity
535 */
536 private Double bestEffortSeverityLevelForSorting() {
537 if (this.cvssV4 != null) {
538 return SeverityUtil.sortAdjustedCVSSv3BaseScore(this.cvssV4.getCvssData().getBaseScore());
539 }
540 if (this.cvssV3 != null) {
541 return SeverityUtil.sortAdjustedCVSSv3BaseScore(this.cvssV3.getCvssData().getBaseScore());
542 }
543 if (this.cvssV2 != null) {
544 return this.cvssV2.getCvssData().getBaseScore();
545 }
546 return SeverityUtil.estimatedSortAdjustedCVSSv3(this.unscoredSeverity);
547 }
548
549 /**
550 * The report text to use for highest severity when this issue is ranked
551 * highest.
552 *
553 * @return The string to display in the report, clarifying for unrecognized
554 * unscored severities that critical is assumed.
555 */
556 public String getHighestSeverityText() {
557 if (this.cvssV4 != null) {
558 return this.cvssV4.getCvssData().getBaseSeverity().value().toUpperCase();
559 }
560 if (this.cvssV3 != null) {
561 return this.cvssV3.getCvssData().getBaseSeverity().value().toUpperCase();
562 }
563 if (this.cvssV2 != null) {
564 return this.cvssV2.getCvssData().getBaseSeverity().toUpperCase();
565 }
566 return SeverityUtil.unscoredToSeveritytext(this.unscoredSeverity).toUpperCase();
567 }
568
569 /**
570 * Sets the CPE that caused this vulnerability to be flagged.
571 *
572 * @param software a Vulnerable Software identifier
573 */
574 public void setMatchedVulnerableSoftware(VulnerableSoftware software) {
575 matchedVulnerableSoftware = software;
576 }
577
578 /**
579 * Get the value of matchedVulnerableSoftware.
580 *
581 * @return the value of matchedVulnerableSoftware
582 */
583 public VulnerableSoftware getMatchedVulnerableSoftware() {
584 return matchedVulnerableSoftware;
585 }
586
587 /**
588 * Returns the source that identified the vulnerability.
589 *
590 * @return the source
591 */
592 public Source getSource() {
593 return source;
594 }
595
596 /**
597 * Sets the source that identified the vulnerability.
598 *
599 * @param source the source
600 */
601 public void setSource(Source source) {
602 this.source = source;
603 }
604 }