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.addReferences(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 }