Piotr Karwasz created COMPRESS-725:
--------------------------------------
Summary: Unsafe Javadoc example in ArchiveStreamFactory
Key: COMPRESS-725
URL: https://issues.apache.org/jira/browse/COMPRESS-725
Project: Commons Compress
Issue Type: Improvement
Reporter: Piotr Karwasz
h2. Summary
This Javadoc problem was reported to the Apache Security Team: the ZIP
decompression example in {{ArchiveStreamFactory}} demonstrates an insecure
extraction pattern that is vulnerable to Zip Slip path traversal. The library
already ships a safe helper ({{ArchiveEntry.resolveIn()}}, since 1.26.0), but
the documentation example does not use it, so anyone copying it inherits the
flaw.
The report is quoted below verbatim, with the reporter's name and e-mail
address redacted.
h2. Reported issue (quoted)
{quote}
*Affected:* Apache Commons Compress (all versions)
*Fixed in:* Documentation update recommended (library provides safe method
since 1.26.0)
h3. Summary
The Javadoc example code in {{ArchiveStreamFactory.java}} (lines 73-79)
demonstrates an insecure ZIP decompression pattern using
{{dir.toPath().resolve(entry.getName())}} directly, without Zip Slip path
traversal protection. While the library itself provides
{{ArchiveEntry.resolveIn()}} (available since 1.26.0) for safe extraction, the
official documentation example does not use it.
h3. Vulnerable code (from ArchiveStreamFactory.java:76)
{code:java}
/**
* Decompressing a ZIP-File:
*
* <pre>
* final InputStream is = Files.newInputStream(input.toPath());
* ArchiveInputStream in = new
ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.ZIP, is);
* ZipArchiveEntry entry = (ZipArchiveEntry) in.getNextEntry();
* OutputStream out =
Files.newOutputStream(dir.toPath().resolve(entry.getName())); // <- ZIP SLIP
* IOUtils.copy(in, out);
* out.close();
* in.close();
* </pre>
*/
{code}
The pattern {{dir.toPath().resolve(entry.getName())}} is vulnerable to Zip Slip
attacks. An attacker can craft an archive with entry names containing {{../}}
sequences (e.g., {{../../../tmp/poc}}) to write files outside the intended
target directory.
h3. Safe alternative (available since 1.26.0)
{code:java}
default Path resolveIn(final Path parentPath) throws IOException {
final String name = getName();
final Path outputFile = parentPath.resolve(name).normalize();
if (!outputFile.startsWith(parentPath)) {
throw new ArchiveException("Zip slip '%s' + '%s' -> '%s'", parentPath,
name, outputFile);
}
return outputFile;
}
{code}
The Javadoc example should use:
{code:java}
OutputStream out = Files.newOutputStream(entry.resolveIn(dir.toPath()));
{code}
h3. Attack chain
{code}
1. Attacker creates malicious ZIP with entry name "../../../tmp/poc"
2. Victim copies the Javadoc example and uses it to extract untrusted ZIP
3. new File(targetDir, entry.getName()) -> "/victim/../../../tmp/poc"
4. f.getCanonicalPath() -> "/tmp/poc" (escapes target directory)
5. Files.newOutputStream(f.toPath()) -> writes to /tmp/poc
{code}
h3. CVSS scoring
* *CVSS v3.1:* 7.5 (High) - {{CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N}}
* *CVSS v4.0:* 7.5 (High) -
{{CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N}}
h3. Impact
Any developer copying the official Javadoc example code introduces a Zip Slip
path traversal vulnerability. Impact includes arbitrary file overwrite
(potentially RCE via symlinks, cron jobs, or SSH authorized_keys).
h3. CVE request
*We request CVE assignment for this vulnerability.*
h3. References
* CWE-22 (Path Traversal): [https://cwe.mitre.org/data/definitions/22.html]
* Zip Slip pattern: [https://snyk.io/blog/zip-slip-vulnerability/]
* resolveIn() method (v1.26.0):
[https://commons.apache.org/proper/commons-compress/changes-report.html#a1.26.0]
{quote}
h2. Suggested action
Update the {{ArchiveStreamFactory}} Javadoc example to extract entries via
{{ArchiveEntry.resolveIn(dir.toPath())}} so the documented pattern is safe by
default.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)