HIGH crlf injectionactix

Crlf Injection in Actix

How Crlf Injection Manifests in Actix

CRLF injection in Actix occurs when user-controlled data is incorporated into HTTP response headers without proper sanitization. Actix Web, being a high-performance Rust framework, handles HTTP requests and responses through its extractor system and response builders. The vulnerability arises when response headers are constructed using unsanitized input.

Consider this Actix handler that creates a Location header:

async fn redirect(req: HttpRequest) -> impl Responder {
    let user_input = req.query_string();
    let location = format!("/redirect?{}&status=302", user_input);
    
    HttpResponse::Found()
        .insert_header((http::header::LOCATION, location))
        .finish()
}

If an attacker submits foo%0D%0ASet-Cookie%3A+session%3Devil as the query parameter, the response header becomes:

Location: /redirect?foo
Set-Cookie: session=evil
status=302

This injects a Set-Cookie header that the victim's browser will process, allowing session fixation attacks. The %0D%0A sequences represent CRLF characters that break out of the intended header context.

Another common pattern involves header reflection:

async fn echo_header(req: HttpRequest) -> impl Responder {
    let header_value = req.headers().get("X-Reflect").unwrap_or_default();
    
    HttpResponse::Ok()
        .insert_header((http::header::CONTENT_TYPE, "text/plain"))
        .body(format!("You sent: {}", header_value.as_bytes()))
}

When the header value contains CRLF sequences, they create new header lines in the response. Actix's type system doesn't prevent this because header values are treated as opaque byte sequences.

Actix's middleware chain can also propagate CRLF injection. A middleware that logs and forwards headers without validation becomes a vector:

struct LoggingMiddleware;

impl Middleware for LoggingMiddleware {
    fn on_request(&self, req: &mut Request, _payload: &mut Payload) -> Result<(), Error> {
        let user_agent = req.headers().get("user-agent").unwrap_or_default();
        log::info!("User agent: {}", user_agent.as_bytes());
        Ok(())
    }
}

If the logged user-agent is later used in a response header without sanitization, the injection persists through the middleware pipeline.

Actix-Specific Detection

Detecting CRLF injection in Actix applications requires examining both the source code and runtime behavior. Static analysis should focus on header construction patterns and input usage.

Code review patterns to identify:

use actix_web::{http, HttpResponse, Responder};

// Vulnerable pattern - direct interpolation
let location = format!("/redirect?{}", user_input);
HttpResponse::Found().insert_header((http::header::LOCATION, location)).finish()

Look for format!, String::from_utf8_lossy, or to_string calls on user input that feeds into insert_header or append_header.

Dynamic detection with middleBrick scans Actix endpoints for CRLF injection by:

  • Sending payloads containing %0D%0A sequences in query parameters, headers, and body data
  • Analyzing response headers for unexpected header lines that match injection patterns
  • Checking for Set-Cookie, Location, and other sensitive headers that appear unexpectedly
  • Validating that response structure matches the documented OpenAPI spec

middleBrick's black-box scanning tests unauthenticated endpoints by default, making it effective for detecting publicly exposed Actix APIs. The scanner identifies when Actix's response builder incorrectly processes CRLF sequences, which wouldn't be caught by Rust's compile-time checks since the issue is semantic rather than syntactic.

For comprehensive testing, middleBrick's CLI can scan specific Actix endpoints:

middlebrick scan https://api.example.com/auth/login

The scanner reports findings with severity levels and maps them to OWASP API Security Top 10 categories, specifically A04:2019 - Injection and A10:2019 - Insufficient Logging & Monitoring.

Actix-Specific Remediation

Remediating CRLF injection in Actix requires input validation and safe header construction. The most robust approach is to avoid string interpolation for headers entirely.

Safe header construction using Actix's type system:

use actix_web::{http, HttpResponse, Responder};

async fn safe_redirect(status: u16, path: &str) -> impl Responder {
    // Validate status code is numeric and within range
    if status < 300 || status > 399 {
        return HttpResponse::BadRequest().body("Invalid status code");
    }
    
    // Validate path contains no CRLF characters
    if path.contains('\r') || path.contains('\n') {
        return HttpResponse::BadRequest().body("Invalid path");
    }
    
    let location = format!("/{}", path);
    
    HttpResponse::build(http::StatusCode::from_u16(status).unwrap_or(http::StatusCode::FOUND))
        .insert_header((http::header::LOCATION, location))
        .finish()
}

Input validation should occur before any header construction:

use actix_web::{http, HttpResponse, Responder};

fn validate_header_value(value: &str) -> Result<String, HttpResponse> {
    // Check for CRLF characters
    if value.contains('\r') || value.contains('\n') {
        return Err(HttpResponse::BadRequest().body("Header contains invalid characters"));
    }
    
    // Check for header injection patterns
    let lower = value.to_lowercase();
    if lower.contains("content-type") || lower.contains("set-cookie") || lower.contains("location") {
        return Err(HttpResponse::BadRequest().body("Header injection attempt detected"));
    }
    
    Ok(value.to_string())
}

async fn safe_echo_header(
    req: HttpRequest,
    header_name: actix_web::http::header::HeaderName,
) -> impl Responder {
    let header_value = req.headers().get(&header_name);
    
    let safe_value = match header_value {
        Some(val) => match validate_header_value(val.to_str().unwrap_or_default()) {
            Ok(s) => s,
            Err(resp) => return resp,
        },
        None => "No header provided".to_string(),
    };
    
    HttpResponse::Ok()
        .insert_header((http::header::CONTENT_TYPE, "text/plain"))
        .body(format!("You sent: {}", safe_value))
}

For middleware that processes headers, implement validation at the middleware level:

use actix_web::{dev::Service, dev::ServiceRequest, dev::ServiceResponse, Error};

struct SecureHeadersMiddleware;

impl Transform for SecureHeadersMiddleware
where
    S: Service,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse;
    type Error = Error;
    type InitError = ();
    type Transform = SecureHeadersMiddlewareMiddleware;

    fn new_transform(&self, service: S) -> Result<Self::Transform, Self::InitError> {
        Ok(SecureHeadersMiddlewareMiddleware { service })
    }
}

struct SecureHeadersMiddlewareMiddleware<S> {
    service: S,
}

impl<S> Service for SecureHeadersMiddlewareMiddleware<S>
where
    S: Service,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse;
    type Error = Error;
    type Future = S::Future;

    fn poll_ready(&self, cx: &mut std::task::Context) -> std::task::Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, mut req: ServiceRequest) -> Self::Future {
        // Validate all headers for CRLF injection
        for (name, value) in req.headers().iter() {
            if let Ok(val_str) = value.to_str() {
                if val_str.contains('\r') || val_str.contains('\n') {
                    req.headers_mut().remove(name);
                }
            }
        }
        
        self.service.call(req)
    }
}

The key principle: never trust user input when constructing HTTP headers. Validate, sanitize, and use Actix's type-safe APIs whenever possible.

Frequently Asked Questions

How does middleBrick detect CRLF injection in Actix applications?
middleBrick performs black-box scanning by sending payloads containing %0D%0A sequences to Actix endpoints. It analyzes response headers for unexpected header lines, validates against OpenAPI specs, and checks for sensitive headers like Set-Cookie or Location that appear without authorization. The scanner identifies when Actix's response builder incorrectly processes CRLF sequences, providing severity levels and OWASP category mappings.
Can Rust's type system prevent CRLF injection in Actix?
No, Rust's type system cannot prevent CRLF injection because the vulnerability is semantic rather than syntactic. Actix treats header values as opaque byte sequences (HeaderValue type), and the framework doesn't validate for CRLF characters. The injection occurs when user-controlled data is interpolated into header construction, which is a logic error that compile-time type checking cannot detect.