Request Smuggling in Axum (Rust)
Request Smuggling in Axum with Rust — how this specific combination creates or exposes the vulnerability
Request smuggling is an application-layer attack that exploits discrepancies in how front-end and back-end HTTP parsers handle message boundaries. When an application built with the Rust web framework Axum sits behind a reverse proxy or load balancer that uses a different parsing strategy, smuggling can occur. Axum, via Tower and Hyper, relies on strict adherence to HTTP specifications, but if request routing or body handling is inconsistent with the upstream component, an attacker can craft a request that is interpreted differently by each layer.
Consider an Axum service deployed behind a proxy that buffers requests and forwards them with modifications to headers such as Content-Length or Transfer-Encoding. If the proxy normalizes or splits requests before they reach Axum, and Axum applies its own parsing logic without validating the upstream interpretation, the boundary between requests can be blurred. For example, a request that smuggles an additional request via an unhandled Transfer-Encoding: chunked body may be fully processed by the proxy as two separate requests, but Axum may treat the second request as part of the same connection, leading to request injection or response leakage.
In Axum, this often surfaces in scenarios where developers manually manipulate headers or streams, or when using custom route guards that read the request body in a non-standard way. Because Axum is built on asynchronous streams, if the stream is not consumed correctly before passing control to downstream handlers, leftover data can be misinterpreted as the start of a new request when the connection is reused. This is especially relevant when TLS offloading or HTTP/2 interactions change how frames are framed and delivered. The attack typically requires an unauthenticated network position between client and server, making it a server-side request smuggling issue rather than a client-side vulnerability.
Real-world impact includes unauthorized access to admin endpoints, bypassing rate limiting, or leaking one user’s data to another within the same backend pool. Because Axum does not enforce a single source of truth for request parsing, the framework assumes the transport layer has already normalized the message. If that assumption is incorrect, the application may process a request that was never intended to reach the target handler, a pattern seen in CVE-classic smuggling techniques adapted to modern Rust stacks.
Testing for this class of issue requires sending carefully crafted requests that mix Content-Length and Transfer-Encoding headers, or exploiting chunked encoding ambiguities. MiddleBrick’s unauthenticated scan can detect such inconsistencies by analyzing how Axum-based endpoints handle malformed or ambiguous headers, revealing whether smuggling is possible in your deployment topology.
Rust-Specific Remediation in Axum — concrete code fixes
To mitigate request smuggling in Axum, ensure consistent request parsing by normalizing headers before routing and avoiding manual stream splitting. The key is to consume the request body completely and validate header semantics within Axum’s middleware layer, preventing downstream handlers from seeing fragmented or ambiguous messages.
Below is a secure Axum handler that explicitly consumes the request body and rejects ambiguous header combinations:
use axum::{
body::Body,
http::{Request, StatusCode},
middleware::Next,
response::IntoResponse,
};
use std::convert::Infallible;
async fn validate_headers_and_body(
mut req: Request<Body>,
next: Next<Body>
) -> Result<impl IntoResponse, Infallible> {
// Reject requests that mix Content-Length and Transfer-Encoding
let has_content_length = req.headers().get("content-length").is_some();
let has_transfer_encoding = req.headers().get("transfer-encoding").is_some();
if has_content_length && has_transfer_encoding {
return Ok((StatusCode::BAD_REQUEST, "Ambiguous headers: do not mix Content-Length and Transfer-Encoding").into_response());
}
// Fully consume the body to prevent leftover data
let _body = match req.body_mut().data().await {
Some(Ok(chunk)) => chunk,
_ => return Ok((StatusCode::BAD_REQUEST, "Invalid body").into_response()),
};
// Proceed safely
Ok(next.run(req).await)
}
This middleware checks for header inconsistencies and ensures the body is fully read before the request proceeds. In a stack using tower::Service, you can integrate this as a layer to protect all routes:
use tower::ServiceBuilder;
let app = Router::new()
.route("/api/secure", get(secure_handler))
.layer(
ServiceBuilder::new()
.layer_fn(|service| {
tower::util::fn_service(move |req: Request<Body>| {
validate_headers_and_body(req, service)
})
}),
);
Additionally, avoid manually parsing chunks of the stream in route handlers. Instead, use Axum’s built-in extractors such as Json<T> or State<>, which handle framing and deserialization safely. If you must work with raw streams, ensure you use hyper::body::to_bytes to materialize the entire body before processing:
use axum::body::to_bytes;
async fn handler(req: Request<Body>) -> impl IntoResponse {
let bytes = to_bytes(req.into_body(), 8192).await.unwrap();
// Process bytes safely
}
These practices align with how tools like MiddleBrick detect irregularities during scans, and they reduce the surface area available for smuggling attacks in Rust-based Axum services.