View Javadoc
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 &lt;CVSSv3 critical> >> &lt;Unscored recognized critical> >>
496      *     &lt;Unscored unrecognized (assumed Critical)> >> &lt;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 }