javasecuritysonarqubezipapache-commons-compress

Apache Commons Compress as solution to Zip Bomb


Java Code is implemented to uncompress zip file using java.util.zip library. Sonarqube reports Security Hotspots vulnerability as prone to "Zip Bomb" security issue with message "Make sure that expanding this archive file is safe here" in the line "ZipEntry entry = zipIn.getNextEntry();".

As a solution, trying to use Apache Commons Compress version 1.21 library which handles Zip Bomb starting from version 1.17. For testing, downloaded a Zip Bomb Vulnerable zip file from here .

But this zip file gets uncompressed without any error/exception. What is wrong with this code mentioned under heading "Implementation using Apache Commons Compress Library"?

<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-compress</artifactId>
 <version>1.21</version>
</dependency>

Zip Bomb Vulnerable code

private void unzipNormal(String zipFilePath, String destDirectory) {
    try {
        File destDir = new File(destDirectory);
        if(!destDir.exists()) {
            destDir.mkdir();
        }

        try(ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath))) {
            ZipEntry entry = zipIn.getNextEntry();
            while(entry != null) {
                String filePath = destDirectory + File.separator + entry.getName();
                if(!entry.isDirectory()) {
                    extractFile(zipIn, filePath);
                } else {
                    File dir = new File(filePath);
                    dir.mkdir();
                }
                zipIn.closeEntry();
                entry = zipIn.getNextEntry();
            }
            zipIn.close();
        }

    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
    try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
        byte[] bytesIn = new byte[4096];
        int read = 0;
        while((read = zipIn.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }
        bos.close();
    } catch (Exception ex) {
        ex.printStackTrace();
        throw ex;
    }
}

Implementation using Apache Commons Compress Library

private void unzip(String srcZipFile, String destFolder) throws IOException {

        Path filePath = Paths.get(srcZipFile);

        try(InputStream inputStream = Files.newInputStream(filePath);
            ZipArchiveInputStream i = new ZipArchiveInputStream(inputStream)
        ) {
            System.out.println("Begin..");
            ArchiveEntry entry = null;
            while((entry = i.getNextEntry()) != null) {
                if(!i.canReadEntryData(entry)) {
                    System.out.println("Continue..");
                    continue;
                }

                Path path = Paths.get(destFolder, entry.getName());
                File f = path.toFile();
                if(entry.isDirectory()) {
                    if (!f.isDirectory() && !f.mkdirs()) {
                        throw new IOException("failed to create directory " + f);
                    }
                } else {
                    File parent = f.getParentFile();
                    if(!parent.isDirectory() && !parent.mkdirs()) {
                        throw new IOException("failed to create directory " + parent);
                    }
                    try (OutputStream o = Files.newOutputStream(f.toPath())) {
                        IOUtils.copy(i, o);
                    }
                }

            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
}

Solution

  • Able to frame the solution with answer from here. Solution uses the Apache POI utility class and hence updated Maven pom.xml with Apache POI dependency.

    Maven pom.xml

    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>4.1.2</version>
    </dependency>
    

    Solution

    public static void main(String[] args) throws IOException {
        Main main = new Main();
        String srcZipFile = "/Users/user1/Documents/ZipBomb/zbsm.zip";
        String destFolder = "/Users/user1/Documents/ZipBomb/unzipApacheLARGE/";
        main.handle(srcZipFile, destFolder);
    }
    
    
    private void handle(String srcZipFile, String destFolder) throws IOException {
        ZipSecureFile zipSecureFile = new ZipSecureFile(srcZipFile);
    
        Enumeration<? extends ZipArchiveEntry> entries = zipSecureFile.getEntries();
        while (entries.hasMoreElements()) {
            ZipArchiveEntry entry = entries.nextElement();
    
            String name = entry.getName();
            try {
                System.out.println("current file: " + name);
                try (InputStream in = zipSecureFile.getInputStream(entry)) {
                    Path path = Paths.get(destFolder, entry.getName());
                    File f = path.toFile();
                    if(entry.isDirectory()) {
                        if (!f.isDirectory() && !f.mkdirs()) {
                            throw new IOException("failed to create directory " + f);
                        }
                    } else {
                        File parent = f.getParentFile();
                        if(!parent.isDirectory() && !parent.mkdirs()) {
                            throw new IOException("failed to create directory " + parent);
                        }
                        try (OutputStream out = Files.newOutputStream(f.toPath())) {
                            org.apache.poi.util.IOUtils.copy(in, out);
                        }
                    }
    
    
                }
            } catch (Exception e) {
                throw new IOException("While handling entry " + name, e);
            }
            System.out.print("Completed Successfully!!");
        }
    }