HIGH symlink attackactixmutual tls

Symlink Attack in Actix with Mutual Tls

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

A symlink attack in Actix with mutual TLS (mTLS) occurs when an authenticated client can influence file paths used by the server such that a trusted mTLS-authenticated handler ends of opening or writing files outside the intended directory. In an mTLS setup, the server validates client certificates before business logic runs, which can create a false sense of safety. If the application builds file system paths from client-supplied data (e.g., a filename, user ID, or session identifier) without canonicalization or strict allowlisting, an attacker can provide a path traversal sequence or a symbolic link to cause the server to read or write sensitive files under the privileges of the mTLS-authenticated service.

Consider an Actix web service that uses mTLS to identify corporate clients and stores uploaded documents under a directory derived on the client certificate subject. A vulnerable handler might concatenate the client-provided filename directly to the base directory. An attacker who possesses a valid client certificate can send a filename like ../../../etc/passwd or a symlink such as /tmp/upload -> /etc/passwd. If the server resolves the path at runtime without removing .. segments or without using path-cleaning functions, the mTLS context does not prevent the traversal; it only ensures the request is authenticated. This exposes a classic path traversal (Insecure Direct Object Reference) that is masked by mTLS, making the issue harder to detect because traffic appears authorized.

The combination of mTLS and unchecked path construction also interacts poorly with symlink races. An attacker with a valid certificate could first cause the server to create a symlink in a predictable temporary location (e.g., by uploading a file with a manipulated name), then trigger the server to follow that symlink in a subsequent request. Because mTLS is verified early, the server may skip additional authorization checks, assuming the authenticated client is allowed to access the target. This can lead to unauthorized reading of configuration files, logs, or keys, or even to privilege escalation if the service runs with elevated permissions and writes files that are later read by other system components.

Moreover, if the Actix service uses asynchronous tasks or worker threads to process uploaded content, a symlink created after the initial path check but before the file move or read can redirect operations. mTLS does not protect against this time-of-check to time-of-use (TOCTOU) class of issues. The server should treat mTLS as an identity signal, not as a boundary against path manipulation, and apply strict path canonicalization, chroot-like restrictions, and allowlisted directories for any filesystem interaction, regardless of the authentication layer.

Mutual Tls-Specific Remediation in Actix — concrete code fixes

Remediation centers on never trusting client input for filesystem paths and isolating file operations from the authentication layer. Below are concrete Actix examples that combine mTLS with secure path handling.

1. Canonicalize and restrict file paths using std::fs::canonicalize and ensure the resolved path remains within an allowed directory:

use actix_web::{web, HttpResponse, Result};
use std::path::{Path, PathBuf};

fn allowed_directory() -> PathBuf {
    Path::new("/srv/uploads").to_path_buf()
}

fn is_path_allowed(path: &Path, base: &Path) -> bool {
    path.strip_prefix(base).is_ok()
}

async fn upload_file(
    payload: web::Payload,
    path_hint: web::Query>,
) -> Result {
    let base = allowed_directory();
    // Use a server-generated filename to avoid client-controlled paths
    let filename = uuid::Uuid::new_v4().to_string();
    let target = base.join(filename);

    // Canonicalize base to prevent symlink escapes via ".." in paths constructed elsewhere
    let base_canonical = match base.canonicalize() {
        Ok(p) => p,
        Err(_) => return Ok(HttpResponse::InternalServerError().finish()),
    };

    // Ensure the final target remains inside the allowed directory
    if !is_path_allowed(&target, &base_canonical) {
        return Ok(HttpResponse::Forbidden().body("Path not allowed"));
    }

    // stream payload to target ...
    Ok(HttpResponse::Ok().body("uploaded"))
}

This approach avoids concatenating user input into paths and uses a server-generated filename to eliminate path traversal risks entirely.

2. If you must accept a filename, sanitize it by removing traversal components and reject paths containing symlinks or directory traversals:

use actix_web::web;
use std::path::Path;

fn sanitize_filename(input: &str) -> Option {
    let path = Path::new(input);
    // Reject paths that attempt traversal or contain absolute components
    if path.components().any(|c| matches!(c, std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::CurDir)) {
        return None;
    }
    // Ensure the filename is valid Unicode and does not contain control characters
    let filename = path.file_name()?.to_string_lossy().into_owned();
    Some(filename)
}

async fn store_with_sanitized_name(
    web::Query(params): web::Query>,
) -> HttpResponse {
    let name = match params.get("name") {
        Some(n) => sanitize_filename(n).unwrap_or_else(|| "unnamed".to_string()),
        None => "unnamed".to_string(),
    };
    let base = Path::new("/srv/uploads").join(name);
    // Proceed with secure file operations
    HttpResponse::Ok().body("stored")
}

3. For mTLS integration, extract certificate identity and use it as a namespaced directory, but still validate paths:

use actix_web::dev::ServiceRequest;
use actix_web_httpauth::extractors::AuthenticationError;
use std::path::PathBuf;

fn get_cert_identity(req: &ServiceRequest) -> Option {
    // Extract subject or serial from the mTLS certificate presented by the client
    // This is a placeholder for actual certificate extraction logic
    Some("client123".to_string())
}

async fn cert_aware_handler(req: ServiceRequest) -> Result {
    if let Some(identity) = get_cert_identity(&req) {
        let base = PathBuf::from("/srv/uploads").join(identity);
        std::fs::create_dir_all(&base).ok();
        // Use base for further operations with canonicalization as shown earlier
    }
    // Continue processing
    Ok(req.into_response(actix_web::web::block(|| ()).into_body()))
}

These examples emphasize that mTLS provides authentication but does not absolve the application from secure path handling. Always canonicalize, reject traversal components, and avoid direct use of client input in filesystem operations.

Frequently Asked Questions

Does mutual TLS prevent symlink attacks in Actix?
No. Mutual TLS authenticates clients but does not protect against path manipulation. If your application builds filesystem paths from client-controlled data, an attacker with a valid certificate can still use traversal sequences or symlinks to access unintended files. Apply strict path canonicalization and allowlisting regardless of mTLS.
What is the most important mitigation for symlink attacks in Actix with mTLS?
Never construct filesystem paths by concatenating client input. Use server-generated filenames, canonicalize base directories, and ensure resolved paths remain inside an allowed directory. Treat mTLS as an identity signal and apply file system permissions and path checks independently.