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.analyzer;
19  
20  import org.owasp.dependencycheck.Engine;
21  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
22  import org.owasp.dependencycheck.dependency.Dependency;
23  import org.owasp.dependencycheck.dependency.Evidence;
24  import org.owasp.dependencycheck.dependency.EvidenceType;
25  import org.owasp.dependencycheck.exception.InitializationException;
26  import org.owasp.dependencycheck.utils.DownloadFailedException;
27  import org.owasp.dependencycheck.utils.Downloader;
28  import org.owasp.dependencycheck.utils.FileUtils;
29  import org.owasp.dependencycheck.utils.ResourceNotFoundException;
30  import org.owasp.dependencycheck.utils.Settings;
31  import org.owasp.dependencycheck.utils.TooManyRequestsException;
32  import org.owasp.dependencycheck.xml.hints.EvidenceMatcher;
33  import org.owasp.dependencycheck.xml.hints.HintParseException;
34  import org.owasp.dependencycheck.xml.hints.HintParser;
35  import org.owasp.dependencycheck.xml.hints.HintRule;
36  import org.owasp.dependencycheck.xml.hints.VendorDuplicatingHintRule;
37  import org.owasp.dependencycheck.xml.suppression.PropertyType;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  import org.xml.sax.SAXException;
41  
42  import javax.annotation.concurrent.ThreadSafe;
43  import java.io.File;
44  import java.io.IOException;
45  import java.io.InputStream;
46  import java.net.MalformedURLException;
47  import java.net.URL;
48  import java.nio.file.Files;
49  import java.util.List;
50  import java.util.Set;
51  import java.util.regex.Pattern;
52  
53  /**
54   * This analyzer adds evidence to dependencies to enhance the accuracy of
55   * library identification.
56   *
57   * @author Jeremy Long
58   */
59  @ThreadSafe
60  public class HintAnalyzer extends AbstractAnalyzer {
61  
62      /**
63       * The Logger for use throughout the class
64       */
65      private static final Logger LOGGER = LoggerFactory.getLogger(HintAnalyzer.class);
66      /**
67       * The name of the hint rule file
68       */
69      private static final String HINT_RULE_FILE_NAME = "dependencycheck-base-hint.xml";
70      /**
71       * The array of hint rules.
72       */
73      private HintRule[] hints = null;
74      /**
75       * The array of vendor duplicating hint rules.
76       */
77      private VendorDuplicatingHintRule[] vendorHints;
78      /**
79       * The name of the analyzer.
80       */
81      private static final String ANALYZER_NAME = "Hint Analyzer";
82      /**
83       * The phase that this analyzer is intended to run in.
84       */
85      private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_INFORMATION_COLLECTION2;
86  
87      /**
88       * Returns the name of the analyzer.
89       *
90       * @return the name of the analyzer.
91       */
92      @Override
93      public String getName() {
94          return ANALYZER_NAME;
95      }
96  
97      /**
98       * Returns the phase that the analyzer is intended to run in.
99       *
100      * @return the phase that the analyzer is intended to run in.
101      */
102     @Override
103     public AnalysisPhase getAnalysisPhase() {
104         return ANALYSIS_PHASE;
105     }
106 
107     /**
108      * <p>
109      * Returns the setting key to determine if the analyzer is enabled.</p>
110      *
111      * @return the key for the analyzer's enabled property
112      */
113     @Override
114     protected String getAnalyzerEnabledSettingKey() {
115         return Settings.KEYS.ANALYZER_HINT_ENABLED;
116     }
117 
118     /**
119      * The prepare method does nothing for this Analyzer.
120      *
121      * @param engine a reference the dependency-check engine
122      * @throws InitializationException thrown if there is an exception
123      */
124     @Override
125     public void prepareAnalyzer(Engine engine) throws InitializationException {
126         try {
127             loadHintRules();
128         } catch (HintParseException ex) {
129             LOGGER.debug("Unable to parse hint file", ex);
130             throw new InitializationException("Unable to parse the hint file", ex);
131         }
132     }
133 
134     /**
135      * The HintAnalyzer uses knowledge about a dependency to add additional
136      * information to help in identification of identifiers or vulnerabilities.
137      *
138      * @param dependency The dependency being analyzed
139      * @param engine The scanning engine
140      * @throws AnalysisException is thrown if there is an exception analyzing
141      * the dependency.
142      */
143     @Override
144     @SuppressWarnings("StringSplitter")
145     protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
146         for (HintRule hint : hints) {
147             boolean matchFound = false;
148             for (EvidenceMatcher given : hint.getGivenVendor()) {
149                 if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.VENDOR), given)) {
150                     matchFound = true;
151                     break;
152                 }
153             }
154             if (!matchFound) {
155                 for (EvidenceMatcher given : hint.getGivenProduct()) {
156                     if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.PRODUCT), given)) {
157                         matchFound = true;
158                         break;
159                     }
160                 }
161             }
162             if (!matchFound) {
163                 for (EvidenceMatcher given : hint.getGivenVersion()) {
164                     if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.VERSION), given)) {
165                         matchFound = true;
166                         break;
167                     }
168                 }
169             }
170             if (!matchFound) {
171                 for (PropertyType pt : hint.getFileNames()) {
172                     if (pt.matches(dependency.getFileName())) {
173                         matchFound = true;
174                         break;
175                     }
176                 }
177             }
178             if (matchFound) {
179                 hint.getAddVendor().forEach((e) -> {
180                     dependency.addEvidence(EvidenceType.VENDOR, e);
181                     for (String weighting : e.getValue().split(" ")) {
182                         dependency.addVendorWeighting(weighting);
183                     }
184                 });
185                 hint.getAddProduct().forEach((e) -> {
186                     dependency.addEvidence(EvidenceType.PRODUCT, e);
187                     for (String weighting : e.getValue().split(" ")) {
188                         dependency.addProductWeighting(weighting);
189                     }
190                 });
191                 hint.getAddVersion().forEach((e) -> dependency.addEvidence(EvidenceType.VERSION, e));
192 
193                 hint.getRemoveVendor().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.VENDOR, e));
194                 hint.getRemoveProduct().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.PRODUCT, e));
195                 hint.getRemoveVersion().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.VERSION, e));
196             }
197         }
198 
199         for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
200             for (VendorDuplicatingHintRule dhr : vendorHints) {
201                 if (dhr.getValue().equalsIgnoreCase(e.getValue())) {
202                     dependency.addEvidence(EvidenceType.VENDOR, new Evidence(e.getSource() + " (hint)",
203                             e.getName(), dhr.getDuplicate(), e.getConfidence(), true));
204                 }
205             }
206         }
207     }
208 
209     /**
210      * Determine if there is matching evidence.
211      *
212      * @param evidences the evidence to test
213      * @param criterion the criteria for a match
214      * @return true if the evidence matches, otherwise false
215      */
216     private boolean hasMatchingEvidence(Set<Evidence> evidences, EvidenceMatcher criterion) {
217         for (Evidence evidence : evidences) {
218             if (criterion.matches(evidence)) {
219                 return true;
220             }
221         }
222         return false;
223     }
224 
225     /**
226      * Removes any matching evidence from the dependency.
227      *
228      * @param dependency the dependency to update
229      * @param type the type of evidence to inspect and possibly remove
230      * @param e the evidence matcher
231      */
232     private void removeMatchingEvidences(Dependency dependency, EvidenceType type, EvidenceMatcher e) {
233         for (Evidence evidence : dependency.getEvidence(type)) {
234             if (e.matches(evidence)) {
235                 dependency.removeEvidence(type, evidence);
236             }
237         }
238     }
239 
240     /**
241      * Loads the hint rules file.
242      *
243      * @throws HintParseException thrown if the XML cannot be parsed.
244      */
245     private void loadHintRules() throws HintParseException {
246         final List<HintRule> localHints;
247         final List<VendorDuplicatingHintRule> localVendorHints;
248         final HintParser parser = new HintParser();
249         File file = null;
250         try (InputStream in = FileUtils.getResourceAsStream(HINT_RULE_FILE_NAME)) {
251             if (in == null) {
252                 throw new HintParseException("Hint rules `" + HINT_RULE_FILE_NAME + "` could not be found");
253             }
254             parser.parseHints(in);
255         } catch (SAXException | IOException ex) {
256             throw new HintParseException("Error parsing hints: " + ex.getMessage(), ex);
257         }
258         localHints = parser.getHintRules();
259         localVendorHints = parser.getVendorDuplicatingHintRules();
260 
261         final String filePath = getSettings().getString(Settings.KEYS.HINTS_FILE);
262         if (filePath != null) {
263             boolean deleteTempFile = false;
264             try {
265                 final Pattern uriRx = Pattern.compile("^(https?|file):.*", Pattern.CASE_INSENSITIVE);
266                 if (uriRx.matcher(filePath).matches()) {
267                     deleteTempFile = true;
268                     file = getSettings().getTempFile("hint", "xml");
269                     final URL url = new URL(filePath);
270                     final Downloader downloader = new Downloader(getSettings());
271                     try {
272                         downloader.fetchFile(url, file, false);
273                     } catch (DownloadFailedException ex) {
274                         try {
275                             Thread.sleep(500);
276                             downloader.fetchFile(url, file, true);
277                         } catch (TooManyRequestsException ex1) {
278                             throw new HintParseException("Unable to download hint file `" + file + "`; received 429 - too many requests", ex1);
279                         } catch (ResourceNotFoundException ex1) {
280                             throw new HintParseException("Unable to download hint file `" + file + "`; received 404 - resource not found", ex1);
281                         } catch (InterruptedException ex1) {
282                             Thread.currentThread().interrupt();
283                             throw new HintParseException("Unable to download hint file `" + file + "`", ex1);
284                         }
285                     } catch (TooManyRequestsException ex) {
286                         throw new HintParseException("Unable to download hint file `" + file + "`; received 429 - too many requests", ex);
287                     } catch (ResourceNotFoundException ex) {
288                         throw new HintParseException("Unable to download hint file `" + file + "`; received 404 - resource not found", ex);
289                     }
290                 } else {
291                     file = new File(filePath);
292                     if (!file.exists()) {
293                         try (InputStream fromClasspath = FileUtils.getResourceAsStream(filePath)) {
294                             deleteTempFile = true;
295                             file = getSettings().getTempFile("hint", "xml");
296                             Files.copy(fromClasspath, file.toPath());
297                         } catch (IOException ex) {
298                             throw new HintParseException("Unable to locate hints file in classpath", ex);
299                         }
300                     }
301                 }
302 
303                 if (file == null) {
304                     throw new HintParseException("Unable to locate hints file:" + filePath);
305                 } else {
306                     try {
307                         parser.parseHints(file);
308                         if (parser.getHintRules() != null && !parser.getHintRules().isEmpty()) {
309                             localHints.addAll(parser.getHintRules());
310                         }
311                         if (parser.getVendorDuplicatingHintRules() != null && !parser.getVendorDuplicatingHintRules().isEmpty()) {
312                             localVendorHints.addAll(parser.getVendorDuplicatingHintRules());
313                         }
314                     } catch (HintParseException ex) {
315                         LOGGER.warn("Unable to parse hint rule xml file '{}'", file.getPath());
316                         LOGGER.warn(ex.getMessage());
317                         LOGGER.debug("", ex);
318                         throw ex;
319                     }
320                 }
321             } catch (DownloadFailedException ex) {
322                 throw new HintParseException("Unable to fetch the configured hint file", ex);
323             } catch (MalformedURLException ex) {
324                 throw new HintParseException("Configured hint file has an invalid URL", ex);
325             } catch (IOException ex) {
326                 throw new HintParseException("Unable to create temp file for hints", ex);
327             } finally {
328                 if (deleteTempFile && file != null) {
329                     FileUtils.delete(file);
330                 }
331             }
332         }
333         hints = localHints.toArray(new HintRule[0]);
334         vendorHints = localVendorHints.toArray(new VendorDuplicatingHintRule[0]);
335         LOGGER.debug("{} hint rules were loaded.", hints.length);
336         LOGGER.debug("{} duplicating hint rules were loaded.", vendorHints.length);
337     }
338 }