HIGH actixrustrequest smuggling

Request Smuggling in Actix (Rust)

Request Smuggling in Actix with Rust

Request smuggling occurs when an intermediary (such as a load balancer or reverse proxy) processes HTTP requests differently than the origin server, allowing attackers to smuggle requests across security boundaries. In Actix with Rust, this can arise from how the application parses and forwards requests, particularly when chunked Transfer-Encoding is combined with Content-Length, or when requests are forwarded without canonicalization. Actix is a high-performance Rust web framework that relies on strict parsing of headers and message bodies. If the application or its deployment stack does not enforce consistent HTTP semantics, an attacker can craft requests where the frontend and Actix backend interpret the boundary between messages differently.

For example, consider an Actix service deployed behind a proxy that buffers and re-sends requests. If the client sends a request with both Transfer-Encoding: chunked and a Content-Length header, Actix’s parser may prioritize one over the other depending on configuration and middleware ordering. An attacker can smuggle a second request into the body of the first, causing the backend to misinterpret routing or authentication context. This is especially relevant when Actix applications handle sensitive endpoints (like admin or payment paths) and share routing logic with public endpoints. Because Actix processes requests asynchronously and can reuse connections via keep-alive, improperly terminated or ambiguous message framing may allow a smuggled request to be interpreted as part of an administrative session. The risk is compounded when TLS terminates at the proxy and the backend assumes the connection is trusted. Runtime scans using tools like middleBrick can surface inconsistencies between spec-defined headers and runtime behavior, revealing mismatches that enable smuggling vectors.

Specific attack patterns include:

  • CL.TE (Content-Length then Transfer-Encoding): Client sends Content-Length followed by Transfer-Encoding: chunked. The frontend uses Transfer-Encoding; Actix uses Content-Length, causing a split.
  • TE.CL (Transfer-Encoding then Content-Length): Reverse scenario where Actix prioritizes Transfer-Encoding but the proxy falls back to Content-Length.
  • Chunked encoding manipulation: Malformed chunk sizes or trailing headers that Actix accepts but the proxy rejects or vice versa.

Because Actix is implemented in Rust, memory-safety guarantees reduce the risk of certain classes of bugs, but they do not prevent logical parsing inconsistencies. Developers must ensure that header normalization and message framing are consistent across the stack. OpenAPI/Swagger specifications analyzed by middleBrick can help identify ambiguous header expectations, and runtime scans can detect discrepancies between documented and actual behavior. MiddleBrick’s checks for Input Validation, Authentication, and Unsafe Consumption are particularly useful for identifying endpoints where smuggling could lead to privilege escalation or data exposure.

Rust-Specific Remediation in Actix

Remediation focuses on strict header enforcement, canonical request representation, and avoiding ambiguous forwarding. In Actix, configure the HTTP parser to reject requests that contain both Content-Length and Transfer-Encoding, and ensure that middleware does not reorder or reinterpret headers. Use Actix’s extractors and guards to enforce a single source of truth for message framing. Below are concrete code examples demonstrating secure practices.

1. Reject ambiguous headers at the service level

Create a middleware wrapper that validates incoming requests before they reach route handlers. This example rejects requests that contain both Content-Length and Transfer-Encoding headers.

use actix_web::{dev::{ServiceRequest, ServiceResponse}, Error, middleware::Next};
use actix_web::http::header::{self, HeaderValue};

async fn validate_no_ambiguous_headers(req: ServiceRequest, next: Next) -> Result {
    let has_content_length = req.headers().contains_key(header::CONTENT_LENGTH);
    let has_te = req.headers().contains_key(header::TRANSFER_ENCODING);

    if has_content_length && has_te {
        return Err(actix_web::error::ErrorBadRequest("Ambiguous headers: Content-Length and Transfer-Encoding"));
    }
    next.call(req).await
}

// Apply middleware in app configuration
// App::new()
//     .wrap(validate_no_ambiguous_headers)
//     .service(web::resource("/admin").to(admin_handler));

2. Normalize and canonicalize forwarded requests

If Actix acts as a client when forwarding requests (e.g., to a backend API), ensure headers are normalized and redundant framing removed. Use reqwest with explicit header sets instead of blindly copying incoming headers.

use reqwest::Client;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_LENGTH, TRANSFER_ENCODING};

async fn forward_request(uri: &str, original_headers: &HeaderMap) -> Result {
    let client = Client::new();
    let mut headers = HeaderMap::new();

    // Selectively copy safe headers; avoid forwarding ambiguous framing headers
    for (name, value) in original_headers.iter() {
        let name_str = name.as_str();
        if name_str.eq_ignore_ascii_case("authorization") || name_str.eq_ignore_ascii_case("x-request-id") {
            headers.insert(name, value.clone());
        }
    }
    // Explicitly avoid forwarding Content-Length or Transfer-Encoding when reconstructing
    headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));

    let response = client
        .post(uri)
        .headers(headers)
        .body("{}")
        .send()
        .await?
        .text()
        .await?;
    Ok(response)
}

3. Enforce strict HTTP version and framing in server configuration

Configure Actix server to use consistent HTTP/1.1 parsing and disable behaviors that may interact unpredictably with proxies. This reduces variability between frontend and backend interpretations.

use actix_web::HttpServer;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        actix_web::App::new()
            .wrap(actix_web::middleware::NormalizePath::trim())
            .service(web::resource("/api").route(web::get().to(api_handler)))
    })
    .workers(2)
    .keep_alive(30)
    .disable_signals(false)
    .bind_rustls("0.0.0.0:8443", rustls::ServerConfig::new())? // Example TLS config
    .expect("Failed to bind")
    .run()
    .await
}

These practices ensure that message boundaries remain unambiguous, reducing the likelihood of request smuggling. Regular scans with middleBrick can validate that deployed configurations align with intended security posture.

Frequently Asked Questions

Can middleBrick detect request smuggling in Actix deployments?
Yes. middleBrick compares spec-defined headers with runtime behavior and flags inconsistencies related to Content-Length and Transfer-Encoding, surfacing potential smuggling vectors.
Is Rust memory safety sufficient to prevent smuggling?
Rust prevents memory corruption, but logical header parsing inconsistencies between frontend proxies and Actix backends can still enable request smuggling. Remediation requires explicit header validation and canonicalization.