HIGH zip slipaxum

Zip Slip in Axum

How Zip Slip Manifests in Axum

Zip Slip is a directory traversal vulnerability that occurs when extracting ZIP archives without proper path validation. In Axum applications, this typically manifests when handling file uploads or processing archives sent to API endpoints.

The vulnerability allows attackers to craft ZIP files with malicious paths like ../../etc/passwd or ../../../../tmp/pwned. When the server extracts these archives using naive extraction methods, files are written outside the intended directory, potentially overwriting critical system files or placing executables in sensitive locations.

In Axum, common scenarios include:

  • File upload endpoints that accept ZIP archives for processing
  • API endpoints that extract archives to temporary directories
  • Handlers that process user-submitted archives for document conversion or analysis

The core issue stems from using Rust's standard library zip::read::ZipArchive::extract or similar extraction methods without path sanitization. Axum itself doesn't provide extraction utilities, but developers often integrate ZIP handling directly in their route handlers:

use axum::{extract::Multipart, http::StatusCode};
use std::fs;
use zip::read::ZipArchive;
use std::io::Cursor;

async fn upload_zip(mut multipart: Multipart) -> Result<String, StatusCode> {
    while let Some(field) = multipart.next_field().await? {
        let data = field.bytes().await?.to_vec();
        let mut archive = ZipArchive::new(Cursor::new(data))?;
        
        // VULNERABLE: No path validation
        let target_dir = "/var/www/uploads/";
        archive.extract(target_dir)?; // This is the problem
        
        Ok("Upload successful".to_string())
    }
}

The attack vector is straightforward: an attacker crafts a ZIP file containing a file entry with a path like ../../../../../etc/passwd. When extracted, this file overwrites the system's password file, potentially allowing privilege escalation or denial of service.

Axum-Specific Detection

Detecting Zip Slip in Axum applications requires both static analysis and runtime scanning. middleBrick's black-box scanning approach is particularly effective for this vulnerability.

When scanning an Axum API endpoint that accepts file uploads, middleBrick tests for Zip Slip by:

  • Sending crafted ZIP archives with traversal paths
  • Verifying whether extracted files appear outside the intended directory
  • Checking for error handling that might leak system information
  • Analyzing the OpenAPI spec for file upload endpoints that lack proper validation

For Axum developers, you can also implement detection in your own code:

use axum::{extract::Multipart, http::StatusCode, Json};
use std::path::Path;
use zip::read::ZipArchive;
use std::io::Cursor;

fn is_suspicious_path(entry_path: &str, base_dir: &Path) -> bool {
    let full_path = base_dir.join(entry_path);
    
    // Check if path escapes base directory
    if !full_path.starts_with(base_dir) {
        return true;
    }
    
    // Check for suspicious patterns
    let suspicious_patterns = [
        "..",
        "/../",
        "../",
        ":",
        "|",
        "&"
    ];
    
    suspicious_patterns.iter().any(|pattern| entry_path.contains(pattern))
}

async fn secure_upload_zip(mut multipart: Multipart) 
    -> Result<Json<UploadResult>, StatusCode> 
{
    while let Some(field) = multipart.next_field().await? {
        let data = field.bytes().await?.to_vec();
        let mut archive = ZipArchive::new(Cursor::new(data))?;
        let target_dir = "/var/www/uploads/";
        let target_path = Path::new(target_dir);
        
        for i in 0..archive.len() {
            let mut file = archive.by_index(i)?;
            let outpath = file.enclosed_name()
                .ok_or_else(|| StatusCode::BAD_REQUEST)?;
                
            if is_suspicious_path(outpath.to_str().unwrap_or(""), target_path) {
                return Err(StatusCode::BAD_REQUEST);
            }
            
            // Safe extraction logic here
        }
    }
    Ok(Json(UploadResult { success: true }))
}

middleBrick's CLI tool makes it easy to scan your Axum endpoints:

# Install middleBrick CLI
npm install -g @middlebrick/cli

# Scan your Axum API endpoint
middlebrick scan http://localhost:3000/api/upload-zip

# With GitHub integration
middlebrick scan https://api.your-app.com/upload --github-repo your-org/your-repo

Axum-Specific Remediation

Remediating Zip Slip in Axum requires a defense-in-depth approach. Here's how to implement secure ZIP handling in your Axum applications:

1. Path Validation

use axum::{extract::Multipart, http::StatusCode};
use std::path::{Path, PathBuf};
use zip::read::ZipArchive;
use std::io::Cursor;

fn sanitize_path(original: &str, base_dir: &Path) -> Result<PathBuf, StatusCode> {
    let path = PathBuf::from(original);
    let canonical_base = base_dir.canonicalize().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    // Resolve and check if it's still within base directory
    let resolved = canonical_base.join(path).canonicalize().map_err(|_| StatusCode::BAD_REQUEST)?;
    
    if !resolved.starts_with(&canonical_base) {
        return Err(StatusCode::BAD_REQUEST);
    }
    
    Ok(resolved)
}

async fn safe_upload_zip(mut multipart: Multipart) 
    -> Result<String, StatusCode> 
{
    let base_dir = "/var/www/uploads/";
    let base_path = Path::new(base_dir);
    
    while let Some(field) = multipart.next_field().await? {
        let data = field.bytes().await?.to_vec();
        let mut archive = ZipArchive::new(Cursor::new(data))?;
        
        for i in 0..archive.len() {
            let mut file = archive.by_index(i)?;
            let outpath = file.enclosed_name()
                .ok_or_else(|| StatusCode::BAD_REQUEST)?;
            
            // Validate path
            let sanitized_path = sanitize_path(outpath.to_str().unwrap_or(""), base_path)?;
            
            // Create directories if needed
            if let Some(parent) = sanitized_path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            
            // Write file safely
            let mut outfile = std::fs::File::create(&sanitized_path)?;
            std::io::copy(&mut file, &mut outfile)?;
        }
    }
    
    Ok("Upload successful".to_string())
}

2. Use Safe Extraction Libraries

Instead of using raw extraction methods, use libraries that handle path validation:

use axum::extract::Multipart;
use camino::{Utf8Path, Utf8PathBuf};
use path_clean::clean;
use std::io::Cursor;

fn safe_extract(zip_data: Vec<u8>, target_dir: &Utf8Path) -> Result<(), String> {
    let mut archive = zip::ZipArchive::new(Cursor::new(zip_data)).map_err(|e| e.to_string())?;
    
    for i in 0..archive.len() {
        let mut file = archive.by_index(i).map_err(|e| e.to_string())?;
        let outpath = file.enclosed_name()
            .ok_or_else(|| "Invalid path in archive".to_string())?
            .to_str().ok_or_else(|| "Invalid UTF-8 path")?
            .to_string();
        
        // Clean path and validate
        let cleaned = clean(&outpath);
        let target_path = Utf8PathBuf::from(target_dir).join(cleaned);
        
        // Ensure path is within target directory
        if !target_path.starts_with(target_dir) {
            return Err(format!("Path {} escapes target directory", outpath));
        }
        
        // Create directories and write file
        if let Some(parent) = target_path.parent() {
            std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
        }
        
        let mut outfile = std::fs::File::create(&target_path).map_err(|e| e.to_string())?;
        std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?;
    }
    
    Ok(())
}

3. Implement Rate Limiting and Size Limits

Combine Zip Slip protection with Axum's built-in middleware:

use axum::{routing::post, Router, Json};
use axum_extra::extract::Payload;
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/upload-zip", post(upload_zip_handler))
    .layer(RequestBodyLimitLayer::new(10_000_000)) // 10MB limit
    .layer(TraceLayer::new_for_http());

Frequently Asked Questions

How does Zip Slip differ from other directory traversal attacks in Axum?
Zip Slip specifically exploits archive extraction, while traditional directory traversal attacks manipulate path parameters in HTTP requests. Zip Slip is more dangerous because it can create multiple files across the filesystem in a single request, potentially overwriting critical system files or planting malicious executables in startup directories.
Can middleBrick detect Zip Slip in my Axum application?
Yes, middleBrick's black-box scanning tests your Axum API endpoints by sending crafted ZIP archives with traversal paths. It verifies whether your application properly validates and sanitizes these paths during extraction. The scan takes 5-15 seconds and provides specific findings with severity levels and remediation guidance.