Here is an example of Java code that is vulnerable to the Zip Slip archive extraction flaw:
🥺 Vulnerable Code
try (ZipInputStream zis = new ZipInputStream(uploaded.getInputStream())) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// Vulnerable: the entry name inside the archive is trusted
File outFile = new File(UPLOAD_DIR, entry.getName());
try (FileOutputStream fos = new FileOutputStream(outFile)) {
zis.transferTo(fos);
}
}
}Archive entry names are attacker controlled strings, and they are allowed to contain ../. An entry called ../../../../opt/app/webapps/ROOT/shell.jsp escapes the upload directory and lands in the web root, which is remote code execution from a file upload form. The same loop happily overwrites configuration files, SSH keys, and cron entries, and it has no limit on how much data a small archive expands into.
😎 Secure Code
Here is a version of the same code that is secured against the Zip Slip archive extraction flaw:
Path targetDir = Paths.get(UPLOAD_DIR).toAbsolutePath().normalize();
long totalBytes = 0;
try (ZipInputStream zis = new ZipInputStream(uploaded.getInputStream())) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
Path resolved = targetDir.resolve(entry.getName()).normalize();
if (!resolved.startsWith(targetDir)) {
throw new SecurityException("Blocked traversal in archive: " + entry.getName());
}
totalBytes += Math.max(entry.getSize(), 0);
if (totalBytes > MAX_TOTAL_BYTES) {
throw new SecurityException("Archive expands beyond the allowed size");
}
Files.createDirectories(resolved.getParent());
try (OutputStream out = Files.newOutputStream(resolved, StandardOpenOption.CREATE_NEW)) {
zis.transferTo(out);
}
}
}Resolving and normalizing the path and then checking that it still starts with the target directory is the control that actually stops traversal - string checks for .. are easy to bypass with encoding and backslashes. CREATE_NEW refuses to overwrite existing files, the running total stops zip bombs, and extracting into a non-executable directory outside the web root means even a planted script cannot be requested by a browser.