HintAnalyzer.java
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.dependency.EvidenceType;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.FileUtils;
import org.owasp.dependencycheck.utils.ResourceNotFoundException;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.TooManyRequestsException;
import org.owasp.dependencycheck.xml.hints.EvidenceMatcher;
import org.owasp.dependencycheck.xml.hints.HintParseException;
import org.owasp.dependencycheck.xml.hints.HintParser;
import org.owasp.dependencycheck.xml.hints.HintRule;
import org.owasp.dependencycheck.xml.hints.VendorDuplicatingHintRule;
import org.owasp.dependencycheck.xml.suppression.PropertyType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import javax.annotation.concurrent.ThreadSafe;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* This analyzer adds evidence to dependencies to enhance the accuracy of
* library identification.
*
* @author Jeremy Long
*/
@ThreadSafe
public class HintAnalyzer extends AbstractAnalyzer {
/**
* The Logger for use throughout the class
*/
private static final Logger LOGGER = LoggerFactory.getLogger(HintAnalyzer.class);
/**
* The name of the hint rule file
*/
private static final String HINT_RULE_FILE_NAME = "dependencycheck-base-hint.xml";
/**
* The array of hint rules.
*/
private HintRule[] hints = null;
/**
* The array of vendor duplicating hint rules.
*/
private VendorDuplicatingHintRule[] vendorHints;
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "Hint Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_INFORMATION_COLLECTION2;
/**
* Returns the name of the analyzer.
*
* @return the name of the analyzer.
*/
@Override
public String getName() {
return ANALYZER_NAME;
}
/**
* Returns the phase that the analyzer is intended to run in.
*
* @return the phase that the analyzer is intended to run in.
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_HINT_ENABLED;
}
/**
* The prepare method does nothing for this Analyzer.
*
* @param engine a reference the dependency-check engine
* @throws InitializationException thrown if there is an exception
*/
@Override
public void prepareAnalyzer(Engine engine) throws InitializationException {
try {
loadHintRules();
} catch (HintParseException ex) {
LOGGER.debug("Unable to parse hint file", ex);
throw new InitializationException("Unable to parse the hint file", ex);
}
}
/**
* The HintAnalyzer uses knowledge about a dependency to add additional
* information to help in identification of identifiers or vulnerabilities.
*
* @param dependency The dependency being analyzed
* @param engine The scanning engine
* @throws AnalysisException is thrown if there is an exception analyzing
* the dependency.
*/
@Override
@SuppressWarnings("StringSplitter")
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
for (HintRule hint : hints) {
boolean matchFound = false;
for (EvidenceMatcher given : hint.getGivenVendor()) {
if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.VENDOR), given)) {
matchFound = true;
break;
}
}
if (!matchFound) {
for (EvidenceMatcher given : hint.getGivenProduct()) {
if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.PRODUCT), given)) {
matchFound = true;
break;
}
}
}
if (!matchFound) {
for (EvidenceMatcher given : hint.getGivenVersion()) {
if (hasMatchingEvidence(dependency.getEvidence(EvidenceType.VERSION), given)) {
matchFound = true;
break;
}
}
}
if (!matchFound) {
for (PropertyType pt : hint.getFileNames()) {
if (pt.matches(dependency.getFileName())) {
matchFound = true;
break;
}
}
}
if (matchFound) {
hint.getAddVendor().forEach((e) -> {
dependency.addEvidence(EvidenceType.VENDOR, e);
for (String weighting : e.getValue().split(" ")) {
dependency.addVendorWeighting(weighting);
}
});
hint.getAddProduct().forEach((e) -> {
dependency.addEvidence(EvidenceType.PRODUCT, e);
for (String weighting : e.getValue().split(" ")) {
dependency.addProductWeighting(weighting);
}
});
hint.getAddVersion().forEach((e) -> dependency.addEvidence(EvidenceType.VERSION, e));
hint.getRemoveVendor().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.VENDOR, e));
hint.getRemoveProduct().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.PRODUCT, e));
hint.getRemoveVersion().forEach((e) -> removeMatchingEvidences(dependency, EvidenceType.VERSION, e));
}
}
for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
for (VendorDuplicatingHintRule dhr : vendorHints) {
if (dhr.getValue().equalsIgnoreCase(e.getValue())) {
dependency.addEvidence(EvidenceType.VENDOR, new Evidence(e.getSource() + " (hint)",
e.getName(), dhr.getDuplicate(), e.getConfidence(), true));
}
}
}
}
/**
* Determine if there is matching evidence.
*
* @param evidences the evidence to test
* @param criterion the criteria for a match
* @return true if the evidence matches, otherwise false
*/
private boolean hasMatchingEvidence(Set<Evidence> evidences, EvidenceMatcher criterion) {
for (Evidence evidence : evidences) {
if (criterion.matches(evidence)) {
return true;
}
}
return false;
}
/**
* Removes any matching evidence from the dependency.
*
* @param dependency the dependency to update
* @param type the type of evidence to inspect and possibly remove
* @param e the evidence matcher
*/
private void removeMatchingEvidences(Dependency dependency, EvidenceType type, EvidenceMatcher e) {
for (Evidence evidence : dependency.getEvidence(type)) {
if (e.matches(evidence)) {
dependency.removeEvidence(type, evidence);
}
}
}
/**
* Loads the hint rules file.
*
* @throws HintParseException thrown if the XML cannot be parsed.
*/
private void loadHintRules() throws HintParseException {
final List<HintRule> localHints;
final List<VendorDuplicatingHintRule> localVendorHints;
final HintParser parser = new HintParser();
File file = null;
try (InputStream in = FileUtils.getResourceAsStream(HINT_RULE_FILE_NAME)) {
if (in == null) {
throw new HintParseException("Hint rules `" + HINT_RULE_FILE_NAME + "` could not be found");
}
parser.parseHints(in);
} catch (SAXException | IOException ex) {
throw new HintParseException("Error parsing hints: " + ex.getMessage(), ex);
}
localHints = parser.getHintRules();
localVendorHints = parser.getVendorDuplicatingHintRules();
final String filePath = getSettings().getString(Settings.KEYS.HINTS_FILE);
if (filePath != null) {
boolean deleteTempFile = false;
try {
final Pattern uriRx = Pattern.compile("^(https?|file):.*", Pattern.CASE_INSENSITIVE);
if (uriRx.matcher(filePath).matches()) {
deleteTempFile = true;
file = getSettings().getTempFile("hint", "xml");
final URL url = new URL(filePath);
try {
Downloader.getInstance().fetchFile(url, file, false);
} catch (DownloadFailedException ex) {
try {
Thread.sleep(500);
Downloader.getInstance().fetchFile(url, file, true);
} catch (TooManyRequestsException ex1) {
throw new HintParseException("Unable to download hint file `" + file + "`; received 429 - too many requests", ex1);
} catch (ResourceNotFoundException ex1) {
throw new HintParseException("Unable to download hint file `" + file + "`; received 404 - resource not found", ex1);
} catch (InterruptedException ex1) {
Thread.currentThread().interrupt();
throw new HintParseException("Unable to download hint file `" + file + "`", ex1);
}
} catch (TooManyRequestsException ex) {
throw new HintParseException("Unable to download hint file `" + file + "`; received 429 - too many requests", ex);
} catch (ResourceNotFoundException ex) {
throw new HintParseException("Unable to download hint file `" + file + "`; received 404 - resource not found", ex);
}
} else {
file = new File(filePath);
if (!file.exists()) {
try (InputStream fromClasspath = FileUtils.getResourceAsStream(filePath)) {
deleteTempFile = true;
file = getSettings().getTempFile("hint", "xml");
Files.copy(fromClasspath, file.toPath());
} catch (IOException ex) {
throw new HintParseException("Unable to locate hints file in classpath", ex);
}
}
}
if (file == null) {
throw new HintParseException("Unable to locate hints file:" + filePath);
} else {
try {
parser.parseHints(file);
if (parser.getHintRules() != null && !parser.getHintRules().isEmpty()) {
localHints.addAll(parser.getHintRules());
}
if (parser.getVendorDuplicatingHintRules() != null && !parser.getVendorDuplicatingHintRules().isEmpty()) {
localVendorHints.addAll(parser.getVendorDuplicatingHintRules());
}
} catch (HintParseException ex) {
LOGGER.warn("Unable to parse hint rule xml file '{}'", file.getPath());
LOGGER.warn(ex.getMessage());
LOGGER.debug("", ex);
throw ex;
}
}
} catch (DownloadFailedException ex) {
throw new HintParseException("Unable to fetch the configured hint file", ex);
} catch (MalformedURLException ex) {
throw new HintParseException("Configured hint file has an invalid URL", ex);
} catch (IOException ex) {
throw new HintParseException("Unable to create temp file for hints", ex);
} finally {
if (deleteTempFile && file != null) {
FileUtils.delete(file);
}
}
}
hints = localHints.toArray(new HintRule[0]);
vendorHints = localVendorHints.toArray(new VendorDuplicatingHintRule[0]);
LOGGER.debug("{} hint rules were loaded.", hints.length);
LOGGER.debug("{} duplicating hint rules were loaded.", vendorHints.length);
}
}