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 .filter(config -> config.getNodes() != null)
88 .map(Config::getNodes)
89 .flatMap(List::stream)
90 .filter(cpe -> cpe.getCpeMatch() != null)
91 .map(Node::getCpeMatch)
92 .flatMap(List::stream)
93 .filter(match -> match.getCriteria() != null)
94 .collect(Collectors.toList());
95 if (!cpeEntries.isEmpty() && cpeEntries.size() > 1) {
96 final CpeMatch firstMatch = cpeEntries.get(0);
97 final String uri = firstMatch.getCriteria();
98 final int pos = uri.indexOf(":", uri.indexOf(":", 10) + 1);
99 final String match = uri.substring(0, pos + 1);
100 return !cpeEntries.stream().allMatch(e -> e.getCriteria().startsWith(match));
101 }
102 }
103 return false;
104 }
105 }