Header Injection in Actix
How Header Injection Manifests in Actix
Header injection vulnerabilities in Actix applications typically occur when user-controlled input flows into HTTP response headers without proper validation. In Actix, this often manifests through the HttpResponse::insert_header method or the add_header extension method, where dynamic values from request parameters, query strings, or JSON payloads are directly inserted into response headers.
A common Actix-specific pattern that leads to header injection involves extracting values from the request body or query parameters and using them as header values. For example:
async fn echo_header(req: HttpRequest) -> HttpResponse {
let header_name = req.query_string()
.split('=')
.next()
.unwrap_or("X-Custom");
HttpResponse::Ok()
.insert_header((header_name, "injected-value"))
.finish()
}
This code is vulnerable because an attacker can control the header name through the query string, potentially injecting malicious headers like X-Forwarded-Host or Location for open redirect attacks. The vulnerability becomes more severe when combined with Actix's header normalization, which automatically lowercases header names, making it easier to bypass simple allowlist checks.
Another Actix-specific manifestation occurs when using the HttpResponseBuilder with dynamic content. Developers might extract values from JSON payloads and directly use them in headers:
async fn create_response(json: web::Json<MyData>) -> HttpResponse {
let custom_header = json.header_name.clone();
let custom_value = json.header_value.clone();
HttpResponse::Ok()
.insert_header((custom_header, custom_value))
.finish()
}
The danger here is that both the header name and value are user-controlled, allowing for CRLF injection attacks where an attacker can insert newline characters to create additional headers or modify the response structure entirely.
Actix-Specific Detection
Detecting header injection in Actix applications requires examining how user input flows into header construction. The most effective approach is static code analysis combined with runtime testing. Using middleBrick's API security scanner, you can identify header injection vulnerabilities by scanning your Actix endpoints with the command:
npx middlebrick scan http://localhost:8080/api/endpoint
middleBrick tests for header injection by sending payloads containing CRLF sequences (%0A%0D) and observing if the response contains unexpected headers. The scanner specifically looks for Actix's insert_header and add_header usage patterns where the header name or value originates from request data.
For manual detection in Actix codebases, search for these patterns:
grep -r "insert_header" . | grep -E "(req\.|query\(|json\.|form\()"
grep -r "add_header" . | grep -E "(req\.|query\(|json\.|form\()"
Pay special attention to Actix's web::Query, web::Json, and web::Form extractors, as these are common sources of user-controlled data that might flow into headers. The scanner also checks for Actix's HttpResponse::default combined with dynamic header setting, which is another indicator of potential injection points.
middleBrick's LLM security checks are particularly relevant for Actix applications using AI features, as header injection can be used to manipulate system prompts or extract sensitive information from AI responses. The scanner tests for 27 regex patterns that detect system prompt leakage through header manipulation.
Actix-Specific Remediation
Remediating header injection in Actix requires a defense-in-depth approach. The most effective strategy is to implement strict allowlists for header names and values. Actix provides several mechanisms for safe header handling:
use actix_web::{http::header, HttpResponse};
async fn safe_response(req: HttpRequest) -> HttpResponse {
let safe_headers = vec!["x-custom", "x-request-id"];
let header_name = req.query_string()
.split('=')
.next()
.unwrap_or("x-custom");
// Validate header name against allowlist
if !safe_headers.contains(&header_name) {
return HttpResponse::BadRequest()
.body("Invalid header name");
}
// Validate and sanitize header value
let header_value = req.query_string()
.split('=')
.nth(1)
.unwrap_or_default();
// Remove CRLF characters
let sanitized_value = header_value.replace(["\r", "\n"], "");
HttpResponse::Ok()
.insert_header((header_name, sanitized_value))
.finish()
}
For Actix applications that need to handle dynamic headers, use the header::IntoHeaderValue trait to ensure values are properly encoded:
use actix_web::{http::header, HttpResponse};
async fn dynamic_headers(json: web::Json<MyData>) -> HttpResponse {
let header_name = json.header_name.clone();
let header_value = json.header_value.clone();
// Validate header name format
if !header_name.chars().all(|c| c.is_alphanumeric() || c == '-') {
return HttpResponse::BadRequest()
.body("Invalid header name format");
}
// Create safe header value
let safe_value = match header::HeaderValue::from_str(&header_value) {
Ok(value) => value,
Err(_) => return HttpResponse::BadRequest()
.body("Invalid header value"),
};
HttpResponse::Ok()
.insert_header((header_name, safe_value))
.finish()
}
For Actix applications using middleware, implement header validation at the middleware level to catch injection attempts before they reach handlers:
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, middleware::Transform};
use futures_util::future::{ready, Ready};
struct HeaderValidation;
impl<S, B> Transform<S> for HeaderValidation
where
S: actix_web::dev::Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = HeaderValidationMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(HeaderValidationMiddleware { service }))
}
}
struct HeaderValidationMiddleware<S> {
service: S,
}
impl<S, B> actix_web::dev::Service for HeaderValidationMiddleware<S>
where
S: actix_web::dev::Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(
&mut self,
cx: &mut std::task::Context,
) -> std::task::Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
// Check for suspicious header patterns
if let Some(headers) = req.headers().get("X-Custom") {
if headers.to_string().contains(["\r", "\n"]) {
// Block request with suspicious headers
return ready(Err(actix_web::error::ErrorBadRequest(
"Suspicious header content detected",
)));
}
}
self.service.call(req)
}
}
This middleware validates headers before they reach your application logic, providing an additional layer of protection against header injection attacks in Actix applications.
Frequently Asked Questions
How can I test my Actix application for header injection vulnerabilities?
npx middlebrick scan <your-actix-endpoint>. The scanner tests for header injection by sending payloads with CRLF sequences and checking if they result in unexpected headers or response modifications. You can also manually test by sending requests with %0A%0D sequences in query parameters and observing the response headers.