Coverage Report - org.owasp.dependencycheck.analyzer.FalsePositiveAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
FalsePositiveAnalyzer
0%
0/115
0%
0/122
8.222
 
 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.UnsupportedEncodingException;
 21  
 import java.net.URLEncoder;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collections;
 24  
 import java.util.Iterator;
 25  
 import java.util.List;
 26  
 import java.util.ListIterator;
 27  
 import java.util.Set;
 28  
 import java.util.logging.Level;
 29  
 import java.util.logging.Logger;
 30  
 import java.util.regex.Matcher;
 31  
 import java.util.regex.Pattern;
 32  
 import org.owasp.dependencycheck.Engine;
 33  
 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
 34  
 import org.owasp.dependencycheck.dependency.Dependency;
 35  
 import org.owasp.dependencycheck.dependency.Identifier;
 36  
 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
 37  
 
 38  
 /**
 39  
  * This analyzer attempts to remove some well known false positives - specifically regarding the java runtime.
 40  
  *
 41  
  * @author Jeremy Long <jeremy.long@owasp.org>
 42  
  */
 43  
 public class FalsePositiveAnalyzer extends AbstractAnalyzer {
 44  
 
 45  
     //<editor-fold defaultstate="collapsed" desc="All standard implmentation details of Analyzer">
 46  
     /**
 47  
      * The name of the analyzer.
 48  
      */
 49  
     private static final String ANALYZER_NAME = "False Positive Analyzer";
 50  
     /**
 51  
      * The phase that this analyzer is intended to run in.
 52  
      */
 53  0
     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
 54  
 
 55  
     /**
 56  
      * Returns the name of the analyzer.
 57  
      *
 58  
      * @return the name of the analyzer.
 59  
      */
 60  
     public String getName() {
 61  0
         return ANALYZER_NAME;
 62  
     }
 63  
 
 64  
     /**
 65  
      * Returns the phase that the analyzer is intended to run in.
 66  
      *
 67  
      * @return the phase that the analyzer is intended to run in.
 68  
      */
 69  
     public AnalysisPhase getAnalysisPhase() {
 70  0
         return ANALYSIS_PHASE;
 71  
     }
 72  
     //</editor-fold>
 73  
 
 74  
     /**
 75  
      * Analyzes the dependencies and removes bad/incorrect CPE associations based on various heuristics.
 76  
      *
 77  
      * @param dependency the dependency to analyze.
 78  
      * @param engine the engine that is scanning the dependencies
 79  
      * @throws AnalysisException is thrown if there is an error reading the JAR file.
 80  
      */
 81  
     @Override
 82  
     public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
 83  0
         removeJreEntries(dependency);
 84  0
         removeBadMatches(dependency);
 85  0
         removeWrongVersionMatches(dependency);
 86  0
         removeSpuriousCPE(dependency);
 87  0
         addFalseNegativeCPEs(dependency);
 88  0
     }
 89  
 
 90  
     /**
 91  
      * <p>
 92  
      * Intended to remove spurious CPE entries. By spurious we mean duplicate, less specific CPE entries.</p>
 93  
      * <p>
 94  
      * Example:</p>
 95  
      * <code>
 96  
      * cpe:/a:some-vendor:some-product
 97  
      * cpe:/a:some-vendor:some-product:1.5
 98  
      * cpe:/a:some-vendor:some-product:1.5.2
 99  
      * </code>
 100  
      * <p>
 101  
      * Should be trimmed to:</p>
 102  
      * <code>
 103  
      * cpe:/a:some-vendor:some-product:1.5.2
 104  
      * </code>
 105  
      *
 106  
      * @param dependency the dependency being analyzed
 107  
      */
 108  
     @SuppressWarnings("null")
 109  
     private void removeSpuriousCPE(Dependency dependency) {
 110  0
         final List<Identifier> ids = new ArrayList<Identifier>();
 111  0
         ids.addAll(dependency.getIdentifiers());
 112  0
         Collections.sort(ids);
 113  0
         final ListIterator<Identifier> mainItr = ids.listIterator();
 114  0
         while (mainItr.hasNext()) {
 115  0
             final Identifier currentId = mainItr.next();
 116  0
             final VulnerableSoftware currentCpe = parseCpe(currentId.getType(), currentId.getValue());
 117  0
             if (currentCpe == null) {
 118  0
                 continue;
 119  
             }
 120  0
             final ListIterator<Identifier> subItr = ids.listIterator(mainItr.nextIndex());
 121  0
             while (subItr.hasNext()) {
 122  0
                 final Identifier nextId = subItr.next();
 123  0
                 final VulnerableSoftware nextCpe = parseCpe(nextId.getType(), nextId.getValue());
 124  0
                 if (nextCpe == null) {
 125  0
                     continue;
 126  
                 }
 127  
                 //TODO fix the version problem below
 128  0
                 if (currentCpe.getVendor().equals(nextCpe.getVendor())) {
 129  0
                     if (currentCpe.getProduct().equals(nextCpe.getProduct())) {
 130  
                         // see if one is contained in the other.. remove the contained one from dependency.getIdentifier
 131  0
                         final String currentVersion = currentCpe.getVersion();
 132  0
                         final String nextVersion = nextCpe.getVersion();
 133  0
                         if (currentVersion == null && nextVersion == null) {
 134  
                             //how did we get here?
 135  0
                             Logger.getLogger(FalsePositiveAnalyzer.class
 136  
                                     .getName()).log(Level.FINE, "currentVersion and nextVersion are both null?");
 137  0
                         } else if (currentVersion == null && nextVersion != null) {
 138  0
                             dependency.getIdentifiers().remove(currentId);
 139  0
                         } else if (nextVersion == null && currentVersion != null) {
 140  0
                             dependency.getIdentifiers().remove(nextId);
 141  0
                         } else if (currentVersion.length() < nextVersion.length()) {
 142  0
                             if (nextVersion.startsWith(currentVersion) || "-".equals(currentVersion)) {
 143  0
                                 dependency.getIdentifiers().remove(currentId);
 144  
                             }
 145  
                         } else {
 146  0
                             if (currentVersion.startsWith(nextVersion) || "-".equals(nextVersion)) {
 147  0
                                 dependency.getIdentifiers().remove(nextId);
 148  
                             }
 149  
                         }
 150  
                     }
 151  
                 }
 152  0
             }
 153  0
         }
 154  0
     }
 155  
     /**
 156  
      * Regex to identify core java libraries and a few other commonly misidentified ones.
 157  
      */
 158  0
     public static final Pattern CORE_JAVA = Pattern.compile("^cpe:/a:(sun|oracle|ibm):(j2[ems]e|"
 159  
             + "java(_platfrom_micro_edition|_runtime_environment|_se|virtual_machine|se_development_kit|fx)?|"
 160  
             + "jdk|jre|jsf|jsse)($|:.*)");
 161  
     /**
 162  
      * Regex to identify core java library files. This is currently incomplete.
 163  
      */
 164  0
     public static final Pattern CORE_FILES = Pattern.compile("^((alt[-])?rt|jsf[-].*|jsse|jfxrt|jfr|jce|javaws|deploy|charsets)\\.jar$");
 165  
 
 166  
     /**
 167  
      * Removes any CPE entries for the JDK/JRE unless the filename ends with rt.jar
 168  
      *
 169  
      * @param dependency the dependency to remove JRE CPEs from
 170  
      */
 171  
     private void removeJreEntries(Dependency dependency) {
 172  0
         final Set<Identifier> identifiers = dependency.getIdentifiers();
 173  0
         final Iterator<Identifier> itr = identifiers.iterator();
 174  0
         while (itr.hasNext()) {
 175  0
             final Identifier i = itr.next();
 176  0
             final Matcher coreCPE = CORE_JAVA.matcher(i.getValue());
 177  0
             final Matcher coreFiles = CORE_FILES.matcher(dependency.getFileName());
 178  0
             if (coreCPE.matches() && !coreFiles.matches()) {
 179  0
                 itr.remove();
 180  
             }
 181  
 
 182  
             //replacecd with the regex above.
 183  
             //            if (("cpe:/a:sun:java".equals(i.getValue())
 184  
             //                    || "cpe:/a:oracle:java".equals(i.getValue())
 185  
             //                    || "cpe:/a:ibm:java".equals(i.getValue())
 186  
             //                    || "cpe:/a:sun:j2se".equals(i.getValue())
 187  
             //                    || "cpe:/a:oracle:j2se".equals(i.getValue())
 188  
             //                    || i.getValue().startsWith("cpe:/a:sun:java:")
 189  
             //                    || i.getValue().startsWith("cpe:/a:sun:j2se:")
 190  
             //                    || i.getValue().startsWith("cpe:/a:sun:java:jre")
 191  
             //                    || i.getValue().startsWith("cpe:/a:sun:java:jdk")
 192  
             //                    || i.getValue().startsWith("cpe:/a:sun:java_se")
 193  
             //                    || i.getValue().startsWith("cpe:/a:oracle:java_se")
 194  
             //                    || i.getValue().startsWith("cpe:/a:oracle:java:")
 195  
             //                    || i.getValue().startsWith("cpe:/a:oracle:j2se:")
 196  
             //                    || i.getValue().startsWith("cpe:/a:oracle:jre")
 197  
             //                    || i.getValue().startsWith("cpe:/a:oracle:jdk")
 198  
             //                    || i.getValue().startsWith("cpe:/a:ibm:java:"))
 199  
             //                    && !dependency.getFileName().toLowerCase().endsWith("rt.jar")) {
 200  
             //                itr.remove();
 201  
             //            }
 202  0
         }
 203  0
     }
 204  
 
 205  
     /**
 206  
      * Parses a CPE string into an IndexEntry.
 207  
      *
 208  
      * @param type the type of identifier
 209  
      * @param value the cpe identifier to parse
 210  
      * @return an VulnerableSoftware object constructed from the identifier
 211  
      */
 212  
     private VulnerableSoftware parseCpe(String type, String value) {
 213  0
         if (!"cpe".equals(type)) {
 214  0
             return null;
 215  
         }
 216  0
         final VulnerableSoftware cpe = new VulnerableSoftware();
 217  
         try {
 218  0
             cpe.parseName(value);
 219  0
         } catch (UnsupportedEncodingException ex) {
 220  0
             Logger.getLogger(FalsePositiveAnalyzer.class.getName()).log(Level.FINEST, null, ex);
 221  0
             return null;
 222  0
         }
 223  0
         return cpe;
 224  
     }
 225  
 
 226  
     /**
 227  
      * Removes bad CPE matches for a dependency. Unfortunately, right now these are hard-coded patches for specific
 228  
      * problems identified when testing this on a LARGE volume of jar files.
 229  
      *
 230  
      * @param dependency the dependency to analyze
 231  
      */
 232  
     private void removeBadMatches(Dependency dependency) {
 233  0
         final Set<Identifier> identifiers = dependency.getIdentifiers();
 234  0
         final Iterator<Identifier> itr = identifiers.iterator();
 235  
 
 236  
         /* TODO - can we utilize the pom's groupid and artifactId to filter??? most of
 237  
          * these are due to low quality data.  Other idea would be to say any CPE
 238  
          * found based on LOW confidence evidence should have a different CPE type? (this
 239  
          * might be a better solution then just removing the URL for "best-guess" matches).
 240  
          */
 241  
         //Set<Evidence> groupId = dependency.getVendorEvidence().getEvidence("pom", "groupid");
 242  
         //Set<Evidence> artifactId = dependency.getVendorEvidence().getEvidence("pom", "artifactid");
 243  0
         while (itr.hasNext()) {
 244  0
             final Identifier i = itr.next();
 245  
             //TODO move this startswith expression to a configuration file?
 246  0
             if ("cpe".equals(i.getType())) {
 247  0
                 if ((i.getValue().matches(".*c\\+\\+.*")
 248  
                         || i.getValue().startsWith("cpe:/a:jquery:jquery")
 249  
                         || i.getValue().startsWith("cpe:/a:prototypejs:prototype")
 250  
                         || i.getValue().startsWith("cpe:/a:yahoo:yui")
 251  
                         || i.getValue().startsWith("cpe:/a:file:file")
 252  
                         || i.getValue().startsWith("cpe:/a:mozilla:mozilla")
 253  
                         || i.getValue().startsWith("cpe:/a:cvs:cvs")
 254  
                         || i.getValue().startsWith("cpe:/a:ftp:ftp")
 255  
                         || i.getValue().startsWith("cpe:/a:ssh:ssh"))
 256  
                         && (dependency.getFileName().toLowerCase().endsWith(".jar")
 257  
                         || dependency.getFileName().toLowerCase().endsWith("pom.xml"))) {
 258  0
                     itr.remove();
 259  0
                 } else if (i.getValue().startsWith("cpe:/a:apache:maven")
 260  
                         && !dependency.getFileName().toLowerCase().matches("maven-core-[\\d\\.]+\\.jar")) {
 261  0
                     itr.remove();
 262  0
                 } else if (i.getValue().startsWith("cpe:/a:m-core:m-core")
 263  
                         && !dependency.getEvidenceUsed().containsUsedString("m-core")) {
 264  0
                     itr.remove();
 265  0
                 } else if (i.getValue().startsWith("cpe:/a:jboss:jboss")
 266  
                         && !dependency.getFileName().toLowerCase().matches("jboss-[\\d\\.]+(GA)?\\.jar")) {
 267  0
                     itr.remove();
 268  
                 }
 269  
             }
 270  0
         }
 271  0
     }
 272  
 
 273  
     /**
 274  
      * Removes CPE matches for the wrong version of a dependency. Currently, this only covers Axis 1 & 2.
 275  
      *
 276  
      * @param dependency the dependency to analyze
 277  
      */
 278  
     private void removeWrongVersionMatches(Dependency dependency) {
 279  0
         final Set<Identifier> identifiers = dependency.getIdentifiers();
 280  0
         final Iterator<Identifier> itr = identifiers.iterator();
 281  
 
 282  0
         final String fileName = dependency.getFileName();
 283  0
         if (fileName != null && fileName.contains("axis2")) {
 284  0
             while (itr.hasNext()) {
 285  0
                 final Identifier i = itr.next();
 286  0
                 if ("cpe".equals(i.getType())) {
 287  0
                     final String cpe = i.getValue();
 288  0
                     if (cpe != null && (cpe.startsWith("cpe:/a:apache:axis:") || "cpe:/a:apache:axis".equals(cpe))) {
 289  0
                         itr.remove();
 290  
                     }
 291  
                 }
 292  0
             }
 293  0
         } else if (fileName != null && fileName.contains("axis")) {
 294  0
             while (itr.hasNext()) {
 295  0
                 final Identifier i = itr.next();
 296  0
                 if ("cpe".equals(i.getType())) {
 297  0
                     final String cpe = i.getValue();
 298  0
                     if (cpe != null && (cpe.startsWith("cpe:/a:apache:axis2:") || "cpe:/a:apache:axis2".equals(cpe))) {
 299  0
                         itr.remove();
 300  
                     }
 301  
                 }
 302  0
             }
 303  
         }
 304  0
     }
 305  
 
 306  
     /**
 307  
      * There are some known CPE entries, specifically regarding sun and oracle products due to the acquisition and
 308  
      * changes in product names, that based on given evidence we can add the related CPE entries to ensure a complete
 309  
      * list of CVE entries.
 310  
      *
 311  
      * @param dependency the dependency being analyzed
 312  
      */
 313  
     private void addFalseNegativeCPEs(Dependency dependency) {
 314  0
         final Iterator<Identifier> itr = dependency.getIdentifiers().iterator();
 315  0
         while (itr.hasNext()) {
 316  0
             final Identifier i = itr.next();
 317  0
             if ("cpe".equals(i.getType()) && i.getValue() != null
 318  
                     && (i.getValue().startsWith("cpe:/a:oracle:opensso:")
 319  
                     || i.getValue().startsWith("cpe:/a:oracle:opensso_enterprise:")
 320  
                     || i.getValue().startsWith("cpe:/a:sun:opensso_enterprise:")
 321  
                     || i.getValue().startsWith("cpe:/a:sun:opensso:"))) {
 322  0
                 final String newCpe = String.format("cpe:/a:sun:opensso_enterprise:%s", i.getValue().substring(22));
 323  0
                 final String newCpe2 = String.format("cpe:/a:oracle:opensso_enterprise:%s", i.getValue().substring(22));
 324  0
                 final String newCpe3 = String.format("cpe:/a:sun:opensso:%s", i.getValue().substring(22));
 325  0
                 final String newCpe4 = String.format("cpe:/a:oracle:opensso:%s", i.getValue().substring(22));
 326  
                 try {
 327  0
                     dependency.addIdentifier("cpe",
 328  
                             newCpe,
 329  
                             String.format("http://web.nvd.nist.gov/view/vuln/search?cpe=%s", URLEncoder.encode(newCpe, "UTF-8")));
 330  0
                     dependency.addIdentifier("cpe",
 331  
                             newCpe2,
 332  
                             String.format("http://web.nvd.nist.gov/view/vuln/search?cpe=%s", URLEncoder.encode(newCpe2, "UTF-8")));
 333  0
                     dependency.addIdentifier("cpe",
 334  
                             newCpe3,
 335  
                             String.format("http://web.nvd.nist.gov/view/vuln/search?cpe=%s", URLEncoder.encode(newCpe3, "UTF-8")));
 336  0
                     dependency.addIdentifier("cpe",
 337  
                             newCpe4,
 338  
                             String.format("http://web.nvd.nist.gov/view/vuln/search?cpe=%s", URLEncoder.encode(newCpe4, "UTF-8")));
 339  0
                 } catch (UnsupportedEncodingException ex) {
 340  0
                     Logger.getLogger(FalsePositiveAnalyzer.class
 341  
                             .getName()).log(Level.FINE, null, ex);
 342  0
                 }
 343  
             }
 344  0
         }
 345  0
     }
 346  
 }