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