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