Skip to content

Restrict entry names which resolve to output directory itself - #589

Open
Marcono1234 wants to merge 1 commit into
srikanth-lingala:masterfrom
Marcono1234:zip-slip-output-dir
Open

Restrict entry names which resolve to output directory itself#589
Marcono1234 wants to merge 1 commit into
srikanth-lingala:masterfrom
Marcono1234:zip-slip-output-dir

Conversation

@Marcono1234

Copy link
Copy Markdown
Contributor

When extracting a ZIP file with entries named ., / or similar, those entries refer to the output directory itself. This PR restricts these entry name to avoid modifications (such as file permission changes) to the output directory.

Here is a small sample which previously caused Zip4j to erroneously modify the permissions of the output directory. The example uses the Apache Commons Compress library to create the ZIP file because that library makes it a bit easier to set these ZIP entry attributes.

import net.lingala.zip4j.ZipFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

class Zip4jPermissionsChange {
    public static void main(String[] args) throws Exception {
        Path zipPath = Path.of("permissions-change.zip");

        Path outputDir = Path.of("permissions-change");
        Files.deleteIfExists(outputDir);
        Files.createDirectory(outputDir);
        Files.setPosixFilePermissions(outputDir, Set.of());

        System.out.println("Permissions before: " + Files.getPosixFilePermissions(outputDir));

        createZip(zipPath);

        try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
            zipFile.extractAll(outputDir.toString());
        }

        System.out.println("Permissions after: " + Files.getPosixFilePermissions(outputDir));
    }

    public static void createZip(Path path) throws Exception {
        try (ZipArchiveOutputStream zipStream = new ZipArchiveOutputStream(path)) {
            var entry = new ZipArchiveEntry("/");
            // Set 'allow all' permissions
            entry.setUnixMode(0b1_1111_1111);
            zipStream.putArchiveEntry(entry);
            zipStream.closeArchiveEntry();
        }
    }
}

@Marcono1234 Marcono1234 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know what you think, or if you (or also anyone else) has other ideas how to validate this.

}
}

private void assertCanonicalPathsAreSame(File outputFile, String outputPath, FileHeader fileHeader)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combined this with the determineOutputFile method to support using null as indication to skip the ZIP entry, but without causing a ZipException.

Comment on lines +201 to +205
// Assume a redundant `/` entry is non-malicious but skip processing it to avoid modifying output
// directory in some way (e.g. changing file permissions)
if (outputFileName.equals(InternalZipConstants.ZIP_FILE_SEPARATOR)) {
return null;
}

@Marcono1234 Marcono1234 Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vaguely remember that I read that some (non-malicious) tools create such / entry, but I might be misremembering it.

If you don't think this is necessary, and a / entry should be treated as error as well instead of being skipped, please let me know.

@Marcono1234
Marcono1234 marked this pull request as draft June 30, 2026 16:30
@Marcono1234
Marcono1234 force-pushed the zip-slip-output-dir branch from 5790134 to 3855710 Compare June 30, 2026 17:29
@Marcono1234
Marcono1234 marked this pull request as ready for review June 30, 2026 17:49
@Marcono1234

Copy link
Copy Markdown
Contributor Author

The CI error seems unrelated?

2026-06-30T17:30:35.5697495Z [ERROR] Tests run: 58, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.178 s <<< FAILURE! - in net.lingala.zip4j.MiscZipFileIT
2026-06-30T17:30:35.5699183Z [ERROR] testCustomThreadFactory(net.lingala.zip4j.MiscZipFileIT)  Time elapsed: 0.566 s  <<< FAILURE!
2026-06-30T17:30:35.5707381Z java.lang.AssertionError: 
2026-06-30T17:30:35.5707989Z 
2026-06-30T17:30:35.5708326Z Expected size: 1 but was: 0 in:
2026-06-30T17:30:35.5708905Z []
2026-06-30T17:30:35.5709756Z 	at net.lingala.zip4j.MiscZipFileIT.testCustomThreadFactory(MiscZipFileIT.java:585)

@Marcono1234
Marcono1234 force-pushed the zip-slip-output-dir branch from 3855710 to d3cc017 Compare July 1, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant