1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import io.github.jeremylong.openvulnerability.client.nvd.CvssV2;
21 import io.github.jeremylong.openvulnerability.client.nvd.CvssV2Data;
22 import org.sonatype.ossindex.service.api.componentreport.ComponentReport;
23 import org.sonatype.ossindex.service.api.componentreport.ComponentReportVulnerability;
24 import org.sonatype.ossindex.service.api.cvss.Cvss2Severity;
25 import org.sonatype.ossindex.service.api.cvss.Cvss2Vector;
26 import org.sonatype.ossindex.service.api.cvss.CvssVector;
27 import org.sonatype.ossindex.service.api.cvss.CvssVectorFactory;
28 import org.sonatype.ossindex.service.client.OssindexClient;
29 import org.owasp.dependencycheck.Engine;
30 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
31 import org.owasp.dependencycheck.data.ossindex.OssindexClientFactory;
32
33 import org.owasp.dependencycheck.dependency.Dependency;
34 import org.owasp.dependencycheck.dependency.Vulnerability;
35 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
36 import org.owasp.dependencycheck.dependency.VulnerableSoftwareBuilder;
37 import org.owasp.dependencycheck.dependency.naming.Identifier;
38 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
39 import org.owasp.dependencycheck.utils.Settings;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import us.springett.parsers.cpe.exceptions.CpeValidationException;
43 import us.springett.parsers.cpe.values.Part;
44
45 import org.sonatype.goodies.packageurl.PackageUrl;
46
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.concurrent.TimeUnit;
53 import java.util.regex.Matcher;
54 import java.util.regex.Pattern;
55
56 import java.net.SocketTimeoutException;
57
58 import javax.annotation.Nullable;
59 import org.apache.commons.lang3.StringUtils;
60 import org.owasp.dependencycheck.utils.CvssUtil;
61 import org.sonatype.goodies.packageurl.InvalidException;
62 import org.sonatype.ossindex.service.client.transport.Transport.TransportException;
63
64
65
66
67
68
69
70 public class OssIndexAnalyzer extends AbstractAnalyzer {
71
72
73
74
75 private static final Logger LOG = LoggerFactory.getLogger(OssIndexAnalyzer.class);
76
77
78
79
80 private static final Pattern CVE_PATTERN = Pattern.compile("\\bCVE-\\d{4}-\\d{4,10}\\b");
81
82
83
84
85 public static final String REFERENCE_TYPE = "OSSINDEX";
86
87
88
89
90 private static Map<PackageUrl, ComponentReport> reports;
91
92
93
94
95 private static final Object FETCH_MUTIX = new Object();
96
97 @Override
98 public String getName() {
99 return "Sonatype OSS Index Analyzer";
100 }
101
102 @Override
103 public AnalysisPhase getAnalysisPhase() {
104 return AnalysisPhase.FINDING_ANALYSIS_PHASE2;
105 }
106
107 @Override
108 protected String getAnalyzerEnabledSettingKey() {
109 return Settings.KEYS.ANALYZER_OSSINDEX_ENABLED;
110 }
111
112
113
114
115
116
117 @Override
118 public boolean supportsParallelProcessing() {
119 return true;
120 }
121
122 @Override
123 protected void closeAnalyzer() throws Exception {
124 synchronized (FETCH_MUTIX) {
125 reports = null;
126 }
127 }
128
129 @Override
130 protected void analyzeDependency(final Dependency dependency, final Engine engine) throws AnalysisException {
131
132 synchronized (FETCH_MUTIX) {
133 if (reports == null) {
134 try {
135 requestDelay();
136 reports = requestReports(engine.getDependencies());
137 } catch (TransportException ex) {
138 final String message = ex.getMessage();
139 final boolean warnOnly = getSettings().getBoolean(Settings.KEYS.ANALYZER_OSSINDEX_WARN_ONLY_ON_REMOTE_ERRORS, false);
140 this.setEnabled(false);
141 if (StringUtils.endsWith(message, "401")) {
142 LOG.error("Invalid credentials for the OSS Index, disabling the analyzer");
143 throw new AnalysisException("Invalid credentials provided for OSS Index", ex);
144 } else if (StringUtils.endsWith(message, "403")) {
145 LOG.error("OSS Index access forbidden, disabling the analyzer");
146 throw new AnalysisException("OSS Index access forbidden", ex);
147 } else if (StringUtils.endsWith(message, "429")) {
148 if (warnOnly) {
149 LOG.warn("OSS Index rate limit exceeded, disabling the analyzer", ex);
150 } else {
151 throw new AnalysisException("OSS Index rate limit exceeded, disabling the analyzer", ex);
152 }
153 } else if (warnOnly) {
154 LOG.warn("Error requesting component reports, disabling the analyzer", ex);
155 } else {
156 LOG.debug("Error requesting component reports, disabling the analyzer", ex);
157 throw new AnalysisException("Failed to request component-reports", ex);
158 }
159 } catch (SocketTimeoutException e) {
160 final boolean warnOnly = getSettings().getBoolean(Settings.KEYS.ANALYZER_OSSINDEX_WARN_ONLY_ON_REMOTE_ERRORS, false);
161 this.setEnabled(false);
162 if (warnOnly) {
163 LOG.warn("OSS Index socket timeout, disabling the analyzer", e);
164 } else {
165 LOG.debug("OSS Index socket timeout", e);
166 throw new AnalysisException("Failed to establish socket to OSS Index", e);
167 }
168 } catch (Exception e) {
169 LOG.debug("Error requesting component reports", e);
170 throw new AnalysisException("Failed to request component-reports", e);
171 }
172 }
173
174
175 if (reports != null) {
176 enrich(dependency);
177 }
178 }
179
180 }
181
182
183
184
185
186 private void requestDelay() throws InterruptedException {
187 final int delay = getSettings().getInt(Settings.KEYS.ANALYZER_OSSINDEX_REQUEST_DELAY, 0);
188 if (delay > 0) {
189 LOG.debug("Request delay: " + delay);
190 TimeUnit.SECONDS.sleep(delay);
191 }
192 }
193
194
195
196
197
198
199
200 @Nullable
201 private PackageUrl parsePackageUrl(final String value) {
202 try {
203 return PackageUrl.parse(value);
204 } catch (InvalidException e) {
205 LOG.debug("Invalid Package-URL: {}", value, e);
206 return null;
207 }
208 }
209
210
211
212
213
214
215
216
217 private Map<PackageUrl, ComponentReport> requestReports(final Dependency[] dependencies) throws Exception {
218 LOG.debug("Requesting component-reports for {} dependencies", dependencies.length);
219
220 final List<PackageUrl> packages = new ArrayList<>();
221 Arrays.stream(dependencies).forEach(dependency -> dependency.getSoftwareIdentifiers().stream()
222 .filter(id -> id instanceof PurlIdentifier)
223 .map(id -> parsePackageUrl(id.getValue()))
224 .filter(id -> id != null && StringUtils.isNotBlank(id.getVersion()))
225 .forEach(packages::add));
226
227 if (!packages.isEmpty()) {
228 try (OssindexClient client = newOssIndexClient()) {
229 LOG.debug("OSS Index Analyzer submitting: " + packages);
230 return client.requestComponentReports(packages);
231 }
232 }
233 LOG.warn("Unable to determine Package-URL identifiers for {} dependencies", dependencies.length);
234 return Collections.emptyMap();
235 }
236
237 OssindexClient newOssIndexClient() {
238 return OssindexClientFactory.create(getSettings());
239 }
240
241
242
243
244
245
246
247 void enrich(final Dependency dependency) {
248 LOG.debug("Enrich dependency: {}", dependency);
249
250 for (Identifier id : dependency.getSoftwareIdentifiers()) {
251 if (id instanceof PurlIdentifier) {
252 LOG.debug(" Package: {} -> {}", id, id.getConfidence());
253
254 final PackageUrl purl = parsePackageUrl(id.getValue());
255 if (purl != null && StringUtils.isNotBlank(purl.getVersion())) {
256 try {
257 final ComponentReport report = reports.get(purl);
258 if (report == null) {
259 LOG.debug("Missing component-report for: " + purl);
260 continue;
261 }
262
263
264 id.setUrl(report.getReference().toString());
265
266 report.getVulnerabilities().stream()
267 .map((vuln) -> transform(report, vuln))
268 .forEachOrdered((v) -> {
269 final Vulnerability existing = dependency.getVulnerabilities().stream()
270 .filter(e -> e.getName().equals(v.getName())).findFirst()
271 .orElse(null);
272 if (existing != null) {
273
274 existing.getReferences().addAll(v.getReferences());
275 } else {
276 dependency.addVulnerability(v);
277 }
278 });
279 } catch (Exception e) {
280 LOG.warn("Failed to fetch component-report for: {}", purl, e);
281 }
282 }
283 }
284 }
285 }
286
287
288
289
290
291
292
293
294 private Vulnerability transform(final ComponentReport report, final ComponentReportVulnerability source) {
295 final Vulnerability result = new Vulnerability();
296 result.setSource(Vulnerability.Source.OSSINDEX);
297
298 if (source.getCve() != null) {
299 result.setName(source.getCve());
300 } else {
301 String cve = null;
302 if (source.getTitle() != null) {
303 final Matcher matcher = CVE_PATTERN.matcher(source.getTitle());
304 if (matcher.find()) {
305 cve = matcher.group();
306 } else {
307 cve = source.getTitle();
308 }
309 }
310 if (cve == null && source.getReference() != null) {
311 final Matcher matcher = CVE_PATTERN.matcher(source.getReference().toString());
312 if (matcher.find()) {
313 cve = matcher.group();
314 }
315 }
316 result.setName(cve != null ? cve : source.getId());
317 }
318 result.setDescription(source.getDescription());
319 result.addCwe(source.getCwe());
320
321 final double cvssScore = source.getCvssScore() != null ? source.getCvssScore().doubleValue() : -1;
322
323 if (source.getCvssVector() != null) {
324 if (source.getCvssVector().startsWith("CVSS:3")) {
325 result.setCvssV3(CvssUtil.vectorToCvssV3(source.getCvssVector(), cvssScore));
326 } else {
327
328 final CvssVector cvssVector = CvssVectorFactory.create(source.getCvssVector());
329 final Map<String, String> metrics = cvssVector.getMetrics();
330 if (cvssVector instanceof Cvss2Vector) {
331 String tmp = metrics.get(Cvss2Vector.ACCESS_VECTOR);
332 CvssV2Data.AccessVectorType accessVector = null;
333 if (tmp != null) {
334 accessVector = CvssV2Data.AccessVectorType.fromValue(tmp);
335 }
336 tmp = metrics.get(Cvss2Vector.ACCESS_COMPLEXITY);
337 CvssV2Data.AccessComplexityType accessComplexity = null;
338 if (tmp != null) {
339 accessComplexity = CvssV2Data.AccessComplexityType.fromValue(tmp);
340 }
341 tmp = metrics.get(Cvss2Vector.AUTHENTICATION);
342 CvssV2Data.AuthenticationType authentication = null;
343 if (tmp != null) {
344 authentication = CvssV2Data.AuthenticationType.fromValue(tmp);
345 }
346 tmp = metrics.get(Cvss2Vector.CONFIDENTIALITY_IMPACT);
347 CvssV2Data.CiaType confidentialityImpact = null;
348 if (tmp != null) {
349 confidentialityImpact = CvssV2Data.CiaType.fromValue(tmp);
350 }
351 tmp = metrics.get(Cvss2Vector.INTEGRITY_IMPACT);
352 CvssV2Data.CiaType integrityImpact = null;
353 if (tmp != null) {
354 integrityImpact = CvssV2Data.CiaType.fromValue(tmp);
355 }
356 tmp = metrics.get(Cvss2Vector.AVAILABILITY_IMPACT);
357 CvssV2Data.CiaType availabilityImpact = null;
358 if (tmp != null) {
359 availabilityImpact = CvssV2Data.CiaType.fromValue(tmp);
360 }
361 final String severity = Cvss2Severity.of((float) cvssScore).name().toUpperCase();
362 final CvssV2Data cvssData = new CvssV2Data("2.0", source.getCvssVector(), accessVector,
363 accessComplexity, authentication, confidentialityImpact,
364 integrityImpact, availabilityImpact, cvssScore,
365 severity, null, null, null, null, null, null, null, null, null, null);
366 final CvssV2 cvssV2 = new CvssV2(null, null, cvssData, severity, null, null, null, null, null, null, null);
367 result.setCvssV2(cvssV2);
368 } else {
369 LOG.warn("Unsupported CVSS vector: {}", cvssVector);
370 result.setUnscoredSeverity(Double.toString(cvssScore));
371 }
372 }
373 } else {
374 LOG.debug("OSS has no vector for {}", result.getName());
375 result.setUnscoredSeverity(Double.toString(cvssScore));
376 }
377
378 result.addReference(REFERENCE_TYPE, source.getTitle(), source.getReference().toString());
379
380
381 source.getExternalReferences().forEach(externalReference
382 -> result.addReference("OSSIndex", externalReference.toString(), externalReference.toString()));
383
384
385 final PackageUrl purl = report.getCoordinates();
386 try {
387 final VulnerableSoftwareBuilder builder = new VulnerableSoftwareBuilder()
388 .part(Part.APPLICATION)
389 .vendor(purl.getNamespaceAsString())
390 .product(purl.getName())
391 .version(purl.getVersion());
392
393
394 final VulnerableSoftware software = builder.build();
395 result.addVulnerableSoftware(software);
396 result.setMatchedVulnerableSoftware(software);
397 } catch (CpeValidationException e) {
398 LOG.warn("Unable to construct vulnerable-software for: {}", purl, e);
399 }
400
401 return result;
402 }
403 }