Zip Slip Vulnerability in Archive Extraction

Critical Risk File Upload & Path Traversal
zip-sliparchive-extractionpath-traversaljavaarbitrary-file-writecompression

What it is

A critical path traversal vulnerability that occurs during the extraction of archive files (ZIP, TAR, etc.) when filenames within the archive contain path traversal sequences like '../'. This allows attackers to write files outside the intended extraction directory, potentially overwriting critical system files or placing executable files in dangerous locations.

import java.io.*; import java.util.zip.*; public class VulnerableZipExtractor { // VULNERABLE: No path validation during extraction public static void extractZip(String zipFilePath, String destDirectory) throws IOException { File destDir = new File(destDirectory); if (!destDir.exists()) { destDir.mkdir(); } ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); ZipEntry entry = zipIn.getNextEntry(); // Extract each entry while (entry != null) { // DANGEROUS: Direct use of entry name without validation String filePath = destDirectory + File.separator + entry.getName(); if (!entry.isDirectory()) { // Extract file without any path checks extractFile(zipIn, filePath); } else { // Create directory without validation File dir = new File(filePath); dir.mkdirs(); } zipIn.closeEntry(); entry = zipIn.getNextEntry(); } zipIn.close(); } private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException { // No validation of file path or size BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(filePath)); byte[] bytesIn = new byte[4096]; int read = 0; // No limits on extraction size - vulnerable to zip bombs while ((read = zipIn.read(bytesIn)) != -1) { bos.write(bytesIn, 0, read); } bos.close(); } public static void main(String[] args) { try { extractZip("malicious.zip", "extracted/"); System.out.println("Extraction completed."); } catch (IOException e) { e.printStackTrace(); } } } /* Malicious ZIP contents that would exploit this: 1. Entry: "../../../etc/passwd" → Writes to /etc/passwd 2. Entry: "..\\..\\..\\windows\\system32\\evil.dll" → Writes to Windows system directory 3. Entry: "legitimate_folder/../../../usr/bin/malware" → Writes executable to system PATH 4. Zip bomb: Small compressed file that expands to 1GB+ → Causes disk space exhaustion */
import java.io.*; import java.nio.file.*; import java.util.zip.*; import java.util.concurrent.TimeUnit; public class SecureZipExtractor { // Security limits private static final long MAX_SIZE = 100 * 1024 * 1024; // 100MB total private static final long MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB per file private static final int MAX_ENTRIES = 1000; private static final int MAX_DEPTH = 10; private static final long EXTRACTION_TIMEOUT = 30; // seconds public static void extractZipSecurely(String zipFilePath, String destDirectory) throws IOException, SecurityException { Path destPath = Paths.get(destDirectory).toAbsolutePath().normalize(); // Create destination if it doesn't exist if (!Files.exists(destPath)) { Files.createDirectories(destPath); } // Validate destination is a directory if (!Files.isDirectory(destPath)) { throw new IllegalArgumentException("Destination must be a directory"); } long startTime = System.currentTimeMillis(); try (ZipInputStream zipIn = new ZipInputStream( new BufferedInputStream(new FileInputStream(zipFilePath)))) { ZipEntry entry; int entryCount = 0; long totalExtractedSize = 0; while ((entry = zipIn.getNextEntry()) != null) { // Check timeout if (System.currentTimeMillis() - startTime > TimeUnit.SECONDS.toMillis(EXTRACTION_TIMEOUT)) { throw new SecurityException("Extraction timeout exceeded"); } // Validate entry count if (++entryCount > MAX_ENTRIES) { throw new SecurityException("Too many entries in archive: " + entryCount); } // Validate and resolve entry path Path entryPath = validateAndResolveEntryPath(entry, destPath); if (entry.isDirectory()) { createSecureDirectory(entryPath); } else { long fileSize = extractFileSecurely(zipIn, entryPath, entry); totalExtractedSize += fileSize; // Check total size limit if (totalExtractedSize > MAX_SIZE) { throw new SecurityException("Total extraction size exceeded: " + totalExtractedSize); } } zipIn.closeEntry(); } } System.out.println("Secure extraction completed. Entries: " + entryCount + ", Total size: " + totalExtractedSize + " bytes"); } private static Path validateAndResolveEntryPath(ZipEntry entry, Path destDir) throws SecurityException { String entryName = entry.getName(); // Basic validation if (entryName == null || entryName.trim().isEmpty()) { throw new SecurityException("Empty entry name"); } // Check for dangerous patterns if (entryName.contains("../") || entryName.contains("..\\")) { throw new SecurityException("Path traversal detected: " + entryName); } // Check for absolute paths if (entryName.startsWith("/") || entryName.startsWith("\\") || (entryName.length() > 1 && entryName.charAt(1) == ':')) { throw new SecurityException("Absolute path detected: " + entryName); } // Check directory depth int depth = entryName.split("[/\\\\]").length; if (depth > MAX_DEPTH) { throw new SecurityException("Directory nesting too deep: " + depth); } // Sanitize filename String sanitized = sanitizeEntryName(entryName); // Resolve path and validate it's within destination Path entryPath = destDir.resolve(sanitized).normalize(); if (!entryPath.startsWith(destDir)) { throw new SecurityException( "Path traversal attempt - resolved path outside destination: " + entryPath + " not within " + destDir); } return entryPath; } private static String sanitizeEntryName(String entryName) { // Remove dangerous characters and normalize separators return entryName .replaceAll("[<>:\"|?*]", "") // Windows forbidden chars .replaceAll("\\0", "") // Null bytes .replace("\\\\", "/") // Normalize separators .replaceAll("/{2,}", "/") // Remove multiple slashes .trim(); } private static void createSecureDirectory(Path dirPath) throws IOException { // Create directory with secure permissions if (!Files.exists(dirPath)) { Files.createDirectories(dirPath); // Set restrictive permissions on Unix systems if (!System.getProperty("os.name").toLowerCase().contains("windows")) { try { Files.setPosixFilePermissions(dirPath, Set.of( PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE )); } catch (UnsupportedOperationException e) { // Filesystem doesn't support POSIX permissions } } } } private static long extractFileSecurely(ZipInputStream zipIn, Path filePath, ZipEntry entry) throws IOException, SecurityException { // Validate file size from entry (if available) long declaredSize = entry.getSize(); if (declaredSize > MAX_FILE_SIZE) { throw new SecurityException("File too large: " + declaredSize + " bytes"); } // Ensure parent directory exists Path parentDir = filePath.getParent(); if (parentDir != null && !Files.exists(parentDir)) { createSecureDirectory(parentDir); } // Extract with monitoring try (OutputStream fileOut = Files.newOutputStream(filePath); BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOut)) { byte[] buffer = new byte[8192]; long totalBytesRead = 0; int bytesRead; while ((bytesRead = zipIn.read(buffer)) != -1) { totalBytesRead += bytesRead; // Check file size limit if (totalBytesRead > MAX_FILE_SIZE) { // Cleanup partial file try { Files.deleteIfExists(filePath); } catch (IOException e) { // Log but don't throw } throw new SecurityException("File size limit exceeded during extraction"); } bufferedOut.write(buffer, 0, bytesRead); } bufferedOut.flush(); // Verify final size matches declaration if provided if (declaredSize >= 0 && totalBytesRead != declaredSize) { System.out.println("Warning: File size mismatch. Declared: " + declaredSize + ", Actual: " + totalBytesRead); } return totalBytesRead; } } // Example usage with error handling public static void main(String[] args) { if (args.length != 2) { System.out.println("Usage: java SecureZipExtractor "); return; } try { extractZipSecurely(args[0], args[1]); System.out.println("Secure extraction completed successfully."); } catch (SecurityException e) { System.err.println("Security violation: " + e.getMessage()); } catch (IOException e) { System.err.println("IO error: " + e.getMessage()); } } // Utility method to validate ZIP file before extraction public static boolean validateZipFile(String zipFilePath) { try (ZipFile zipFile = new ZipFile(zipFilePath)) { // Basic validation - can be expanded return zipFile.size() <= MAX_ENTRIES; } catch (IOException e) { return false; } } }

💡 Why This Fix Works

The vulnerable code directly uses ZIP entry names to create file paths, allowing path traversal attacks. The secure version implements comprehensive validation, resource limits, and proper path resolution to prevent zip slip vulnerabilities.

import zipfile import os # VULNERABLE: No path validation def extract_zip(zip_path, extract_to): with zipfile.ZipFile(zip_path, 'r') as zip_ref: for member in zip_ref.infolist(): # DANGEROUS: Direct extraction without validation zip_ref.extract(member, extract_to) # This allows: # member.filename = "../../../etc/passwd" # Results in overwriting system files print(f"Extracted {zip_path} to {extract_to}") # VULNERABLE: Manual extraction def manual_extract(zip_path, extract_to): with zipfile.ZipFile(zip_path, 'r') as zip_file: for filename in zip_file.namelist(): # No validation of filename file_path = os.path.join(extract_to, filename) with zip_file.open(filename) as source: with open(file_path, 'wb') as target: # No size limits - vulnerable to zip bombs target.write(source.read())
import zipfile import os import tempfile from pathlib import Path import time class SecureZipExtractor: def __init__(self): self.max_size = 100 * 1024 * 1024 # 100MB self.max_file_size = 50 * 1024 * 1024 # 50MB per file self.max_entries = 1000 self.max_depth = 10 self.timeout = 30 # seconds def extract_zip_securely(self, zip_path, extract_to): """Securely extract ZIP file with comprehensive validation""" extract_path = Path(extract_to).resolve() extract_path.mkdir(parents=True, exist_ok=True) if not extract_path.is_dir(): raise ValueError("Destination must be a directory") start_time = time.time() with zipfile.ZipFile(zip_path, 'r') as zip_file: entries = zip_file.infolist() if len(entries) > self.max_entries: raise SecurityError(f"Too many entries: {len(entries)}") total_size = 0 for i, entry in enumerate(entries): # Check timeout if time.time() - start_time > self.timeout: raise SecurityError("Extraction timeout exceeded") # Validate entry safe_path = self._validate_entry_path(entry.filename, extract_path) if entry.is_dir(): safe_path.mkdir(parents=True, exist_ok=True) self._set_secure_permissions(safe_path) else: file_size = self._extract_file_securely( zip_file, entry, safe_path ) total_size += file_size if total_size > self.max_size: raise SecurityError( f"Total extraction size exceeded: {total_size}" ) # Progress reporting if i % 100 == 0: print(f"Processed {i+1}/{len(entries)} entries") print(f"Secure extraction completed. Total size: {total_size} bytes") return total_size def _validate_entry_path(self, entry_name, base_path): """Validate and resolve entry path safely""" if not entry_name or not entry_name.strip(): raise SecurityError("Empty entry name") # Check for dangerous patterns dangerous_patterns = [ '../', '..\\', '..\\\\', '/..', '\\..', '\\\\..' ] for pattern in dangerous_patterns: if pattern in entry_name: raise SecurityError(f"Path traversal detected: {entry_name}") # Check for absolute paths if entry_name.startswith(('/', '\\')) or \ (len(entry_name) > 1 and entry_name[1] == ':'): raise SecurityError(f"Absolute path detected: {entry_name}") # Check directory depth depth = len([p for p in entry_name.split('/') if p and p != '.']) if depth > self.max_depth: raise SecurityError(f"Directory nesting too deep: {depth}") # Sanitize and resolve path sanitized = self._sanitize_filename(entry_name) safe_path = (base_path / sanitized).resolve() # Ensure path is within base directory try: safe_path.relative_to(base_path) except ValueError: raise SecurityError( f"Path traversal attempt: {safe_path} not within {base_path}" ) return safe_path def _sanitize_filename(self, filename): """Remove dangerous characters from filename""" import re # Remove null bytes and control characters sanitized = ''.join(char for char in filename if ord(char) >= 32) # Remove dangerous characters sanitized = re.sub(r'[<>:"|?*]', '', sanitized) # Normalize path separators sanitized = sanitized.replace('\\', '/') # Remove multiple consecutive slashes sanitized = re.sub(r'/+', '/', sanitized) return sanitized.strip() def _extract_file_securely(self, zip_file, entry, output_path): """Extract individual file with size validation""" declared_size = entry.file_size if declared_size > self.max_file_size: raise SecurityError(f"File too large: {declared_size} bytes") # Ensure parent directory exists output_path.parent.mkdir(parents=True, exist_ok=True) with zip_file.open(entry) as source: with open(output_path, 'wb') as target: total_bytes = 0 chunk_size = 8192 while True: chunk = source.read(chunk_size) if not chunk: break total_bytes += len(chunk) # Check size limit if total_bytes > self.max_file_size: # Clean up partial file try: output_path.unlink() except OSError: pass raise SecurityError( "File size limit exceeded during extraction" ) target.write(chunk) # Set secure permissions self._set_secure_permissions(output_path) # Verify final size if declared_size > 0 and total_bytes != declared_size: print(f"Warning: Size mismatch for {output_path.name}. " f"Declared: {declared_size}, Actual: {total_bytes}") return total_bytes def _set_secure_permissions(self, path): """Set secure file permissions""" try: if path.is_file(): # Read/write for owner only path.chmod(0o600) elif path.is_dir(): # Read/write/execute for owner only path.chmod(0o700) except OSError: # Permissions not supported on this filesystem pass def validate_zip_before_extraction(self, zip_path): """Pre-validate ZIP file before extraction""" try: with zipfile.ZipFile(zip_path, 'r') as zip_file: # Check if it's a valid ZIP if zip_file.testzip() is not None: raise SecurityError("Corrupted ZIP file") # Check number of entries entries = zip_file.infolist() if len(entries) > self.max_entries: raise SecurityError(f"Too many entries: {len(entries)}") # Check total declared size total_declared = sum(entry.file_size for entry in entries) if total_declared > self.max_size: raise SecurityError(f"Total size too large: {total_declared}") # Check for suspicious patterns for entry in entries: if '../' in entry.filename or '..\\' in entry.filename: raise SecurityError( f"Suspicious entry detected: {entry.filename}" ) return True except zipfile.BadZipFile: raise SecurityError("Invalid ZIP file") class SecurityError(Exception): """Custom exception for security violations""" pass # Example usage def main(): extractor = SecureZipExtractor() try: # Validate before extraction extractor.validate_zip_before_extraction('archive.zip') # Perform secure extraction total_size = extractor.extract_zip_securely( 'archive.zip', 'safe_extraction_dir' ) print(f"Successfully extracted {total_size} bytes") except SecurityError as e: print(f"Security violation: {e}") except Exception as e: print(f"Extraction failed: {e}") if __name__ == '__main__': main()

💡 Why This Fix Works

The vulnerable Python code directly extracts ZIP entries without validation. The secure version implements comprehensive path validation, resource limits, and proper error handling to prevent zip slip attacks.

Why it happens

The most critical flaw is extracting archive entries without validating their paths. Archives can contain entries with malicious names like '../../../etc/passwd' or '..\\..\\..\\windows\\system32\\evil.dll' that, when extracted, write files outside the intended directory. This is especially dangerous because the extraction process has the application's file system privileges.

Root causes

Unvalidated Archive Entry Names

The most critical flaw is extracting archive entries without validating their paths. Archives can contain entries with malicious names like '../../../etc/passwd' or '..\\..\\..\\windows\\system32\\evil.dll' that, when extracted, write files outside the intended directory. This is especially dangerous because the extraction process has the application's file system privileges.

Preview example – JAVA
// VULNERABLE: Direct extraction without path validation
import java.util.zip.*;
import java.io.*;

public void extractZip(String zipFile, String destDir) throws IOException {
    ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
    ZipEntry entry;
    
    while ((entry = zis.getNextEntry()) != null) {
        // DANGEROUS: Using entry name directly
        File outFile = new File(destDir + File.separator + entry.getName());
        
        // Attacker can use: "../../../etc/crontab"
        // Results in writing to: /etc/crontab
        FileOutputStream fos = new FileOutputStream(outFile);
        // ... write data
    }
}

Insufficient Canonical Path Validation

Applications that perform basic path validation but fail to use canonical path resolution can be bypassed. Attackers can use various encoding techniques, symlinks, or platform-specific path separators to circumvent validation. Without proper canonicalization, paths like 'folder/../../../etc/passwd' may pass basic checks but still result in path traversal.

Preview example – JAVA
// VULNERABLE: Weak path validation
public boolean isValidPath(String entryName, String destDir) {
    // Basic check that can be bypassed
    if (entryName.contains("../")) {
        return false;
    }
    
    // Bypasses:
    // "..\\" (Windows separators)
    // "foo/../../../etc/passwd" (after folder)
    // "%2e%2e%2f" (URL encoded)
    // Symlink attacks
    
    return true;
}

Platform-Specific Path Handling Issues

Different operating systems handle file paths differently, creating bypass opportunities. Windows systems use backslashes and support alternate data streams, while Unix systems use forward slashes and support symbolic links. Applications that only validate one platform's conventions can be exploited when deployed on different systems.

Preview example – JAVA
// VULNERABLE: Platform-specific bypass
public void extractEntry(ZipEntry entry, String baseDir) {
    String name = entry.getName();
    
    // Only checks for Unix-style traversal
    if (name.contains("../")) {
        throw new SecurityException("Path traversal detected");
    }
    
    // Windows bypasses:
    // "..\\..\\..\\windows\\system32\\malware.exe"
    // "file.txt:ads" (Alternate Data Stream)
    // "C:\\..\\..\\etc\\passwd" (Absolute paths)
    
    File outputFile = new File(baseDir, name);
    // Extraction continues...
}

Archive Bomb and Resource Exhaustion

Beyond path traversal, malicious archives can contain zip bombs or consume excessive resources during extraction. Large files, deeply nested directories, or archives with extremely high compression ratios can cause denial of service, disk space exhaustion, or memory exhaustion during the extraction process.

Preview example – JAVA
// VULNERABLE: No resource limits during extraction
public void extractArchive(ZipInputStream zis) throws IOException {
    ZipEntry entry;
    
    while ((entry = zis.getNextEntry()) != null) {
        // No size limits - vulnerable to zip bombs
        // No extraction time limits
        // No nested directory depth limits
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int len;
        
        // Attacker can create 10GB file that extracts to 1TB
        while ((len = zis.read(buffer)) > 0) {
            baos.write(buffer, 0, len); // Unlimited memory allocation
        }
    }
}

Fixes

1

Implement Comprehensive Path Validation

Always validate and canonicalize archive entry paths before extraction. Resolve canonical paths, check for path traversal sequences, validate against the intended extraction directory, and reject any entries that would write outside the target directory. Use platform-independent validation logic.

View implementation – JAVA
import java.nio.file.*;
import java.util.zip.*;

public class SecureZipExtractor {
    private static final int MAX_ENTRIES = 1000;
    private static final long MAX_SIZE = 100 * 1024 * 1024; // 100MB
    private static final int MAX_DEPTH = 10;
    
    public void extractZip(String zipFile, String destDir) throws IOException {
        Path destPath = Paths.get(destDir).toAbsolutePath().normalize();
        
        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
            ZipEntry entry;
            int entryCount = 0;
            long totalSize = 0;
            
            while ((entry = zis.getNextEntry()) != null) {
                // Validate entry
                validateZipEntry(entry, destPath, ++entryCount, totalSize);
                
                Path entryPath = validateAndResolvePath(entry.getName(), destPath);
                
                if (entry.isDirectory()) {
                    Files.createDirectories(entryPath);
                } else {
                    // Ensure parent directory exists
                    Files.createDirectories(entryPath.getParent());
                    
                    // Extract file with size limit
                    totalSize += extractFile(zis, entryPath, entry.getSize());
                }
            }
        }
    }
    
    private Path validateAndResolvePath(String entryName, Path destDir) {
        // Remove any dangerous characters
        String sanitized = entryName.replaceAll("[^a-zA-Z0-9/._-]", "");
        
        // Convert to Path and resolve
        Path entryPath = destDir.resolve(sanitized).normalize();
        
        // Ensure the resolved path is within destination directory
        if (!entryPath.startsWith(destDir)) {
            throw new SecurityException("Path traversal attempt: " + entryName);
        }
        
        return entryPath;
    }
}
2

Implement Resource Limits and Monitoring

Protect against zip bombs and resource exhaustion by implementing strict limits on extraction. Set maximum file sizes, total extraction size, number of entries, directory depth, and extraction time. Monitor resource usage during extraction and abort if limits are exceeded.

View implementation – JAVA
private long extractFile(InputStream inputStream, Path outputPath, long entrySize) 
        throws IOException {
    final long MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB per file
    final int BUFFER_SIZE = 8192;
    
    // Validate declared size
    if (entrySize > MAX_FILE_SIZE) {
        throw new SecurityException("File too large: " + entrySize);
    }
    
    try (OutputStream fos = Files.newOutputStream(outputPath)) {
        byte[] buffer = new byte[BUFFER_SIZE];
        long totalBytesRead = 0;
        int bytesRead;
        
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            totalBytesRead += bytesRead;
            
            // Check against both declared and actual size
            if (totalBytesRead > MAX_FILE_SIZE) {
                throw new SecurityException("File extraction size exceeded limit");
            }
            
            fos.write(buffer, 0, bytesRead);
        }
        
        // Verify final size matches declaration (if provided)
        if (entrySize > 0 && totalBytesRead != entrySize) {
            throw new SecurityException("File size mismatch");
        }
        
        return totalBytesRead;
    }
}

private void validateZipEntry(ZipEntry entry, Path destDir, int entryCount, long totalSize) {
    // Limit number of entries
    if (entryCount > MAX_ENTRIES) {
        throw new SecurityException("Too many archive entries");
    }
    
    // Limit total extraction size
    if (totalSize > MAX_SIZE) {
        throw new SecurityException("Archive too large");
    }
    
    // Limit directory depth
    String name = entry.getName();
    int depth = name.split("/").length;
    if (depth > MAX_DEPTH) {
        throw new SecurityException("Directory nesting too deep");
    }
    
    // Check for suspicious patterns
    if (name.contains("../") || name.contains("..\\") || 
        name.startsWith("/") || name.contains(":")) {
        throw new SecurityException("Suspicious entry name: " + name);
    }
}
3

Use Secure Archive Libraries

Leverage security-enhanced archive libraries that provide built-in protection against zip slip and other archive-based attacks. Libraries like Apache Commons Compress with security features, or dedicated secure extraction libraries, can help prevent common pitfalls.

View implementation – JAVA
import org.apache.commons.compress.archivers.zip.*;
import org.apache.commons.compress.utils.IOUtils;

public class SecureApacheExtractor {
    public void extractZipSecurely(File zipFile, File destDir) throws IOException {
        try (ZipFile zip = new ZipFile(zipFile)) {
            Enumeration<ZipArchiveEntry> entries = zip.getEntries();
            
            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();
                
                // Validate entry name
                if (!isValidEntryName(entry.getName())) {
                    throw new SecurityException("Invalid entry: " + entry.getName());
                }
                
                File destFile = new File(destDir, entry.getName());
                
                // Critical: Validate canonical path
                String destDirCanonical = destDir.getCanonicalPath();
                String destFileCanonical = destFile.getCanonicalPath();
                
                if (!destFileCanonical.startsWith(destDirCanonical + File.separator)) {
                    throw new SecurityException("Path traversal: " + entry.getName());
                }
                
                if (entry.isDirectory()) {
                    destFile.mkdirs();
                } else {
                    // Ensure parent exists
                    destFile.getParentFile().mkdirs();
                    
                    try (InputStream is = zip.getInputStream(entry);
                         FileOutputStream fos = new FileOutputStream(destFile)) {
                        
                        // Use IOUtils.copy with size limits
                        long copied = IOUtils.copy(is, fos, 50 * 1024 * 1024); // 50MB limit
                        
                        if (copied > 50 * 1024 * 1024) {
                            throw new SecurityException("File too large");
                        }
                    }
                }
            }
        }
    }
    
    private boolean isValidEntryName(String name) {
        // Comprehensive validation
        if (name == null || name.trim().isEmpty()) return false;
        if (name.contains("../") || name.contains("..\\")) return false;
        if (name.startsWith("/") || name.startsWith("\\")) return false;
        if (name.contains(":") && System.getProperty("os.name").toLowerCase().contains("windows")) {
            return false; // Prevent absolute paths on Windows
        }
        return true;
    }
}
4

Implement Sandboxed Extraction Environment

Extract archives in isolated environments such as containers, chroot jails, or temporary directories with restricted permissions. This limits the impact of successful zip slip attacks and provides additional layers of protection against malicious archive content.

View implementation – JAVA
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;

public class SandboxedExtractor {
    private Path createSecureExtractionDir() throws IOException {
        // Create temporary directory with restricted permissions
        Path tempDir = Files.createTempDirectory("secure_extract_");
        
        // Set restrictive permissions (Unix)
        if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
            Set<PosixFilePermission> permissions = Set.of(
                PosixFilePermission.OWNER_READ,
                PosixFilePermission.OWNER_WRITE,
                PosixFilePermission.OWNER_EXECUTE
            );
            Files.setPosixFilePermissions(tempDir, permissions);
        }
        
        // Register for cleanup on exit
        tempDir.toFile().deleteOnExit();
        
        return tempDir;
    }
    
    public Path extractToSandbox(String archivePath) throws IOException {
        Path sandboxDir = createSecureExtractionDir();
        
        try {
            // Perform extraction in sandbox
            extractZip(archivePath, sandboxDir.toString());
            
            // Post-extraction validation
            validateExtractedContent(sandboxDir);
            
            return sandboxDir;
            
        } catch (Exception e) {
            // Cleanup on failure
            cleanupDirectory(sandboxDir);
            throw e;
        }
    }
    
    private void validateExtractedContent(Path extractDir) throws IOException {
        Files.walk(extractDir)
            .forEach(path -> {
                try {
                    // Validate each extracted file
                    if (Files.isRegularFile(path)) {
                        // Check file size
                        if (Files.size(path) > 100 * 1024 * 1024) {
                            throw new SecurityException("Extracted file too large: " + path);
                        }
                        
                        // Check for executable permissions
                        if (Files.isExecutable(path)) {
                            throw new SecurityException("Executable file detected: " + path);
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
    }
    
    private void cleanupDirectory(Path dir) {
        try {
            Files.walk(dir)
                .sorted(Comparator.reverseOrder())
                .map(Path::toFile)
                .forEach(File::delete);
        } catch (IOException e) {
            // Log cleanup failure
            System.err.println("Failed to cleanup directory: " + e.getMessage());
        }
    }
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies zip slip vulnerability in archive extraction and many other security issues in your codebase.