CVE-2023-40828 (published 2023-08-28T22:15Z)

An issue in pf4j pf4j v.3.9.0 and before allows a remote attacker to obtain sensitive information and execute arbitrary code via the expandIfZip method in the extract function.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/pf4j/pf4j
commit_details: repo_url: https://github.com/pf4j/pf4j
author: Decebal Suiu
author_date: 2023-08-16 21:00:59+03:00
msg: Add security checks to prevent directory traversal when decompressing (#538)
repository: repo_url: https://github.com/pf4j/pf4j
repo_name: pf4j/pf4j
description: Plugin Framework for Java (PF4J)
repo_language: Java
owner: pf4j
file_changes: filename: Unzip.java
change_type: ModificationType.MODIFY
diff: @@ -15,17 +15,17 @@
  */
 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());
 

programming_language: Java
filename: UnzipTest.java
change_type: ModificationType.ADD
diff: @@ -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;
+    }
+
+}

programming_language: Java