Crlf Injection in Actix with Mutual Tls
Crlf Injection in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when untrusted input is reflected in HTTP headers without sanitization, allowing an attacker to inject additional header lines (e.g., \r\n) and potentially perform header splitting or response splitting. In Actix, this commonly arises when user-controlled data is placed into response headers, cookies, or location values without validation. The risk is compounded when Mutual Tls is in use because mTLS enforces client authentication at the transport layer, which can create a false sense of security. Operators may assume that strong client authentication prevents application-layer injection, but Crlf Injection is independent of transport-layer authentication: it resides in how the application builds HTTP messages, not in how clients are authenticated.
With Mutual Tls, the client certificate is validated before the application processes the request, which may lead developers to trust the identity of the caller and skip input sanitization. However, the injection vector is the headers or cookies crafted by the server in its responses, not the client identity. For example, if an Actix handler takes a query parameter redirect_to and places it into a Location header without removing \r\n sequences, an authenticated mTLS client can supply a payload like https://api.example.com/path\r\nSet-Cookie: session=hijacked. The mTLS channel ensures the request came from a trusted client, but the server still reflects that untrusted data into headers, enabling response splitting. This can lead to cache poisoning, cross-user cookie injection, or misleading the client about the nature of the transaction.
Another specific concern with the Actix + mTLS combination is that middleware or logging may inadvertently reflect headers into responses or logs. If an Actix application uses custom headers derived from mTLS client certificate fields (e.g., common name or serial) and later echoes those fields into other headers without sanitization, the attacker may influence the certificate-derived data through other means (such as a compromised CA or a misconfigured enrollment process) or simply supply newline characters in fields that are reflected. While mTLS prevents unauthorized clients, it does not sanitize or validate the content of authorized requests. Therefore, Crlf Injection remains a risk: the server must treat all data used in headers, cookies, and status-line contexts as untrusted, regardless of transport-layer guarantees.
Mutual Tls-Specific Remediation in Actix — concrete code fixes
Remediation focuses on strict input validation and output encoding for any data placed into HTTP headers, cookies, or the status line. Do not rely on mTLS to sanitize inputs; treat all user-influenced data as hostile even when mTLS is enforced. In Actix, you can sanitize inputs by disallowing or escaping \r and \n characters, and by using framework-prov header building utilities that prevent header splitting.
Below are concrete Actix examples demonstrating secure handling when Mutual Tls is in use.
Example 1: Safe header construction with sanitization
use actix_web::{web, HttpResponse, Result};
fn sanitize_header_value(value: &str) -> String {
value.chars().filter(|&c| c != '\r' && c != '\n').collect()
}
async fn handler_location(query: web::Query>) -> Result {
let redirect_to = query.get("url").map(|s| s.as_str()).unwrap_or("/");
let safe_location = sanitize_header_value(redirect_to);
Ok(HttpResponse::Found()
.header("Location", safe_location)
.finish())
}
Example 2: Using typed responses to avoid manual header injection
use actix_web::{web, HttpResponse, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct RedirectQuery {
target: String,
}
async fn safe_redirect(query: web::Query) -> Result {
// Validate format (e.g., only allow relative paths or a strict allowlist domain)
if query.target.starts_with('/') || query.target.starts_with("https://api.example.com") {
// Actix's HeaderMap values are not raw strings; using HeaderValue enforces basic safety
match ::actix_web::http::header::HeaderValue::from_str(&query.target) {
Ok(hval) => Ok(HttpResponse::SeeOther()
.insert_header((::actix_web::http::header::LOCATION, hval))
.finish()),
Err(_) => Ok(HttpResponse::BadRequest().body("Invalid location")),
}
} else {
Ok(HttpResponse::BadRequest().body("Not allowed"))
}
}
Example 3: Securing cookie values when mTLS is enforced
use actix_web::{web, HttpResponse, Result, cookie::Cookie};
async fn set_secure_cookie() -> Result {
let mut cookie = Cookie::build("session", "abc123")
.http_only(true)
.secure(true)
.path("/");
// Ensure cookie value does not contain newline characters
cookie = cookie.same_site(actix_web::cookie::SameSite::Strict);
Ok(HttpResponse::Ok()
.cookie(cookie.build())
.finish())
}
Additional guidance
- Validate and restrict inputs: use allowlists for URLs, paths, or hostnames instead of trying to sanitize arbitrary strings.
- Avoid reflecting raw headers or certificate fields directly into other headers; if you must reflect metadata, canonicalize and escape
\rand\n. - Use Actix's
HeaderValuefor constructing header values; it provides basic validity checks and helps prevent malformed headers that could facilitate splitting. - Continue to enforce mTLS for access control, but treat it as an authentication mechanism, not an input-sanitization mechanism.