From 9f8270165a8e313fc0c3372f3b4974fb61a73a77 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Thu, 11 Apr 2013 22:59:46 -0400 Subject: [PATCH] added functionality to remove some false positives Former-commit-id: cb57e83478e39b7145482214b45743e2e38e7faf --- .../analyzer/FalsePositiveAnalyzer.java | 154 ++++++++++++++++++ .../dependencycheck/analyzer/JarAnalyzer.java | 86 ++++++---- ...rg.owasp.dependencycheck.analyzer.Analyzer | 1 + 3 files changed, 207 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java diff --git a/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java b/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java new file mode 100644 index 000000000..c81acb9ce --- /dev/null +++ b/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java @@ -0,0 +1,154 @@ +/* + * This file is part of DependencyCheck. + * + * DependencyCheck is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * DependencyCheck is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * DependencyCheck. If not, see http://www.gnu.org/licenses/. + * + * Copyright (c) 2012 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.analyzer; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import org.owasp.dependencycheck.Engine; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.dependency.Identifier; + +/** + * This analyzer attempts to remove some well known false positives - specifically + * regarding the java runtime. + * + * @author Jeremy Long (jeremy.long@gmail.com) + */ +public class FalsePositiveAnalyzer extends AbstractAnalyzer { + + /** + * The set of file extensions supported by this analyzer. + */ + private static final Set EXTENSIONS = null; //newHashSet("jar"); + /** + * The name of the analyzer. + */ + private static final String ANALYZER_NAME = "False Positive Analyzer"; + /** + * The phase that this analyzer is intended to run in. + */ + private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_IDENTIFIER_ANALYSIS; + + /** + * Returns a list of file EXTENSIONS supported by this analyzer. + * + * @return a list of file EXTENSIONS supported by this analyzer. + */ + public Set getSupportedExtensions() { + return EXTENSIONS; + } + + /** + * Returns the name of the analyzer. + * + * @return the name of the analyzer. + */ + public String getName() { + return ANALYZER_NAME; + } + + /** + * Returns whether or not this analyzer can process the given extension. + * + * @param extension the file extension to test for support + * @return whether or not the specified file extension is supported by this + * analyzer. + */ + public boolean supportsExtension(String extension) { + return true; //EXTENSIONS.contains(extension); + } + + /** + * Returns the phase that the analyzer is intended to run in. + * + * @return the phase that the analyzer is intended to run in. + */ + public AnalysisPhase getAnalysisPhase() { + return ANALYSIS_PHASE; + } + + /** + * The initialize method does nothing for this Analyzer. + * + * @throws Exception never thrown by this analyzer + */ + public void initialize() throws Exception { + //do nothing + } + + /** + * The close method does nothing for this Analyzer. + * + * @throws Exception never thrown by this analyzer + */ + public void close() throws Exception { + //do nothing + } + /** + * a list of spring versions. + */ + private List springVersions; + + /** + * + * + * @param dependency the dependency to analyze. + * @param engine the engine that is scanning the dependencies + * @throws AnalysisException is thrown if there is an error reading the JAR + * file. + */ + public void analyze(Dependency dependency, Engine engine) throws AnalysisException { + removeJreEntries(dependency); + removeVersions(dependency); + } + + private void removeVersions(Dependency dependency) { + //todo implement this so that the following is corrected? + //cpe: cpe:/a:apache:axis2:1.4 + //cpe: cpe:/a:apache:axis:1.4 + /* the above was identified from the evidence below: + Source Name Value + Manifest Bundle-Vendor Apache Software Foundation + Manifest Bundle-Version 1.4 + file name axis2-kernel-1.4.1 + pom artifactid axis2-kernel + pom name Apache Axis2 - Kernel + */ + } + + /** + * Removes any CPE entries for the JDK/JRE unless the filename ends with rt.jar + * + * @param dependency the dependency to remove JRE CPEs from + */ + private void removeJreEntries(Dependency dependency) { + List identifiers = dependency.getIdentifiers(); + Iterator itr = identifiers.iterator(); + while (itr.hasNext()) { + Identifier i = itr.next(); + if ((i.getValue().startsWith("cpe:/a:sun:java:") + || i.getValue().startsWith("cpe:/a:oracle:jre") + || i.getValue().startsWith("cpe:/a:oracle:jdk")) + && !dependency.getFileName().toLowerCase().endsWith("rt.jar")) { + itr.remove(); + } + } + } +} diff --git a/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java b/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java index 42b04e35d..5e0231c42 100644 --- a/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java +++ b/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java @@ -30,7 +30,9 @@ import org.owasp.dependencycheck.dependency.EvidenceCollection; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; @@ -38,6 +40,7 @@ import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.bind.JAXBContext; @@ -189,10 +192,7 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { analyzePackageNames(dependency, addPackagesAsEvidence); } catch (IOException ex) { throw new AnalysisException("Exception occurred reading the JAR file.", ex); - } catch (JAXBException ex) { - throw new AnalysisException("Exception occurred reading the POM within the JAR file.", ex); } - } /** @@ -208,10 +208,10 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { * pom. * @return whether or not evidence was added to the dependency */ - protected boolean analyzePOM(Dependency dependency) throws IOException, JAXBException, AnalysisException { + protected boolean analyzePOM(Dependency dependency) throws IOException, AnalysisException { boolean foundSomething = false; Properties pomProperties = null; - Model pom = null; + List poms = new ArrayList(); FileInputStream fs = null; try { fs = new FileInputStream(dependency.getActualFilePath()); @@ -222,17 +222,28 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { final String entryName = (new File(entry.getName())).getName().toLowerCase(); if (!entry.isDirectory() && "pom.xml".equals(entryName)) { - if (pom == null) { - final NonClosingStream stream = new NonClosingStream(zin); + final NonClosingStream stream = new NonClosingStream(zin); + Model p = null; + try { final JAXBElement obj = (JAXBElement) pomUnmarshaller.unmarshal(stream); - pom = (Model) obj.getValue(); - zin.closeEntry(); - } else { - throw new AnalysisException("JAR file contains multiple pom.xml files - unable to process POM"); + p = (Model) obj.getValue(); + } catch (JAXBException ex) { + String msg = String.format("Unable to parse POM '%s' in '%s'", + entry.getName(), dependency.getFilePath()); + AnalysisException ax = new AnalysisException(msg, ex); + dependency.getAnalysisExceptions().add(ax); + Logger.getLogger(JarAnalyzer.class.getName()).log(Level.INFO, msg); } + if (p != null) { + poms.add(p); + } + zin.closeEntry(); } else if (!entry.isDirectory() && "pom.properties".equals(entryName)) { + //TODO what if there is more then one pom.properties? + // need to find the POM, then look to see if there is a sibling + // pom.properties and use those together. if (pomProperties == null) { - Reader reader = null; + Reader reader; try { reader = new InputStreamReader(zin, "UTF-8"); pomProperties = new Properties(); @@ -243,7 +254,10 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { zin.closeEntry(); } } else { - throw new AnalysisException("JAR file contains multiple pom.properties files - unable to process POM"); + String msg = "JAR file contains multiple pom.properties files - unable to process POM"; + AnalysisException ax = new AnalysisException(msg); + dependency.getAnalysisExceptions().add(ax); + Logger.getLogger(JarAnalyzer.class.getName()).log(Level.INFO, msg); } } @@ -257,7 +271,7 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { } } - if (pom != null) { + for (Model pom : poms) { //group id final String groupid = interpolateString(pom.getGroupId(), pomProperties); if (groupid != null) { @@ -576,8 +590,11 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { } else { key = key.toLowerCase(); - if (!IGNORE_LIST.contains(key) && !key.endsWith("jdk") - && !key.contains("lastmodified") && !key.endsWith("package")) { + if (!IGNORE_LIST.contains(key) + && !key.endsWith("jdk") + && !key.contains("lastmodified") + && !key.endsWith("package") + && !isImportPackage(key, value)) { foundSomething = true; if (key.contains("version")) { @@ -696,22 +713,23 @@ public class JarAnalyzer extends AbstractAnalyzer implements Analyzer { sb.append(text.substring(end + 1)); return interpolateString(sb.toString(), properties); //yes yes, this should be a loop... } -// private void addPredefinedData(Dependency dependency) { -// Evidence springTest1 = new Evidence("Manifest", -// "Implementation-Title", -// "Spring Framework", -// Evidence.Confidence.HIGH); -// -// Evidence springTest2 = new Evidence("Manifest", -// "Implementation-Title", -// "org.springframework.core", -// Evidence.Confidence.HIGH); -// -// Set evidence = dependency.getProductEvidence().getEvidence(); -// if (evidence.contains(springTest1) || evidence.contains(springTest2)) { -// dependency.getProductEvidence().addEvidence("a priori", "product", "springsource_spring_framework", Evidence.Confidence.HIGH); -// dependency.getVendorEvidence().addEvidence("a priori", "vendor", "SpringSource", Evidence.Confidence.HIGH); -// dependency.getVendorEvidence().addEvidence("a priori", "vendor", "vmware", Evidence.Confidence.HIGH); -// } -// } + + /** + * Determines if the key value pair from the manifest is for an "import" type + * entry for package names. + * @param key the key from the manifest + * @param value the value from the manifest + * @return true or false depending on if it is believed the entry is an "import" entry + */ + private boolean isImportPackage(String key, String value) { + final Pattern packageRx = Pattern.compile("^((([a-zA-Z_#\\$0-9]\\.)+)\\s*\\;\\s*)+$"); + if (packageRx.matcher(value).matches()) { + if (key.contains("import") || key.contains("include")) { + return true; + } else { + return false; + } + } + return false; + } } diff --git a/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer b/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer index a2e3e122f..25294d6f0 100644 --- a/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer +++ b/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer @@ -2,5 +2,6 @@ org.owasp.dependencycheck.analyzer.JarAnalyzer org.owasp.dependencycheck.analyzer.FileNameAnalyzer org.owasp.dependencycheck.analyzer.HintAnalyzer org.owasp.dependencycheck.analyzer.SpringCleaningAnalyzer +org.owasp.dependencycheck.analyzer.FalsePositiveAnalyzer org.owasp.dependencycheck.data.cpe.CPEAnalyzer org.owasp.dependencycheck.data.nvdcve.NvdCveAnalyzer \ No newline at end of file