Container Escape in Actix with Bearer Tokens
Container Escape in Actix with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A container escape in Actix using Bearer tokens occurs when an API endpoint that relies on token-based authentication is also exposed within a containerized environment that has overly permissive network or filesystem access. In this scenario, an attacker who obtains or guesses a Bearer token may leverage it to interact with an Actix-based service, then exploit misconfigured container controls to move laterally or access host resources.
Actix is a Rust web framework often used to build high-performance HTTP services. When Bearer tokens are used for authorization, the framework typically validates the token on each request via middleware. If the validation is incomplete (for example, failing to reject malformed tokens or not enforcing scope checks), an authenticated request may reach handlers that execute privileged operations.
Within a container, processes often run with elevated privileges or mount sensitive host paths (e.g., /var/run/docker.sock or /proc). If an Actix route performs operations such as spawning processes, reading host files, or making outbound network calls based on token-authenticated input, an attacker can use the Bearer token to trigger these actions. Common triggers include path traversal via user-supplied parameters or SSRF through server-side request forgery, which may allow the attacker to reach metadata services or other container-internal endpoints.
The combination is risky because Bearer tokens are often treated as opaque strings; if the Actix application does not validate token scope, binding, or audience, an attacker may reuse a token across services. If the container network allows the Actix service to reach the host Docker socket or internal APIs, the token can be used to issue privileged container operations (e.g., creating new containers or mounting filesystems), effectively breaking container isolation.
During a black-box scan, middleBrick tests for Bearer token handling by probing authentication boundaries and then checks whether authenticated endpoints expose dangerous functionality. It also looks for SSRF, improper input validation, and excessive agency patterns (such as tool usage or function calling) that could allow an authenticated request to trigger unsafe container interactions. Findings highlight gaps such as missing scope enforcement, overly permissive container runtime policies, and unchecked deserialization that can turn a valid Bearer token into a path for container escape.
Bearer Tokens-Specific Remediation in Actix — concrete code fixes
Remediation focuses on strict token validation, scope and audience checks, and avoiding privileged operations from token-authenticated handlers. Below are concrete Actix examples that demonstrate secure patterns.
1. Validate Bearer token structure and reject malformed tokens
Ensure middleware rejects malformed or missing Authorization headers before routing.
use actix_web::{dev::ServiceRequest, Error, HttpRequest};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use futures_util::future::{ok, Either, Ready};
async fn validate_token(req: ServiceRequest) -> Result {
let head = req.head();
if let Some(auth) = head.extensions.get::() {
let token = auth.token();
// Reject obviously malformed tokens early
if token.len() < 32 || token.contains(' ') {
return Err((actix_web::error::ErrorUnauthorized("Invalid token"), req));
}
// Additional checks: verify token format (e.g., JWT structure)
if !token.contains('.') {
return Err((actix_web::error::ErrorUnauthorized("Invalid token format"), req));
}
// Scope validation example (pseudocode)
// if !has_scope(token, "containers:read") { ... }
} else {
return Err((actix_web::error::ErrorUnauthorized("Missing token"), req));
}
ok(req).map_err(|e| (e, req))
}
// In your Actix app configuration:
// App::new()
// .wrap_fn(|req, srv| validate_token(req).and_then(|req| srv.call(req)))
2. Enforce scope and audience checks for token claims
When using JWT Bearer tokens, validate claims such as scope, audience, and issuer within your authorization logic.
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
scope: String,
aud: String,
exp: usize,
// other standard claims
}
fn verify_token(token: &str) -> Result {
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&["my-actix-api"]);
validation.validate_exp = true;
let token_data = decode::(
token,
&DecodingKey::from_rsa_pem(include_bytes!("public_key.pem"))?,
&validation,
)?;
Ok(token_data.claims)
}
// Use in a handler:
async fn restricted_endpoint(
req: HttpRequest,
auth: BearerAuth,
) -> Result {
let claims = verify_token(auth.token())?;
if !claims.scope.contains("host:containers") {
return Err(actix_web::error::ErrorForbidden("Insufficient scope"));
}
// Proceed with safe, scoped operation
Ok(HttpResponse::Ok().body("Authorized"))
}
3. Avoid dangerous container operations from authenticated endpoints
Do not allow token-authenticated requests to directly invoke container runtimes or access host sockets. If such operations are necessary, enforce strict input validation and use a separate privileged service with audited controls.
// BAD: Do not do this async fn run_container_command(auth: BearerAuth, cmd: web::Query) -> Result { // Never construct commands from user input // Avoid: std::process::Command::new("docker").arg(&cmd.action).output() unimplemented!() } // BETTER: Use a controlled interface with pre-approved actions enum ContainerAction { List, Inspect(String), // ID validated via regex } fn parse_action(input: &str) -> Result { // Strict parsing, no shell interaction if input == "list" { Ok(ContainerAction::List) } else if input.starts_with("inspect:") { let id = input.trim_start_matches("inspect:"); if id.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') { Ok(ContainerAction::Inspect(id.to_string())) } else { Err("Invalid container ID") } } else { Err("Not allowed") } } async fn safe_container_endpoint( auth: BearerAuth, action: web::Query , ) -> Result { let claims = verify_token(auth.token())?; if !claims.scope.contains("containers:execute") { return Err(actix_web::error::ErrorForbidden("No execution scope")); } match parse_action(&action.action) { Ok(ContainerAction::List) => { // Call internal, audited helper let list = container_api::list()?; Ok(HttpResponse::Ok().json(list)) } Ok(ContainerAction::Inspect(id)) => { let details = container_api::inspect(&id)?; Ok(HttpResponse::Ok().json(details)) } Err(_) => Err(actix_web::error::ErrorBadRequest("Invalid action")), } }