diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java index 648c32cd3..5d33a9b66 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.cli.ParseException; +import org.apache.commons.lang.StringUtils; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; @@ -354,6 +355,10 @@ public class App { if (pathToMono != null && !pathToMono.isEmpty()) { Settings.setString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono); } + String pathToBundleAudit = cli.getPathToBundleAudit(); + if (!StringUtils.isEmpty(pathToBundleAudit)){ + Settings.setString(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH, pathToBundleAudit); + } if (cveBase12 != null && !cveBase12.isEmpty()) { Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveBase12); Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveBase20); diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java index 7101fa389..dbd48215f 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -328,6 +328,10 @@ public final class CliParser { .withDescription("The path to Mono for .NET Assembly analysis on non-windows systems.") .create(); + final Option pathToBundleAudit = OptionBuilder.withArgName("path").hasArg() + .withLongOpt(ARGUMENT.PATH_TO_BUNDLE_AUDIT) + .withDescription("The path to bundle-audit for Gem bundle analysis.").create(); + final Option connectionTimeout = OptionBuilder.withArgName("timeout").hasArg().withLongOpt(ARGUMENT.CONNECTION_TIMEOUT) .withDescription("The connection timeout (in milliseconds) to use when downloading resources.") .create(ARGUMENT.CONNECTION_TIMEOUT_SHORT); @@ -426,7 +430,8 @@ public final class CliParser { .addOption(nexusUrl) .addOption(nexusUsesProxy) .addOption(additionalZipExtensions) - .addOption(pathToMono); + .addOption(pathToMono) + .addOption(pathToBundleAudit); } /** @@ -690,6 +695,15 @@ public final class CliParser { return line.getOptionValue(ARGUMENT.PATH_TO_MONO); } + /** + * Returns the path to bundle-audit for Ruby bundle analysis. + * + * @return the path to Mono + */ + public String getPathToBundleAudit() { + return line.getOptionValue(ARGUMENT.PATH_TO_BUNDLE_AUDIT); + } + /** * Returns the output format specified on the command line. Defaults to HTML if no format was specified. * @@ -1160,5 +1174,9 @@ public final class CliParser { * Exclude path argument. */ public static final String EXCLUDE = "exclude"; + /** + * The CLI argument name for setting the path to bundle-audit for Ruby bundle analysis. + */ + public static final String PATH_TO_BUNDLE_AUDIT = "bundleAudit"; } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java new file mode 100644 index 000000000..10e0dcce1 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java @@ -0,0 +1,187 @@ +/* + * 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) 2015 Institute for Defense Analyses. 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.utils.FileFilterBuilder; +import org.owasp.dependencycheck.utils.Settings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Used to analyze Ruby Bundler Gemspec.lock files utilizing the 3rd party bundle-audit tool. + * + * @author Dale Visser + */ +public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { + + private static final Logger LOGGER = LoggerFactory.getLogger(RubyBundleAuditAnalyzer.class); + + /** + * The name of the analyzer. + */ + private static final String ANALYZER_NAME = "Ruby Bundle Audit Analyzer"; + + /** + * The phase that this analyzer is intended to run in. + */ + private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION; + + private static final FileFilter FILTER = + FileFilterBuilder.newInstance().addFilenames("Gemfile.lock").build(); + + /** + * @return a filter that accepts files named Rakefile or matching the glob pattern, *.gemspec + */ + @Override + protected FileFilter getFileFilter() { + return FILTER; + } + + /** + * Launch bundle-audit. + * + * @return a handle to the process + */ + private Process launchBundleAudit(File folder) throws AnalysisException { + if (!folder.isDirectory()){ + throw new AnalysisException(String.format("%s should have been a directory.", folder.getAbsolutePath())); + } + final List args = new ArrayList(); + final String bundleAuditPath = Settings.getString(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH); + args.add(null == bundleAuditPath ? "bundle-audit" : bundleAuditPath); + args.add("check"); + final ProcessBuilder builder = new ProcessBuilder(args); + builder.directory(folder); + try { + return builder.start(); + } catch (IOException ioe) { + throw new AnalysisException("bundle-audit failure", ioe); + } + } + + /** + * Initialize the analyzer. In this case, extract GrokAssembly.exe to a temporary location. + * + * @throws Exception if anything goes wrong + */ + @Override + public void initializeFileTypeAnalyzer() throws Exception { + // Now, need to see if bundle-audit actually runs from this location. + try { + Process process = launchBundleAudit(Settings.getTempDirectory()); + int exitValue = process.waitFor(); + if (0 == exitValue) { + LOGGER.warn("Unexpected exit code from bundle-audit process. Disabling %s: %d", ANALYZER_NAME, exitValue); + setEnabled(false); + } else { + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8")); + if (!reader.ready()) { + LOGGER.warn("Bundle-audit error stream unexpectedly not ready. Disabling " + ANALYZER_NAME); + setEnabled(false); + } else { + final String line = reader.readLine(); + if (!line.contains("Errno::ENOENT")) { + LOGGER.warn("Unexpected bundle-audit output. Disabling %s: %s", ANALYZER_NAME, line); + setEnabled(false); + } + } + } finally { + if (null != reader) { + reader.close(); + } + } + } + } catch (AnalysisException ae) { + LOGGER.warn("Exception while trying to launch bundle-audit. Disabling " + ANALYZER_NAME, ae.getCause()); + setEnabled(false); + } + } + + /** + * 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; + } + + /** + * Returns the key used in the properties file to reference the analyzer's enabled property. + * + * @return the analyzer's enabled property setting key + */ + @Override + protected String getAnalyzerEnabledSettingKey() { + return Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED; + } + + @Override + protected void analyzeFileType(Dependency dependency, Engine engine) + throws AnalysisException { + final File parentFile = dependency.getActualFile().getParentFile(); + final Process process = launchBundleAudit(parentFile); + try { + process.waitFor(); + } catch (InterruptedException ie) { + throw new AnalysisException("bundle-audit process interrupted", ie); + } + BufferedReader rdr = null; + try { + rdr = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); + while (rdr.ready()) { + final String nextLine = rdr.readLine(); + if (null == nextLine) { + break; + } + LOGGER.info(String.format("bundle-audit (%s): %s", parentFile.getName(), nextLine)); + } + } catch (IOException ioe) { + LOGGER.warn("bundle-audit failure", ioe); + } finally { + if (null != rdr) { + try { + rdr.close(); + } catch (IOException ioe) { + LOGGER.warn("bundle-audit close failure", ioe); + } + } + } + + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java index 5112912e7..6f29e7bb2 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java @@ -49,11 +49,12 @@ public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer { */ private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION; + private static final String GEMSPEC = "gemspec"; + private static final FileFilter FILTER = - FileFilterBuilder.newInstance().addExtensions("gemspec").addFilenames("Rakefile").build(); + FileFilterBuilder.newInstance().addExtensions(GEMSPEC).addFilenames("Rakefile").build(); private static final String EMAIL = "email"; - private static final String GEMSPEC = "gemspec"; /** * @return a filter that accepts files named Rakefile or matching the glob pattern, *.gemspec diff --git a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer index 659c104b5..a4d0f78c8 100644 --- a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer +++ b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer @@ -17,4 +17,5 @@ org.owasp.dependencycheck.analyzer.PythonPackageAnalyzer org.owasp.dependencycheck.analyzer.AutoconfAnalyzer org.owasp.dependencycheck.analyzer.OpenSSLAnalyzer org.owasp.dependencycheck.analyzer.CMakeAnalyzer -org.owasp.dependencycheck.analyzer.RubyGemspecAnalyzer \ No newline at end of file +org.owasp.dependencycheck.analyzer.RubyGemspecAnalyzer +org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer \ No newline at end of file diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java index 482e19753..ec99710d5 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -246,6 +246,10 @@ public final class Settings { * The path to mono, if available. */ public static final String ANALYZER_ASSEMBLY_MONO_PATH = "analyzer.assembly.mono.path"; + /** + * The path to bundle-audit, if available. + */ + public static final String ANALYZER_BUNDLE_AUDIT_PATH = "analyzer.bundle.audit.path"; /** * The additional configured zip file extensions, if available. */