Path Traversal in Axum with Mutual Tls
Path Traversal in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
Path Traversal occurs when user-controlled input used in file system operations is not properly validated, allowing an attacker to access files outside the intended directory. In Axum, this typically manifests through route handlers that directly concatenate user input (e.g., path parameters or query strings) into filesystem paths. When Mutual Tls (mTLS) is enforced, the server validates client certificates, which may create a false sense of security. Operators might assume that because authentication is strong, input validation is less critical. This can lead to relaxed handling of file paths, increasing the risk of traversal attacks.
Consider an Axum handler that serves files from a configured base directory:
use axum::{routing::get, Router};
use std::path::PathBuf;
async fn serve_file(path: String) -> String {
let base = PathBuf::from("/var/data");
let full_path = base.join(path); // No normalization or validation
full_path.to_string_lossy().into_owned()
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/files/:path", get(serve_file));
}
An attacker could send a request like /files/../../../etc/passwd. Even with mTLS ensuring the client is authenticated, the handler lacks path sanitization, potentially exposing sensitive system files. mTLS does not mitigate logical flaws like path manipulation; it only confirms identity. The scan may flag this as BFLA/Privilege Escalation or Input Validation depending on context, noting that authenticated access combined with weak path handling can lead to unauthorized data access.
In the context of middleBrick scans, which test the unauthenticated attack surface, mTLS-enabled endpoints might be probed as unauthenticated if certificates are not supplied. If the endpoint falls back to a default behavior or exposes paths without proper checks, findings related to Path Traversal will be reported with remediation guidance to validate and sanitize all user inputs.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
To secure Axum endpoints with mTLS while preventing Path Traversal, combine strict certificate validation with rigorous input handling. mTLS is configured at the server or reverse proxy layer (e.g., using Rustls or Hyper with TLS acceptors), while path safety is enforced in application logic.
First, ensure your Axum service integrates with a TLS acceptor that requests client certificates. Below is a realistic example using axum::Server with Rustls:
use axum::Server;
use rustls::{Certificate, PrivateKey, ServerConfig};
use std::sync::Arc;
async fn build_https_server() {
let certs = load_certs("ca.crt");
let key = load_private_key("server.key");
let mut config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() // Will be changed to require client auth
.with_single_cert(certs, key)
.expect("bad certificate or key");
// Require client certificate verification
config.client_auth_mode = rustls::server::ClientAuthMode::RequireAndVerifyWebPKI;
config.client_root_cas = load_client_cas("clients/"); // Load trusted client CAs
let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(Router::new().route("/files/:name", get(secure_file_handler))) });
let addr = "0.0.0.0:8443".parse().unwrap();
Server::bind(&addr).https(config).serve(make_svc).await.unwrap();
}
fn load_certs(path: &str) -> Vec<Certificate> { /* ... */ }
fn load_private_key(path: &str) -> PrivateKey { /* ... */ }
fn load_client_cas(dir: &str) -> rustls::RootCertStore { /* ... */ }
This ensures that only clients with valid certificates signed by a trusted CA can connect. However, mTLS alone does not protect against path manipulation. You must still sanitize inputs in handlers:
use axum::{routing::get, Extension};
use std::path::{Path, PathBuf};
use uuid::Uuid;
async fn secure_file_handler(
Extension(base_dir): Extension,
name: String,
) -> Result<String, (StatusCode, String)> {
// Validate: only allow alphanumeric, hyphens, underscores, and extensions
if !name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.') {
return Err((StatusCode::BAD_REQUEST, "Invalid filename".into()));
}
let path = base_dir.join(name);
// Ensure the resolved path is within base_dir
if !path.starts_with(&base_dir) {
return Err((StatusCode::FORBIDDEN, "Access denied".into()));
}
// Proceed to read file securely
Ok("File content".into())
}
Key practices:
- Input validation: Restrict filenames to a safe character set and reject sequences like
... - Path canonicalization: Use
Path::starts_withafter joining to confirm the final path remains within the intended directory. - Separation of concerns: mTLS handles authentication; application logic handles authorization and input safety.
middleBrick scans will verify that both transport-layer authentication (via mTLS) and application-layer input validation are in place, highlighting any gaps where Path Traversal could occur despite strong TLS configuration.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |