From a1a9602509c908b69f0b6d05588ef0ba7f388b31 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 26 Jun 2016 07:31:17 -0400 Subject: [PATCH] added support for fully executable jar files per issue #454 --- .../analyzer/ArchiveAnalyzer.java | 103 +++++++++++++++--- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java index 4bf4755e5..0caf06280 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java @@ -31,6 +31,7 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.collections.buffer.CircularFifoBuffer; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveInputStream; @@ -58,8 +59,8 @@ import org.slf4j.LoggerFactory; /** *

- * An analyzer that extracts files from archives and ensures any supported files contained within the archive are added to the - * dependency list.

+ * An analyzer that extracts files from archives and ensures any supported files + * contained within the archive are added to the dependency list.

* * @author Jeremy Long */ @@ -70,7 +71,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { */ private static final Logger LOGGER = LoggerFactory.getLogger(ArchiveAnalyzer.class); /** - * The count of directories created during analysis. This is used for creating temporary directories. + * The count of directories created during analysis. This is used for + * creating temporary directories. */ private static int dirCount = 0; /** @@ -78,7 +80,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { */ private File tempFileLocation = null; /** - * The max scan depth that the analyzer will recursively extract nested archives. + * The max scan depth that the analyzer will recursively extract nested + * archives. */ private static final int MAX_SCAN_DEPTH = Settings.getInt("archive.scan.depth", 3); /** @@ -100,13 +103,15 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { */ private static final Set ZIPPABLES = newHashSet("zip", "ear", "war", "jar", "sar", "apk", "nupkg"); /** - * The set of file extensions supported by this analyzer. Note for developers, any additions to this list will need to be - * explicitly handled in {@link #extractFiles(File, File, Engine)}. + * The set of file extensions supported by this analyzer. Note for + * developers, any additions to this list will need to be explicitly handled + * in {@link #extractFiles(File, File, Engine)}. */ private static final Set EXTENSIONS = newHashSet("tar", "gz", "tgz", "bz2", "tbz2"); /** - * Detects files with extensions to remove from the engine's collection of dependencies. + * Detects files with extensions to remove from the engine's collection of + * dependencies. */ private static final FileFilter REMOVE_FROM_ANALYSIS = FileFilterBuilder.newInstance().addExtensions("zip", "tar", "gz", "tgz", "bz2", "tbz2") .build(); @@ -157,7 +162,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { // /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ @@ -169,7 +175,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { /** * The initialize method does nothing for this Analyzer. * - * @throws Exception is thrown if there is an exception deleting or creating temporary files + * @throws Exception is thrown if there is an exception deleting or creating + * temporary files */ @Override public void initializeFileTypeAnalyzer() throws Exception { @@ -186,9 +193,11 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { } /** - * The close method deletes any temporary files and directories created during analysis. + * The close method deletes any temporary files and directories created + * during analysis. * - * @throws Exception thrown if there is an exception deleting temporary files + * @throws Exception thrown if there is an exception deleting temporary + * files */ @Override public void close() throws Exception { @@ -205,8 +214,9 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, scanned, - * and added to the list of dependencies within the engine. + * Analyzes a given dependency. If the dependency is an archive, such as a + * WAR or EAR, the contents are extracted, scanned, and added to the list of + * dependencies within the engine. * * @param dependency the dependency to analyze * @param engine the engine scanning @@ -249,7 +259,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { } /** - * If a zip file was identified as a possible JAR, this method will add the zip to the list of dependencies. + * If a zip file was identified as a possible JAR, this method will add the + * zip to the list of dependencies. * * @param dependency the zip file * @param engine the engine @@ -349,7 +360,9 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { final String archiveExt = FileUtils.getFileExtension(archive.getName()).toLowerCase(); try { if (ZIPPABLES.contains(archiveExt)) { - extractArchive(new ZipArchiveInputStream(new BufferedInputStream(fis)), destination, engine); + BufferedInputStream in = new BufferedInputStream(fis); + ensureReadableJar(archiveExt, in); + extractArchive(new ZipArchiveInputStream(in), destination, engine); } else if ("tar".equals(archiveExt)) { extractArchive(new TarArchiveInputStream(new BufferedInputStream(fis)), destination, engine); } else if ("gz".equals(archiveExt) || "tgz".equals(archiveExt)) { @@ -377,13 +390,65 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { } } + /** + * Checks if the file being scanned is a JAR that begins with '#!/bin' which + * indicates it is a fully executable jar. If a fully executable JAR is identified + * the input stream will be advanced to the start of the actual JAR file ( + * skipping the script). + * + * @see + * Installing + * Spring Boot Applications + * @param archiveExt the file extension + * @param in the input stream + * @throws IOException thrown if there is an error reading the stream + */ + private void ensureReadableJar(final String archiveExt, BufferedInputStream in) throws IOException { + if ("jar".equals(archiveExt) && in.markSupported()) { + in.mark(7); + byte[] b = new byte[7]; + in.read(b); + if (b[0] == '#' + && b[1] == '!' + && b[2] == '/' + && b[3] == 'b' + && b[4] == 'i' + && b[5] == 'n' + && b[6] == '/') { + boolean stillLooking = true; + int chr; + CircularFifoBuffer buf = new CircularFifoBuffer(6); + while (stillLooking && (chr = in.read()) != -1) { + if (chr == '\n' || chr == '\r') { + if ('e' == (Integer) buf.remove() + && 'x' == (Integer) buf.remove() + && 'i' == (Integer) buf.remove() + && 't' == (Integer) buf.remove() + && ' ' == (Integer) buf.remove() + && '0' == (Integer) buf.remove()) { + in.mark(2); + if (in.read() == 'P' && in.read() == 'K') { + stillLooking = false; + in.reset(); + } + } + } + buf.add(chr); + } + } else { + in.reset(); + } + } + } + /** * Extracts files from an archive. * * @param input the archive to extract files from * @param destination the location to write the files too * @param engine the dependency-check engine - * @throws ArchiveExtractionException thrown if there is an exception extracting files from the archive + * @throws ArchiveExtractionException thrown if there is an exception + * extracting files from the archive */ private void extractArchive(ArchiveInputStream input, File destination, Engine engine) throws ArchiveExtractionException { ArchiveEntry entry; @@ -442,7 +507,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { * * @param inputStream the compressed file * @param outputFile the location to write the decompressed file - * @throws ArchiveExtractionException thrown if there is an exception decompressing the file + * @throws ArchiveExtractionException thrown if there is an exception + * decompressing the file */ private void decompressFile(CompressorInputStream inputStream, File outputFile) throws ArchiveExtractionException { LOGGER.debug("Decompressing '{}'", outputFile.getPath()); @@ -462,7 +528,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Close the given {@link Closeable} instance, ignoring nulls, and logging any thrown {@link IOException}. + * Close the given {@link Closeable} instance, ignoring nulls, and logging + * any thrown {@link IOException}. * * @param closeable to be closed */