HIGH symlink attackaxummutual tls

Symlink Attack in Axum with Mutual Tls

Symlink Attack in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability

A symlink attack in an Axum service that uses mutual TLS (mTLS) occurs when an endpoint that validates client certificates also interacts with the filesystem using user-supplied paths. Even though mTLS ensures the client is authenticated, it does not restrict what the server does with the request data. If an endpoint constructs file paths by concatenating or joining a client-provided identifier with a base directory, an attacker can supply a path that traverses directories or includes a symbolic link. When the server resolves the path, it may read or write files outside the intended directory, leading to unauthorized data access or tampering.

In Axum, this typically arises in handlers that accept a path segment as a parameter and then open a file on disk. Mutual TLS protects transport and verifies identity, but it does not sanitize or constrain the content of the path. For example, an authenticated client with a valid certificate could send a request with a parameter like ../../../etc/passwd or a crafted filename that resolves to a sensitive location. If the handler uses standard library file operations without canonicalization or prefix checks, the symlink or path traversal succeeds from the server’s perspective, potentially exposing configuration files, application secrets, or user data. The presence of mTLS may give a false sense of security, encouraging less rigorous input validation.

Another scenario involves file uploads or temporary file creation. If the server stores uploaded files using names derived from client input and places them in a shared directory, an attacker could attempt to name a file so that it becomes a symlink on the filesystem (requires filesystem-level opportunities). During a subsequent read or write, the application might follow the symlink and operate on an arbitrary file. MiddleBrick’s checks for Input Validation and Property Authorization highlight these risks by examining whether user-controlled data is used in filesystem operations and whether authorization logic accounts for path manipulation.

Because Axum applications often integrate with Rust’s type system and async patterns, developers might assume that strong typing and middleware protections reduce risk. However, without explicit validation of file paths and strict base-directory enforcement, the combination of mTLS and filesystem access remains vulnerable. The security checks for Data Exposure and Unsafe Consumption can detect whether responses inadvertently leak filesystem details or whether uploaded content is inspected safely.

Mutual Tls-Specific Remediation in Axum — concrete code fixes

Remediation focuses on ensuring that mTLS authentication is complemented by strict path handling. Do not rely on transport security alone; treat all user input as untrusted, even when the request includes a valid client certificate.

1. Validate and sanitize path input

Normalize and validate any path components before using them in filesystem operations. Reject paths containing .. or absolute segments, and ensure the resolved path remains within a designated base directory.

use axum::extract::Path;
use std::path::{Path, PathBuf};

fn safe_join(base: &Path, user_path: &str) -> Result {
    // Prevent attempts to bypass with leading slash or Windows drive letters
    if user_path.starts_with('/') || user_path.contains("\\") || user_path.contains("..") {
        return Err("invalid path");
    }
    let joined = base.join(user_path);
    // Ensure the canonicalized path starts with the base directory
    let canonical = std::fs::canonicalize(&joined).map_err(|_| "not_found")?;
    if canonical.starts_with(base) {
        Ok(canonical)
    } else {
        Err("outside_base")
    }
}

async fn download_file(
    Path(filename): Path,
) -> Result {
    let base = Path::new("/var/app/uploads");
    let path = safe_join(base, &filename).map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "invalid path"))?;
    // proceed to read path
    Ok(axum::response::IntoResponse::into_response(...))
}

2. Use a dedicated upload directory with unique filenames

Avoid using user-supplied names directly. Generate a unique identifier for storage and keep the original name only in metadata that is not used for filesystem access.

use axum::extract::Multipart;
use uuid::Uuid;

async fn upload_handler(
    mut multipart: Multipart,
) -> Result {
    while let Some(field) = multipart.next_field().await.map_err(|_| "multipart_error")? {
        let data = field.bytes().await.map_err(|_| "read_error")?;
        let ext = field.headers().get("filename")
            .and_then(|v| v.to_str().ok())
            .map(|s| srsplit('.').last().unwrap_or("bin"))
            .unwrap_or("bin");
        let filename = format!("upload-{}.{}", Uuid::new_v4(), ext);
        let path = Path::new("/var/app/uploads").join(filename);
        tokio::fs::write(path, data).await.map_err(|_| "write_error")?;
    }
    Ok("uploaded".to_string())
}

3. Apply principle of least privilege to the runtime user

Run the Axum process with a dedicated operating system user that has minimal filesystem permissions. Even if a symlink attack bypasses application logic, the OS permissions limit the potential impact.

4. Harden TLS and client verification

Ensure your mTLS configuration in Axum verifies client certificates strictly and does not accept insecure fallback. Use strong cipher suites and keep certificate validation code isolated from path construction logic.

use axum::Router;
use hyper_rustls::HttpsConnector;
use std::sync::Arc;
use rustls_pemfile::{certs, pkcs8_private_keys};

// Example of setting up a server with client cert verification
async fn build_secure_router() -> Router {
    // Load server certificate and key
    let cert_file = &mut std::io::BufReader::new(std::fs::File::open("server.crt").unwrap());
    let key_file = &mut std::io::BufReader::new(std::fs::File::open("server.key").unwrap());
    let cert_chain = certs(cert_file).collect::, _>>().unwrap();
    let mut keys = pkcs8_private_keys(key_file).collect::, _>>().unwrap();

    let config = rustls::ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth() // mTLS configured separately via extractor or middleware
        .with_single_cert(cert_chain, keys.remove(0))
        .unwrap();
    // Further integration with mTLS verification logic...
    Router::new()
}

These changes ensure that authentication via mTLS is not the only control and that filesystem interactions are bounded and predictable.

Frequently Asked Questions

Does mutual TLS prevent symlink attacks in Axum?
No. Mutual TLS authenticates the client but does not restrict how the server handles paths derived from user input. Explicit path validation and base-directory enforcement are required.
Can middleBrick detect symlink risks in Axum mTLS setups?
Yes. middleBrick’s Input Validation and Property Authorization checks identify whether user-controlled data influences filesystem operations, regardless of transport-layer authentication.