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) 2023 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.nvd.ecosystem;
19  
20  import io.github.jeremylong.openvulnerability.client.nvd.Config;
21  import io.github.jeremylong.openvulnerability.client.nvd.CpeMatch;
22  import io.github.jeremylong.openvulnerability.client.nvd.Node;
23  import io.github.jeremylong.openvulnerability.client.nvd.DefCveItem;
24  import java.util.List;
25  import java.util.stream.Collectors;
26  import javax.annotation.concurrent.NotThreadSafe;
27  
28  /**
29   * Utility for mapping CVEs to their ecosystems.
30   * <br><br>
31   * Follows a best effort approach:
32   * <ul>
33   * <li>scans through the description for known keywords or file extensions;
34   * alternatively </li>
35   * <li>attempts looks at the reference-data URLs for known hosts or path / query
36   * strings.</li>
37   * </ul>
38   * This class is not thread safe and must be instantiated on a per-thread basis.
39   *
40   * @author skjolber
41   */
42  @NotThreadSafe
43  public class CveEcosystemMapper {
44  
45      /**
46       * A reference to the Description Ecosystem Mapper.
47       */
48      private final DescriptionEcosystemMapper descriptionEcosystemMapper = new DescriptionEcosystemMapper();
49      /**
50       * A reference to the URL Ecosystem Mapper.
51       */
52      private final UrlEcosystemMapper urlEcosystemMapper = new UrlEcosystemMapper();
53  
54      /**
55       * Analyzes the description and associated URLs to determine if the
56       * vulnerability/software is for a specific known ecosystem. The ecosystem
57       * can be used later for filtering CPE matches.
58       *
59       * @param cve the item to be analyzed
60       * @return the ecosystem if one could be identified; otherwise
61       * <code>null</code>
62       */
63      public String getEcosystem(DefCveItem cve) {
64          //if there are multiple vendor/product pairs we don't know if they are
65          //all the same ecosystem.
66          if (hasMultipleVendorProductConfigurations(cve)) {
67              return null;
68          }
69          final String ecosystem = descriptionEcosystemMapper.getEcosystem(cve);
70          if (ecosystem != null) {
71              return ecosystem;
72          }
73          return urlEcosystemMapper.getEcosystem(cve);
74      }
75  
76      /**
77       * Analyzes the vulnerable configurations to see if the CVE applies to only
78       * a single vendor/product pair.
79       *
80       * @param cve the item to be analyzed
81       * @return the ecosystem if one could be identified; otherwise
82       * <code>null</code>
83       */
84      private boolean hasMultipleVendorProductConfigurations(DefCveItem cve) {
85          if (cve.getCve().getConfigurations() != null && !cve.getCve().getConfigurations().isEmpty()) {
86              final List<CpeMatch> cpeEntries = cve.getCve().getConfigurations().stream()
87                      .map(Config::getNodes)
88                      .flatMap(List::stream)
89                      .filter(cpe -> cpe.getCpeMatch() != null)
90                      .map(Node::getCpeMatch)
91                      .flatMap(List::stream)
92                      .filter(match -> match.getCriteria() != null)
93                      .collect(Collectors.toList());
94              if (!cpeEntries.isEmpty() && cpeEntries.size() > 1) {
95                  final CpeMatch firstMatch = cpeEntries.get(0);
96                  final String uri = firstMatch.getCriteria();
97                  final int pos = uri.indexOf(":", uri.indexOf(":", 10) + 1);
98                  final String match = uri.substring(0, pos + 1);
99                  return !cpeEntries.stream().allMatch(e -> e.getCriteria().startsWith(match));
100             }
101         }
102         return false;
103     }
104 }