]> source.dussan.org Git - pf4j.git/commitdiff
Add security checks to prevent directory traversal when decompressing #526 538/head
authorDecebal Suiu <decebal.suiu@gmail.com>
Wed, 16 Aug 2023 17:55:25 +0000 (20:55 +0300)
committerDecebal Suiu <decebal.suiu@gmail.com>
Wed, 16 Aug 2023 17:55:25 +0000 (20:55 +0300)
pf4j/src/main/java/org/pf4j/util/Unzip.java
pf4j/src/test/java/org/pf4j/util/UnzipTest.java [new file with mode: 0644]

index 198cf7d8a5066b3d9cc94ece6b7d15e211699057..a78cf83e45093b92b8cd2d3cda457b85269310bb 100644 (file)
  */
 package org.pf4j.util;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
 import java.util.zip.ZipInputStream;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /**
  * This class extracts the content of the plugin zip into a directory.
  * It's a class for only the internal use.
@@ -75,11 +75,17 @@ public class Unzip {
             FileUtils.delete(destination.toPath());
         }
 
+        String destinationCanonicalPath = destination.getCanonicalPath();
         try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source))) {
             ZipEntry zipEntry;
             while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                 File file = new File(destination, zipEntry.getName());
 
+                String fileCanonicalPath = file.getCanonicalPath();
+                if (!fileCanonicalPath.startsWith(destinationCanonicalPath)) {
+                    throw new ZipException("The file "+ zipEntry.getName() + " is trying to leave the target output directory of "+ destination);
+                }
+
                 // create intermediary directories - sometimes zip don't add them
                 File dir = new File(file.getParent());
 
diff --git a/pf4j/src/test/java/org/pf4j/util/UnzipTest.java b/pf4j/src/test/java/org/pf4j/util/UnzipTest.java
new file mode 100644 (file)
index 0000000..202e1b0
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012-present the original author or authors.
+ *
+ * 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.
+ */
+package org.pf4j.util;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipOutputStream;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class UnzipTest {
+
+    @Test
+    public void zipSlip() throws IOException {
+        File zipFile = createMaliciousZipFile();
+        Path destination = Files.createTempDirectory("zipSlip");
+
+        Unzip unzip = new Unzip();
+        unzip.setSource(zipFile);
+        unzip.setDestination(destination.toFile());
+
+        Exception exception = assertThrows(ZipException.class, unzip::extract);
+        assertTrue(exception.getMessage().contains("is trying to leave the target output directory"));
+    }
+
+    private File createMaliciousZipFile() throws IOException {
+        File zipFile = File.createTempFile("malicious", ".zip");
+        String maliciousFileName = "../malicious.sh";
+        try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
+            ZipEntry entry = new ZipEntry(maliciousFileName);
+            zipOutputStream.putNextEntry(entry);
+            zipOutputStream.write("Malicious content".getBytes());
+            zipOutputStream.closeEntry();
+        }
+
+        return zipFile;
+    }
+
+}