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 java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.net.MalformedURLException;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.regex.Pattern;
29 import org.owasp.dependencycheck.Engine;
30 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
31 import org.owasp.dependencycheck.dependency.Dependency;
32 import org.owasp.dependencycheck.dependency.Evidence;
33 import org.owasp.dependencycheck.exception.InitializationException;
34 import org.owasp.dependencycheck.xml.suppression.PropertyType;
35 import org.owasp.dependencycheck.utils.DownloadFailedException;
36 import org.owasp.dependencycheck.utils.Downloader;
37 import org.owasp.dependencycheck.utils.FileUtils;
38 import org.owasp.dependencycheck.utils.Settings;
39 import org.owasp.dependencycheck.xml.hints.VendorDuplicatingHintRule;
40 import org.owasp.dependencycheck.xml.hints.HintParseException;
41 import org.owasp.dependencycheck.xml.hints.HintParser;
42 import org.owasp.dependencycheck.xml.hints.HintRule;
43 import org.owasp.dependencycheck.xml.hints.Hints;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46 import org.xml.sax.SAXException;
47
48 /**
49 * This analyzer adds evidence to dependencies to enhance the accuracy of
50 * library identification.
51 *
52 * @author Jeremy Long
53 */
54 public class HintAnalyzer extends AbstractAnalyzer {
55
56 //<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
57 /**
58 * The name of the analyzer.
59 */
60 private static final String ANALYZER_NAME = "Hint Analyzer";
61 /**
62 * The phase that this analyzer is intended to run in.
63 */
64 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.PRE_IDENTIFIER_ANALYSIS;
65
66 /**
67 * Returns the name of the analyzer.
68 *
69 * @return the name of the analyzer.
70 */
71 @Override
72 public String getName() {
73 return ANALYZER_NAME;
74 }
75
76 /**
77 * Returns the phase that the analyzer is intended to run in.
78 *
79 * @return the phase that the analyzer is intended to run in.
80 */
81 @Override
82 public AnalysisPhase getAnalysisPhase() {
83 return ANALYSIS_PHASE;
84 }
85 /**
86 * <p>
87 * Returns the setting key to determine if the analyzer is enabled.</p>
88 *
89 * @return the key for the analyzer's enabled property
90 */
91 @Override
92 protected String getAnalyzerEnabledSettingKey() {
93 return Settings.KEYS.ANALYZER_HINT_ENABLED;
94 }
95
96 /**
97 * The initialize method does nothing for this Analyzer.
98 *
99 * @throws InitializationException thrown if there is an exception
100 */
101 @Override
102 public void initializeAnalyzer() throws InitializationException {
103 try {
104 loadHintRules();
105 } catch (HintParseException ex) {
106 LOGGER.debug("Unable to parse hint file", ex);
107 throw new InitializationException("Unable to parse the hint file", ex);
108 }
109 }
110 //</editor-fold>
111
112 /**
113 * The Logger for use throughout the class
114 */
115 private static final Logger LOGGER = LoggerFactory.getLogger(HintAnalyzer.class);
116 /**
117 * The name of the hint rule file
118 */
119 private static final String HINT_RULE_FILE_NAME = "dependencycheck-base-hint.xml";
120 /**
121 * The collection of hints.
122 */
123 private Hints hints;
124
125 /**
126 * The HintAnalyzer uses knowledge about a dependency to add additional
127 * information to help in identification of identifiers or vulnerabilities.
128 *
129 * @param dependency The dependency being analyzed
130 * @param engine The scanning engine
131 * @throws AnalysisException is thrown if there is an exception analyzing
132 * the dependency.
133 */
134 @Override
135 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
136 for (HintRule hint : hints.getHintRules()) {
137 boolean shouldAdd = false;
138 for (Evidence given : hint.getGivenVendor()) {
139 if (dependency.getVendorEvidence().getEvidence().contains(given)) {
140 shouldAdd = true;
141 break;
142 }
143 }
144 if (!shouldAdd) {
145 for (Evidence given : hint.getGivenProduct()) {
146 if (dependency.getProductEvidence().getEvidence().contains(given)) {
147 shouldAdd = true;
148 break;
149 }
150 }
151 }
152 if (!shouldAdd) {
153 for (PropertyType pt : hint.getFilenames()) {
154 if (pt.matches(dependency.getFileName())) {
155 shouldAdd = true;
156 }
157 }
158 }
159 if (shouldAdd) {
160 for (Evidence e : hint.getAddVendor()) {
161 dependency.getVendorEvidence().addEvidence(e);
162 }
163 for (Evidence e : hint.getAddProduct()) {
164 dependency.getProductEvidence().addEvidence(e);
165 }
166 for (Evidence e : hint.getAddVersion()) {
167 dependency.getVersionEvidence().addEvidence(e);
168 }
169 }
170 }
171
172 final Iterator<Evidence> itr = dependency.getVendorEvidence().iterator();
173 final List<Evidence> newEntries = new ArrayList<Evidence>();
174 while (itr.hasNext()) {
175 final Evidence e = itr.next();
176 for (VendorDuplicatingHintRule dhr : hints.getVendorDuplicatingHintRules()) {
177 if (dhr.getValue().equalsIgnoreCase(e.getValue(false))) {
178 newEntries.add(new Evidence(e.getSource() + " (hint)",
179 e.getName(), dhr.getDuplicate(), e.getConfidence()));
180 }
181 }
182 }
183 for (Evidence e : newEntries) {
184 dependency.getVendorEvidence().addEvidence(e);
185 }
186
187 //<editor-fold defaultstate="collapsed" desc="Old implementation">
188 /*
189 final Evidence springTest1 = new Evidence("Manifest",
190 "Implementation-Title",
191 "Spring Framework",
192 Confidence.HIGH);
193
194 final Evidence springTest2 = new Evidence("Manifest",
195 "Implementation-Title",
196 "org.springframework.core",
197 Confidence.HIGH);
198
199 final Evidence springTest3 = new Evidence("Manifest",
200 "Implementation-Title",
201 "spring-core",
202 Confidence.HIGH);
203
204 final Evidence springTest4 = new Evidence("jar",
205 "package name",
206 "springframework",
207 Confidence.LOW);
208
209 final Evidence springSecurityTest1 = new Evidence("Manifest",
210 "Bundle-Name",
211 "Spring Security Core",
212 Confidence.MEDIUM);
213
214 final Evidence springSecurityTest2 = new Evidence("pom",
215 "artifactid",
216 "spring-security-core",
217 Confidence.HIGH);
218
219 final Evidence symfony = new Evidence("composer.lock",
220 "vendor",
221 "symfony",
222 Confidence.HIGHEST);
223
224 final Evidence zendframeworkVendor = new Evidence("composer.lock",
225 "vendor",
226 "zendframework",
227 Confidence.HIGHEST);
228
229 final Evidence zendframeworkProduct = new Evidence("composer.lock",
230 "product",
231 "zendframework",
232 Confidence.HIGHEST);
233
234 //springsource/vware problem
235 final Set<Evidence> product = dependency.getProductEvidence().getEvidence();
236 final Set<Evidence> vendor = dependency.getVendorEvidence().getEvidence();
237
238 if (product.contains(springTest1) || product.contains(springTest2) || product.contains(springTest3)
239 || (dependency.getFileName().contains("spring") && product.contains(springTest4))) {
240 dependency.getProductEvidence().addEvidence("hint analyzer", "product", "springsource spring framework", Confidence.HIGH);
241 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "SpringSource", Confidence.HIGH);
242 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "vmware", Confidence.HIGH);
243 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "pivotal", Confidence.HIGH);
244 }
245
246 if (vendor.contains(springTest4)) {
247 dependency.getProductEvidence().addEvidence("hint analyzer", "product", "springsource_spring_framework", Confidence.HIGH);
248 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "vmware", Confidence.HIGH);
249 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "pivotal", Confidence.HIGH);
250 }
251
252 if (product.contains(springSecurityTest1) || product.contains(springSecurityTest2)) {
253 dependency.getProductEvidence().addEvidence("hint analyzer", "product", "springsource_spring_security", Confidence.HIGH);
254 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "SpringSource", Confidence.HIGH);
255 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "vmware", Confidence.HIGH);
256 }
257
258 if (vendor.contains(symfony)) {
259 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "sensiolabs", Confidence.HIGHEST);
260 }
261
262 if (vendor.contains(zendframeworkVendor)) {
263 dependency.getVendorEvidence().addEvidence("hint analyzer", "vendor", "zend", Confidence.HIGHEST);
264 }
265
266 if (product.contains(zendframeworkProduct)) {
267 dependency.getProductEvidence().addEvidence("hint analyzer", "vendor", "zend_framework", Confidence.HIGHEST);
268 }
269
270 //sun/oracle problem
271 final Iterator<Evidence> itr = dependency.getVendorEvidence().iterator();
272 final List<Evidence> newEntries = new ArrayList<Evidence>();
273 while (itr.hasNext()) {
274 final Evidence e = itr.next();
275 if ("sun".equalsIgnoreCase(e.getValue(false))) {
276 final Evidence newEvidence = new Evidence(e.getSource() + " (hint)", e.getName(), "oracle", e.getConfidence());
277 newEntries.add(newEvidence);
278 } else if ("oracle".equalsIgnoreCase(e.getValue(false))) {
279 final Evidence newEvidence = new Evidence(e.getSource() + " (hint)", e.getName(), "sun", e.getConfidence());
280 newEntries.add(newEvidence);
281 }
282 }
283 for (Evidence e : newEntries) {
284 dependency.getVendorEvidence().addEvidence(e);
285 }
286 */
287 //</editor-fold>
288 }
289
290 /**
291 * Loads the hint rules file.
292 *
293 * @throws HintParseException thrown if the XML cannot be parsed.
294 */
295 private void loadHintRules() throws HintParseException {
296 final HintParser parser = new HintParser();
297 File file = null;
298 try {
299 hints = parser.parseHints(this.getClass().getClassLoader().getResourceAsStream(HINT_RULE_FILE_NAME));
300 } catch (HintParseException ex) {
301 LOGGER.error("Unable to parse the base hint data file");
302 LOGGER.debug("Unable to parse the base hint data file", ex);
303 } catch (SAXException ex) {
304 LOGGER.error("Unable to parse the base hint data file");
305 LOGGER.debug("Unable to parse the base hint data file", ex);
306 }
307 final String filePath = Settings.getString(Settings.KEYS.HINTS_FILE);
308 if (filePath == null) {
309 return;
310 }
311 boolean deleteTempFile = false;
312 try {
313 final Pattern uriRx = Pattern.compile("^(https?|file)\\:.*", Pattern.CASE_INSENSITIVE);
314 if (uriRx.matcher(filePath).matches()) {
315 deleteTempFile = true;
316 file = FileUtils.getTempFile("hint", "xml");
317 final URL url = new URL(filePath);
318 try {
319 Downloader.fetchFile(url, file, false);
320 } catch (DownloadFailedException ex) {
321 Downloader.fetchFile(url, file, true);
322 }
323 } else {
324 file = new File(filePath);
325 if (!file.exists()) {
326 InputStream fromClasspath = null;
327 try {
328 fromClasspath = this.getClass().getClassLoader().getResourceAsStream(filePath);
329 if (fromClasspath != null) {
330 deleteTempFile = true;
331 file = FileUtils.getTempFile("hint", "xml");
332 try {
333 org.apache.commons.io.FileUtils.copyInputStreamToFile(fromClasspath, file);
334 } catch (IOException ex) {
335 throw new HintParseException("Unable to locate hints file in classpath", ex);
336 }
337 }
338 } finally {
339 if (fromClasspath != null) {
340 fromClasspath.close();
341 }
342 }
343 }
344 }
345
346 if (file != null) {
347 try {
348 final Hints newHints = parser.parseHints(file);
349 hints.getHintRules().addAll(newHints.getHintRules());
350 hints.getVendorDuplicatingHintRules().addAll(newHints.getVendorDuplicatingHintRules());
351 LOGGER.debug("{} hint rules were loaded.", hints.getHintRules().size());
352 LOGGER.debug("{} duplicating hint rules were loaded.", hints.getVendorDuplicatingHintRules().size());
353 } catch (HintParseException ex) {
354 LOGGER.warn("Unable to parse hint rule xml file '{}'", file.getPath());
355 LOGGER.warn(ex.getMessage());
356 LOGGER.debug("", ex);
357 throw ex;
358 }
359 }
360 } catch (DownloadFailedException ex) {
361 throw new HintParseException("Unable to fetch the configured hint file", ex);
362 } catch (MalformedURLException ex) {
363 throw new HintParseException("Configured hint file has an invalid URL", ex);
364 } catch (IOException ex) {
365 throw new HintParseException("Unable to create temp file for hints", ex);
366 } finally {
367 if (deleteTempFile && file != null) {
368 FileUtils.delete(file);
369 }
370 }
371 }
372 }