CVE-2018-1002201 (published 2018-07-25T17:29Z)

zt-zip before 1.13 is vulnerable to directory traversal, allowing attackers to write to arbitrary files via a ../ (dot dot slash) in a Zip archive entry that is mishandled during extraction. This vulnerability is also known as 'Zip-Slip'.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:N/I:P/A:P)
CVSS v3 base score: 5.5, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/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/zeroturnaround/zt-zip
commit_details: repo_url: https://github.com/zeroturnaround/zt-zip
author: Toomas Romer
author_date: 2018-04-26 17:52:37+03:00
msg: Fixed potential security vulnerability reported by Snyk Security Research Team

This is an arbitrary file write vulnerability, that can be achieved using a specially crafted zip archive, that holds path traversal filenames. So when the filename gets concatenated to the target extraction directory, the final path ends up outside of the target folder.
repository: repo_url: https://github.com/zeroturnaround/zt-zip
repo_name: zeroturnaround/zt-zip
description: ZeroTurnaround ZIP Library
repo_language: Java
owner: zeroturnaround
file_changes: filename: ZipUtil.java
change_type: ModificationType.MODIFY
diff: @@ -1150,6 +1150,15 @@ public final class ZipUtil {
       String name = mapper.map(zipEntry.getName());
       if (name != null) {
         File file = new File(outputDir, name);
+
+        /* If we see the relative traversal string of ".." we need to make sure
+         * that the outputdir + name doesn't leave the outputdir. See
+         * DirectoryTraversalMaliciousTest for details.
+         */
+        if (name.indexOf("..") != -1 && !file.getCanonicalPath().startsWith(outputDir.getCanonicalPath())) {
+          throw new ZipException("The file "+name+" is trying to leave the target output directory of "+outputDir+". Ignoring this file.");
+        }
+
         if (zipEntry.isDirectory()) {
           FileUtils.forceMkdir(file);
         }
@@ -1218,11 +1227,29 @@ public final class ZipUtil {
             parentDirectory = file;
           }
           File destFile = new File(parentDirectory, dirs[dirs.length - 1]);
+
+          /* If we see the relative traversal string of ".." we need to make sure
+           * that the outputdir + name doesn't leave the outputdir. See
+           * DirectoryTraversalMaliciousTest for details.
+           */
+          if (name.indexOf("..") != -1 && !destFile.getCanonicalPath().startsWith(outputDir.getCanonicalPath())) {
+            throw new ZipException("The file "+name+" is trying to leave the target output directory of "+outputDir+". Ignoring this file.");
+          }
+
           FileUtils.copy(in, destFile);
         }
         // it could be that there are just top level files that the unpacker is used for
         else {
           File destFile = new File(outputDir, name);
+
+          /* If we see the relative traversal string of ".." we need to make sure
+           * that the outputdir + name doesn't leave the outputdir. See
+           * DirectoryTraversalMaliciousTest for details.
+           */
+          if (name.indexOf("..") != -1 && !destFile.getCanonicalPath().startsWith(outputDir.getCanonicalPath())) {
+            throw new ZipException("The file "+name+" is trying to leave the target output directory of "+outputDir+". Ignoring this file.");
+          }
+
           FileUtils.copy(in, destFile);
         }
       }
@@ -1258,6 +1285,15 @@ public final class ZipUtil {
       String name = mapper.map(getUnrootedName(root, zipEntry.getName()));
       if (name != null) {
         File file = new File(outputDir, name);
+
+        /* If we see the relative traversal string of ".." we need to make sure
+         * that the outputdir + name doesn't leave the outputdir. See
+         * DirectoryTraversalMaliciousTest for details.
+         */
+        if (name.indexOf("..") != -1 && !file.getCanonicalPath().startsWith(outputDir.getCanonicalPath())) {
+          throw new ZipException("The file "+name+" is trying to leave the target output directory of "+outputDir+". Ignoring this file.");
+        }
+
         if (zipEntry.isDirectory()) {
           FileUtils.forceMkdir(file);
         }

programming_language: Java
filename: DirectoryTraversalMaliciousTest.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,61 @@
+package org.zeroturnaround.zip;
+
+/**
+ *    Copyright (C) 2012 ZeroTurnaround LLC <support@zeroturnaround.com>
+ *
+ *    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.
+ */
+import java.io.File;
+
+import junit.framework.TestCase;
+
+public class DirectoryTraversalMaliciousTest extends TestCase {
+  /*
+   * This is the contents of the file. There is one evil file that tries to get out of the
+   * target.
+   *
+   * $ unzip -t zip-slip.zip
+   * Archive: zip-slip.zip
+   * testing: good.txt OK
+   * testing: ../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../tmp/evil.txt OK
+   * No errors detected in compressed data of zip-slip.zip.
+   */
+  private static final File badFile = new File("src/test/resources/zip-malicious-traversal.zip");
+  private static final File badFileBackslashes = new File("src/test/resources/zip-malicious-traversal-backslashes.zip");
+
+  public void testUnpackDoesntLeaveTarget() throws Exception {
+    File file = File.createTempFile("temp", null);
+    File tmpDir = file.getParentFile();
+
+    try {
+      ZipUtil.unpack(badFile, tmpDir);
+      fail();
+    }
+    catch (ZipException e) {
+      assertTrue(true);
+    }
+  }
+
+  public void testUnwrapDoesntLeaveTarget() throws Exception {
+    File file = File.createTempFile("temp", null);
+    File tmpDir = file.getParentFile();
+
+    try {
+      ZipUtil.iterate(badFileBackslashes, new ZipUtil.BackslashUnpacker(tmpDir));
+      fail();
+    }
+    catch (ZipException e) {
+      assertTrue(true);
+    }
+  }
+}

programming_language: TypeScript