Container Escape in Axum with Basic Auth
Container Escape in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability
A container escape in an Axum service that uses HTTP Basic Authentication can occur when authentication logic interacts with the runtime environment in ways that expose host-level resources. Axum routes and extractors are type-safe and composable, but if Basic Auth credentials are used to construct shell commands, file paths, or dynamic configuration, an attacker may leverage specially crafted inputs to traverse directories or invoke system utilities. For example, a username containing path traversal sequences or shell metacharacters could lead to unauthorized file reads or execution of arbitrary commands inside the container. Because the container filesystem is typically isolated, a successful escape grants access to host files or processes that should be unreachable. In an API security scan, middleBrick tests unauthenticated attack surfaces and flags findings such as Injection and Unsafe Consumption that can contribute to container escape scenarios, even when Basic Auth is present but improperly validated. The risk is compounded when the Axum application runs with elevated privileges or mounts sensitive host directories into the container. An attacker may also probe for IDOR or BOLA issues alongside weak authentication boundaries, which middleBrick reports under BOLA/IDOR and Property Authorization checks. Remediation focuses on strict input validation, avoiding direct use of credentials in system interactions, and applying the principle of least privilege to the container runtime.
Basic Auth-Specific Remediation in Axum — concrete code fixes
To mitigate container escape risks when using Basic Authentication in Axum, validate and sanitize all credential inputs before any use in system interactions. Avoid constructing commands or file paths directly from headers or user-controlled strings. Instead, treat credentials as opaque identifiers and map them to internal identifiers safely. Below are concrete Axum examples demonstrating secure handling.
Example 1: Safe Basic Auth extraction and validation
use axum::async_trait;
use axum::extract::{Request, Extension, FromRequest};
use axum::http::StatusCode;
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Arc;
struct User {
id: u64,
username: String,
}
struct BasicAuth {
username: String,
password: String,
}
impl BasicAuth {
fn from_header(header: &str) -> Option {
let decoded = base64::decode(header.as_bytes()).ok()?;
let decoded_str = String::from_utf8(decoded).ok()?;
let mut parts = decoded_str.splitn(2, ':');
let username = parts.next()?.to_string();
let password = parts.next()?.to_string();
Some(BasicAuth { username, password })
}
}
#[async_trait]
impl FromRequest for User
where
S: Send + Sync,
{
type Rejection = (StatusCode, String);
async fn from_request(req: Request, _state: &S) -> Result {
let auth_header = req.headers()
.get("authorization")
.and_then(|v| v.to_str().ok())
.filter(|s| s.starts_with("Basic "))
.map(|s| &s[6..]);
let creds = auth_header
.and_then(BasicAuth::from_header)
.ok_or((StatusCode::UNAUTHORIZED, "Invalid credentials".into()))?;
// Validate username format: allow only alphanumeric and underscores
if !creds.username.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
return Err((StatusCode::BAD_REQUEST, "Invalid username".into()));
}
// Map to internal user without using credentials in shell or filesystem operations
let user = User {
id: 1,
username: creds.username,
};
Ok(user)
}
}
async fn handler(Extension(user): Extension) -> String {
format!("Authenticated as: {}", user.username)
}
Example 2: Avoiding command or path injection when using credentials
use axum::routing::get;
use axum::Router;
use std::net::SocketAddr;
// Unsafe pattern to avoid:
// let path = format!("/data/{}", username);
// Command injection risk if username includes shell metacharacters
// Secure alternative: use a controlled mapping
async fn get_user_data(username: String) -> Result {
// Whitelist known users or use a database keyed by validated identifier
let allowed_users = ["alice", "bob", "charlie"];
if !allowed_users.contains(&username.as_str()) {
return Err((StatusCode::FORBIDDEN, "Access denied".into()));
}
// Use constant-time lookups or a safe internal ID
Ok(format!("/internal/data/{}", username.replace(|c: char| !c.is_alphanumeric(), "_")))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/user/:username", get(|username: String| async move {
get_user_data(username).await.map_err(|(status, msg)| (status, msg.into()))
}));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Key practices include validating username characters, avoiding string interpolation for shell or filesystem paths, using parameterized queries if interacting with databases, and ensuring the container runs with minimal privileges. middleBrick scans can identify related issues such as Injection and Unsafe Consumption in unauthenticated tests, providing prioritized findings and remediation guidance.